mirror of
https://github.com/C9Glax/tranga.git
synced 2025-07-07 11:21:35 +02:00
Compare commits
19 Commits
6f5823596a
...
JobQueue-S
Author | SHA1 | Date | |
---|---|---|---|
6b4317834d | |||
88fef8417c | |||
eb9fc08b2d | |||
9743bb6e8e | |||
e8d612557f | |||
cf2dbeaf6a | |||
84940c414c | |||
ea627081b8 | |||
a90a6fb200 | |||
c3a0bb03e9 | |||
f8ccd2d69e | |||
ad224190a2 | |||
f05f2cc8e0 | |||
d6f0630a99 | |||
0ac4c23ac9 | |||
d6847d769e | |||
f6f5e21151 | |||
da3b5078af | |||
681d56710a |
@ -9,7 +9,7 @@ namespace API.Controllers;
|
||||
[ApiVersion(2)]
|
||||
[ApiController]
|
||||
[Route("v{v:apiVersion}/[controller]")]
|
||||
public class FileLibraryController(IServiceScope scope) : Controller
|
||||
public class FileLibraryController(MangaContext context) : Controller
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns all <see cref="FileLibrary"/>
|
||||
@ -19,8 +19,6 @@ public class FileLibraryController(IServiceScope scope) : Controller
|
||||
[ProducesResponseType<FileLibrary[]>(Status200OK, "application/json")]
|
||||
public IActionResult GetFileLibraries()
|
||||
{
|
||||
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||
|
||||
return Ok(context.FileLibraries.ToArray());
|
||||
}
|
||||
|
||||
@ -35,7 +33,6 @@ public class FileLibraryController(IServiceScope scope) : Controller
|
||||
[ProducesResponseType(Status404NotFound)]
|
||||
public IActionResult GetFileLibrary(string FileLibraryId)
|
||||
{
|
||||
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||
if (context.FileLibraries.Find(FileLibraryId) is not { } library)
|
||||
return NotFound();
|
||||
|
||||
@ -56,14 +53,13 @@ public class FileLibraryController(IServiceScope scope) : Controller
|
||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||
public IActionResult ChangeLibraryBasePath(string FileLibraryId, [FromBody]string newBasePath)
|
||||
{
|
||||
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||
if (context.FileLibraries.Find(FileLibraryId) is not { } library)
|
||||
return NotFound();
|
||||
|
||||
//TODO Path check
|
||||
library.BasePath = newBasePath;
|
||||
|
||||
if(context.Sync().Result is { success: false } result)
|
||||
if(context.Sync() is { success: false } result)
|
||||
return StatusCode(Status500InternalServerError, result.exceptionMessage);
|
||||
return Ok();
|
||||
}
|
||||
@ -83,14 +79,13 @@ public class FileLibraryController(IServiceScope scope) : Controller
|
||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||
public IActionResult ChangeLibraryName(string FileLibraryId, [FromBody] string newName)
|
||||
{
|
||||
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||
if (context.FileLibraries.Find(FileLibraryId) is not { } library)
|
||||
return NotFound();
|
||||
|
||||
//TODO Name check
|
||||
library.LibraryName = newName;
|
||||
|
||||
if(context.Sync().Result is { success: false } result)
|
||||
if(context.Sync() is { success: false } result)
|
||||
return StatusCode(Status500InternalServerError, result.exceptionMessage);
|
||||
return Ok();
|
||||
}
|
||||
@ -106,12 +101,11 @@ public class FileLibraryController(IServiceScope scope) : Controller
|
||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||
public IActionResult CreateNewLibrary([FromBody]FileLibrary library)
|
||||
{
|
||||
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||
|
||||
//TODO Parameter check
|
||||
context.FileLibraries.Add(library);
|
||||
|
||||
if(context.Sync().Result is { success: false } result)
|
||||
if(context.Sync() is { success: false } result)
|
||||
return StatusCode(Status500InternalServerError, result.exceptionMessage);
|
||||
return Created();
|
||||
}
|
||||
@ -128,13 +122,12 @@ public class FileLibraryController(IServiceScope scope) : Controller
|
||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||
public IActionResult DeleteLocalLibrary(string FileLibraryId)
|
||||
{
|
||||
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||
if (context.FileLibraries.Find(FileLibraryId) is not { } library)
|
||||
return NotFound();
|
||||
|
||||
context.FileLibraries.Remove(library);
|
||||
|
||||
if(context.Sync().Result is { success: false } result)
|
||||
if(context.Sync() is { success: false } result)
|
||||
return StatusCode(Status500InternalServerError, result.exceptionMessage);
|
||||
return Ok();
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ namespace API.Controllers;
|
||||
[ApiVersion(2)]
|
||||
[ApiController]
|
||||
[Route("v{v:apiVersion}/[controller]")]
|
||||
public class LibraryConnectorController(IServiceScope scope) : Controller
|
||||
public class LibraryConnectorController(LibraryContext context) : Controller
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets all configured <see cref="LibraryConnector"/>
|
||||
@ -20,8 +20,6 @@ public class LibraryConnectorController(IServiceScope scope) : Controller
|
||||
[ProducesResponseType<LibraryConnector[]>(Status200OK, "application/json")]
|
||||
public IActionResult GetAllConnectors()
|
||||
{
|
||||
LibraryContext context = scope.ServiceProvider.GetRequiredService<LibraryContext>();
|
||||
|
||||
LibraryConnector[] connectors = context.LibraryConnectors.ToArray();
|
||||
|
||||
return Ok(connectors);
|
||||
@ -38,7 +36,6 @@ public class LibraryConnectorController(IServiceScope scope) : Controller
|
||||
[ProducesResponseType(Status404NotFound)]
|
||||
public IActionResult GetConnector(string LibraryConnectorId)
|
||||
{
|
||||
LibraryContext context = scope.ServiceProvider.GetRequiredService<LibraryContext>();
|
||||
if (context.LibraryConnectors.Find(LibraryConnectorId) is not { } connector)
|
||||
return NotFound();
|
||||
|
||||
@ -56,11 +53,10 @@ public class LibraryConnectorController(IServiceScope scope) : Controller
|
||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||
public IActionResult CreateConnector([FromBody]LibraryConnector libraryConnector)
|
||||
{
|
||||
LibraryContext context = scope.ServiceProvider.GetRequiredService<LibraryContext>();
|
||||
|
||||
context.LibraryConnectors.Add(libraryConnector);
|
||||
|
||||
if(context.Sync().Result is { success: false } result)
|
||||
if(context.Sync() is { success: false } result)
|
||||
return StatusCode(Status500InternalServerError, result.exceptionMessage);
|
||||
return Created();
|
||||
}
|
||||
@ -78,13 +74,12 @@ public class LibraryConnectorController(IServiceScope scope) : Controller
|
||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||
public IActionResult DeleteConnector(string LibraryConnectorId)
|
||||
{
|
||||
LibraryContext context = scope.ServiceProvider.GetRequiredService<LibraryContext>();
|
||||
if (context.LibraryConnectors.Find(LibraryConnectorId) is not { } connector)
|
||||
return NotFound();
|
||||
|
||||
context.LibraryConnectors.Remove(connector);
|
||||
|
||||
if(context.Sync().Result is { success: false } result)
|
||||
if(context.Sync() is { success: false } result)
|
||||
return StatusCode(Status500InternalServerError, result.exceptionMessage);
|
||||
return Ok();
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ namespace API.Controllers;
|
||||
[ApiVersion(2)]
|
||||
[ApiController]
|
||||
[Route("v{v:apiVersion}/[controller]")]
|
||||
public class MangaConnectorController(IServiceScope scope) : Controller
|
||||
public class MangaConnectorController(MangaContext context) : Controller
|
||||
{
|
||||
/// <summary>
|
||||
/// Get all <see cref="MangaConnector"/> (Scanlation-Sites)
|
||||
@ -20,7 +20,6 @@ public class MangaConnectorController(IServiceScope scope) : Controller
|
||||
[ProducesResponseType<MangaConnector[]>(Status200OK, "application/json")]
|
||||
public IActionResult GetConnectors()
|
||||
{
|
||||
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||
return Ok(context.MangaConnectors.Select(c => c.Name).ToArray());
|
||||
}
|
||||
|
||||
@ -35,7 +34,6 @@ public class MangaConnectorController(IServiceScope scope) : Controller
|
||||
[ProducesResponseType(Status404NotFound)]
|
||||
public IActionResult GetConnector(string MangaConnectorName)
|
||||
{
|
||||
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||
if(context.MangaConnectors.Find(MangaConnectorName) is not { } connector)
|
||||
return NotFound();
|
||||
|
||||
@ -50,7 +48,6 @@ public class MangaConnectorController(IServiceScope scope) : Controller
|
||||
[ProducesResponseType<MangaConnector[]>(Status200OK, "application/json")]
|
||||
public IActionResult GetEnabledConnectors()
|
||||
{
|
||||
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||
|
||||
return Ok(context.MangaConnectors.Where(c => c.Enabled).ToArray());
|
||||
}
|
||||
@ -63,7 +60,6 @@ public class MangaConnectorController(IServiceScope scope) : Controller
|
||||
[ProducesResponseType<MangaConnector[]>(Status200OK, "application/json")]
|
||||
public IActionResult GetDisabledConnectors()
|
||||
{
|
||||
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||
|
||||
return Ok(context.MangaConnectors.Where(c => c.Enabled == false).ToArray());
|
||||
}
|
||||
@ -82,13 +78,12 @@ public class MangaConnectorController(IServiceScope scope) : Controller
|
||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||
public IActionResult SetEnabled(string MangaConnectorName, bool Enabled)
|
||||
{
|
||||
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||
if(context.MangaConnectors.Find(MangaConnectorName) is not { } connector)
|
||||
return NotFound();
|
||||
|
||||
connector.Enabled = Enabled;
|
||||
|
||||
if(context.Sync().Result is { success: false } result)
|
||||
if(context.Sync() is { success: false } result)
|
||||
return StatusCode(Status500InternalServerError, result.exceptionMessage);
|
||||
return Accepted();
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ namespace API.Controllers;
|
||||
[ApiVersion(2)]
|
||||
[ApiController]
|
||||
[Route("v{v:apiVersion}/[controller]")]
|
||||
public class MangaController(IServiceScope scope) : Controller
|
||||
public class MangaController(MangaContext context) : Controller
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns all cached <see cref="Manga"/>
|
||||
@ -26,7 +26,6 @@ public class MangaController(IServiceScope scope) : Controller
|
||||
[ProducesResponseType<Manga[]>(Status200OK, "application/json")]
|
||||
public IActionResult GetAllManga()
|
||||
{
|
||||
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||
Manga[] ret = context.Mangas.ToArray();
|
||||
return Ok(ret);
|
||||
}
|
||||
@ -40,7 +39,6 @@ public class MangaController(IServiceScope scope) : Controller
|
||||
[ProducesResponseType<Manga[]>(Status200OK, "application/json")]
|
||||
public IActionResult GetManga([FromBody]string[] MangaIds)
|
||||
{
|
||||
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||
Manga[] ret = context.Mangas.Where(m => MangaIds.Contains(m.Key)).ToArray();
|
||||
return Ok(ret);
|
||||
}
|
||||
@ -56,7 +54,6 @@ public class MangaController(IServiceScope scope) : Controller
|
||||
[ProducesResponseType(Status404NotFound)]
|
||||
public IActionResult GetManga(string MangaId)
|
||||
{
|
||||
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||
if (context.Mangas.Find(MangaId) is not { } manga)
|
||||
return NotFound(nameof(MangaId));
|
||||
return Ok(manga);
|
||||
@ -75,13 +72,12 @@ public class MangaController(IServiceScope scope) : Controller
|
||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||
public IActionResult DeleteManga(string MangaId)
|
||||
{
|
||||
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||
if (context.Mangas.Find(MangaId) is not { } manga)
|
||||
return NotFound(nameof(MangaId));
|
||||
|
||||
context.Mangas.Remove(manga);
|
||||
|
||||
if(context.Sync().Result is { success: false } result)
|
||||
if(context.Sync() is { success: false } result)
|
||||
return StatusCode(Status500InternalServerError, result.exceptionMessage);
|
||||
return Ok();
|
||||
}
|
||||
@ -99,7 +95,6 @@ public class MangaController(IServiceScope scope) : Controller
|
||||
[ProducesResponseType(Status404NotFound)]
|
||||
public IActionResult MergeIntoManga(string MangaIdFrom, string MangaIdInto)
|
||||
{
|
||||
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||
if (context.Mangas.Find(MangaIdFrom) is not { } from)
|
||||
return NotFound(nameof(MangaIdFrom));
|
||||
if (context.Mangas.Find(MangaIdInto) is not { } into)
|
||||
@ -130,16 +125,15 @@ public class MangaController(IServiceScope scope) : Controller
|
||||
[ProducesResponseType<int>(Status503ServiceUnavailable, "text/plain")]
|
||||
public IActionResult GetCover(string MangaId, [FromQuery]int? width, [FromQuery]int? height)
|
||||
{
|
||||
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||
if (context.Mangas.Find(MangaId) is not { } manga)
|
||||
return NotFound(nameof(MangaId));
|
||||
|
||||
if (!System.IO.File.Exists(manga.CoverFileNameInCache))
|
||||
{
|
||||
if (Tranga.GetRunningWorkers().Any(worker => worker is DownloadCoverFromMangaconnectorWorker w && w.MangaConnectorId.ObjId == MangaId))
|
||||
if (Tranga.GetRunningWorkers().Any(worker => worker is DownloadCoverFromMangaconnectorWorker w && context.MangaConnectorToManga.Find(w.MangaConnectorIdId)?.ObjId == MangaId))
|
||||
{
|
||||
Response.Headers.Append("Retry-After", $"{TrangaSettings.workCycleTimeout * 2 / 1000:D}");
|
||||
return StatusCode(Status503ServiceUnavailable, TrangaSettings.workCycleTimeout * 2 / 1000);
|
||||
Response.Headers.Append("Retry-After", $"{Tranga.Settings.WorkCycleTimeoutMs * 2 / 1000:D}");
|
||||
return StatusCode(Status503ServiceUnavailable, Tranga.Settings.WorkCycleTimeoutMs * 2 / 1000);
|
||||
}
|
||||
else
|
||||
return NoContent();
|
||||
@ -176,7 +170,6 @@ public class MangaController(IServiceScope scope) : Controller
|
||||
[ProducesResponseType(Status404NotFound)]
|
||||
public IActionResult GetChapters(string MangaId)
|
||||
{
|
||||
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||
if (context.Mangas.Find(MangaId) is not { } manga)
|
||||
return NotFound(nameof(MangaId));
|
||||
|
||||
@ -197,7 +190,6 @@ public class MangaController(IServiceScope scope) : Controller
|
||||
[ProducesResponseType(Status404NotFound)]
|
||||
public IActionResult GetChaptersDownloaded(string MangaId)
|
||||
{
|
||||
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||
if (context.Mangas.Find(MangaId) is not { } manga)
|
||||
return NotFound(nameof(MangaId));
|
||||
|
||||
@ -221,7 +213,6 @@ public class MangaController(IServiceScope scope) : Controller
|
||||
[ProducesResponseType(Status404NotFound)]
|
||||
public IActionResult GetChaptersNotDownloaded(string MangaId)
|
||||
{
|
||||
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||
if (context.Mangas.Find(MangaId) is not { } manga)
|
||||
return NotFound(nameof(MangaId));
|
||||
|
||||
@ -249,17 +240,16 @@ public class MangaController(IServiceScope scope) : Controller
|
||||
[ProducesResponseType<int>(Status503ServiceUnavailable, "text/plain")]
|
||||
public IActionResult GetLatestChapter(string MangaId)
|
||||
{
|
||||
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||
if (context.Mangas.Find(MangaId) is not { } manga)
|
||||
return NotFound(nameof(MangaId));
|
||||
|
||||
List<Chapter> chapters = manga.Chapters.ToList();
|
||||
if (chapters.Count == 0)
|
||||
{
|
||||
if (Tranga.GetRunningWorkers().Any(worker => worker is RetrieveMangaChaptersFromMangaconnectorWorker w && w.MangaConnectorId.ObjId == MangaId && w.State < WorkerExecutionState.Completed))
|
||||
if (Tranga.GetRunningWorkers().Any(worker => worker is RetrieveMangaChaptersFromMangaconnectorWorker w && context.MangaConnectorToManga.Find(w.MangaConnectorIdId)?.ObjId == MangaId && w.State < WorkerExecutionState.Completed))
|
||||
{
|
||||
Response.Headers.Append("Retry-After", $"{TrangaSettings.workCycleTimeout * 2 / 1000:D}");
|
||||
return StatusCode(Status503ServiceUnavailable, TrangaSettings.workCycleTimeout * 2/ 1000);
|
||||
Response.Headers.Append("Retry-After", $"{Tranga.Settings.WorkCycleTimeoutMs * 2 / 1000:D}");
|
||||
return StatusCode(Status503ServiceUnavailable, Tranga.Settings.WorkCycleTimeoutMs * 2/ 1000);
|
||||
}else
|
||||
return Ok(0);
|
||||
}
|
||||
@ -288,17 +278,16 @@ public class MangaController(IServiceScope scope) : Controller
|
||||
[ProducesResponseType<int>(Status503ServiceUnavailable, "text/plain")]
|
||||
public IActionResult GetLatestChapterDownloaded(string MangaId)
|
||||
{
|
||||
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||
if (context.Mangas.Find(MangaId) is not { } manga)
|
||||
return NotFound(nameof(MangaId));
|
||||
|
||||
List<Chapter> chapters = manga.Chapters.ToList();
|
||||
if (chapters.Count == 0)
|
||||
{
|
||||
if (Tranga.GetRunningWorkers().Any(worker => worker is RetrieveMangaChaptersFromMangaconnectorWorker w && w.MangaConnectorId.ObjId == MangaId && w.State < WorkerExecutionState.Completed))
|
||||
if (Tranga.GetRunningWorkers().Any(worker => worker is RetrieveMangaChaptersFromMangaconnectorWorker w && context.MangaConnectorToManga.Find(w.MangaConnectorIdId)?.ObjId == MangaId && w.State < WorkerExecutionState.Completed))
|
||||
{
|
||||
Response.Headers.Append("Retry-After", $"{TrangaSettings.workCycleTimeout * 2 / 1000:D}");
|
||||
return StatusCode(Status503ServiceUnavailable, TrangaSettings.workCycleTimeout * 2/ 1000);
|
||||
Response.Headers.Append("Retry-After", $"{Tranga.Settings.WorkCycleTimeoutMs * 2 / 1000:D}");
|
||||
return StatusCode(Status503ServiceUnavailable, Tranga.Settings.WorkCycleTimeoutMs * 2/ 1000);
|
||||
}else
|
||||
return NoContent();
|
||||
}
|
||||
@ -324,12 +313,11 @@ public class MangaController(IServiceScope scope) : Controller
|
||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||
public IActionResult IgnoreChaptersBefore(string MangaId, [FromBody]float chapterThreshold)
|
||||
{
|
||||
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||
if (context.Mangas.Find(MangaId) is not { } manga)
|
||||
return NotFound();
|
||||
|
||||
manga.IgnoreChaptersBefore = chapterThreshold;
|
||||
if(context.Sync().Result is { success: false } result)
|
||||
if(context.Sync() is { success: false } result)
|
||||
return StatusCode(Status500InternalServerError, result.exceptionMessage);
|
||||
|
||||
return Accepted();
|
||||
@ -347,17 +335,56 @@ public class MangaController(IServiceScope scope) : Controller
|
||||
[ProducesResponseType(Status404NotFound)]
|
||||
public IActionResult MoveFolder(string MangaId, string LibraryId)
|
||||
{
|
||||
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||
if (context.Mangas.Find(MangaId) is not { } manga)
|
||||
return NotFound(nameof(MangaId));
|
||||
if(context.FileLibraries.Find(LibraryId) is not { } library)
|
||||
return NotFound(nameof(LibraryId));
|
||||
|
||||
MoveMangaLibraryWorker moveLibrary = new(manga, library, scope);
|
||||
UpdateChaptersDownloadedWorker updateDownloadedFiles = new(manga, [moveLibrary]);
|
||||
MoveMangaLibraryWorker moveLibrary = new(manga, library);
|
||||
|
||||
Tranga.AddWorkers([moveLibrary, updateDownloadedFiles]);
|
||||
Tranga.AddWorkers([moveLibrary]);
|
||||
|
||||
return Accepted();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// (Un-)Marks <see cref="Manga"/> as requested for Download from <see cref="MangaConnector"/>
|
||||
/// </summary>
|
||||
/// <param name="MangaId"><see cref="Manga"/> with <paramref name="MangaId"/></param>
|
||||
/// <param name="MangaConnectorName"><see cref="MangaConnector"/> with <paramref name="MangaConnectorName"/></param>
|
||||
/// <param name="IsRequested">true to mark as requested, false to mark as not-requested</param>
|
||||
/// <response code="200"></response>
|
||||
/// <response code="404"><paramref name="MangaId"/> or <paramref name="MangaConnectorName"/> not found</response>
|
||||
/// <response code="412"><see cref="Manga"/> was not linked to <see cref="MangaConnector"/>, so nothing changed</response>
|
||||
/// <response code="428"><see cref="Manga"/> is not linked to <see cref="MangaConnector"/> yet. Search for <see cref="Manga"/> on <see cref="MangaConnector"/> first (to create a <see cref="MangaConnectorId{T}"/>).</response>
|
||||
/// <response code="500">Error during Database Operation</response>
|
||||
[HttpPost("{MangaId}/SetAsDownloadFrom/{MangaConnectorName}/{IsRequested}")]
|
||||
[ProducesResponseType(Status200OK)]
|
||||
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||
[ProducesResponseType<string>(Status412PreconditionFailed, "text/plain")]
|
||||
[ProducesResponseType<string>(Status428PreconditionRequired, "text/plain")]
|
||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||
public IActionResult MarkAsRequested(string MangaId, string MangaConnectorName, bool IsRequested)
|
||||
{
|
||||
if (context.Mangas.Find(MangaId) is null)
|
||||
return NotFound(nameof(MangaId));
|
||||
if(context.MangaConnectors.Find(MangaConnectorName) is null)
|
||||
return NotFound(nameof(MangaConnectorName));
|
||||
|
||||
if (context.MangaConnectorToManga.FirstOrDefault(id => id.MangaConnectorName == MangaConnectorName && id.ObjId == MangaId) is not { } mcId)
|
||||
if(IsRequested)
|
||||
return StatusCode(Status428PreconditionRequired, "Don't know how to download this Manga from MangaConnector");
|
||||
else
|
||||
return StatusCode(Status412PreconditionFailed, "Not linked anyways.");
|
||||
|
||||
mcId.UseForDownload = IsRequested;
|
||||
if(context.Sync() is { success: false } result)
|
||||
return StatusCode(Status500InternalServerError, result.exceptionMessage);
|
||||
|
||||
DownloadCoverFromMangaconnectorWorker downloadCover = new(mcId);
|
||||
RetrieveMangaChaptersFromMangaconnectorWorker retrieveChapters = new(mcId, Tranga.Settings.DownloadLanguage);
|
||||
Tranga.AddWorkers([downloadCover, retrieveChapters]);
|
||||
|
||||
return Ok();
|
||||
}
|
||||
}
|
@ -11,7 +11,7 @@ namespace API.Controllers;
|
||||
[ApiVersion(2)]
|
||||
[ApiController]
|
||||
[Route("v{v:apiVersion}/[controller]")]
|
||||
public class MetadataFetcherController(IServiceScope scope) : Controller
|
||||
public class MetadataFetcherController(MangaContext context) : Controller
|
||||
{
|
||||
/// <summary>
|
||||
/// Get all <see cref="MetadataFetcher"/> (Metadata-Sites)
|
||||
@ -32,8 +32,6 @@ public class MetadataFetcherController(IServiceScope scope) : Controller
|
||||
[ProducesResponseType<MetadataEntry[]>(Status200OK, "application/json")]
|
||||
public IActionResult GetLinkedEntries()
|
||||
{
|
||||
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||
|
||||
return Ok(context.MetadataEntries.ToArray());
|
||||
}
|
||||
|
||||
@ -52,7 +50,6 @@ public class MetadataFetcherController(IServiceScope scope) : Controller
|
||||
[ProducesResponseType(Status404NotFound)]
|
||||
public IActionResult SearchMangaMetadata(string MangaId, string MetadataFetcherName, [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)]string? searchTerm = null)
|
||||
{
|
||||
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||
if(context.Mangas.Find(MangaId) is not { } manga)
|
||||
return NotFound();
|
||||
if(Tranga.MetadataFetchers.FirstOrDefault(f => f.Name == MetadataFetcherName) is not { } fetcher)
|
||||
@ -79,7 +76,6 @@ public class MetadataFetcherController(IServiceScope scope) : Controller
|
||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||
public IActionResult LinkMangaMetadata(string MangaId, string MetadataFetcherName, [FromBody]string Identifier)
|
||||
{
|
||||
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||
if(context.Mangas.Find(MangaId) is not { } manga)
|
||||
return NotFound();
|
||||
if(Tranga.MetadataFetchers.FirstOrDefault(f => f.Name == MetadataFetcherName) is not { } fetcher)
|
||||
@ -88,7 +84,7 @@ public class MetadataFetcherController(IServiceScope scope) : Controller
|
||||
MetadataEntry entry = fetcher.CreateMetadataEntry(manga, Identifier);
|
||||
context.MetadataEntries.Add(entry);
|
||||
|
||||
if(context.Sync().Result is { } errorMessage)
|
||||
if(context.Sync() is { } errorMessage)
|
||||
return StatusCode(Status500InternalServerError, errorMessage);
|
||||
return Ok(entry);
|
||||
}
|
||||
@ -109,7 +105,6 @@ public class MetadataFetcherController(IServiceScope scope) : Controller
|
||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||
public IActionResult UnlinkMangaMetadata(string MangaId, string MetadataFetcherName)
|
||||
{
|
||||
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||
if(context.Mangas.Find(MangaId) is null)
|
||||
return NotFound();
|
||||
if(Tranga.MetadataFetchers.FirstOrDefault(f => f.Name == MetadataFetcherName) is null)
|
||||
@ -119,7 +114,7 @@ public class MetadataFetcherController(IServiceScope scope) : Controller
|
||||
|
||||
context.Remove(entry);
|
||||
|
||||
if(context.Sync().Result is { success: false } result)
|
||||
if(context.Sync() is { success: false } result)
|
||||
return StatusCode(Status500InternalServerError, result.exceptionMessage);
|
||||
return Ok();
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ namespace API.Controllers;
|
||||
[ApiController]
|
||||
[Produces("application/json")]
|
||||
[Route("v{v:apiVersion}/[controller]")]
|
||||
public class NotificationConnectorController(IServiceScope scope) : Controller
|
||||
public class NotificationConnectorController(NotificationsContext context) : Controller
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets all configured <see cref="NotificationConnector"/>
|
||||
@ -23,7 +23,6 @@ public class NotificationConnectorController(IServiceScope scope) : Controller
|
||||
[ProducesResponseType<NotificationConnector[]>(Status200OK, "application/json")]
|
||||
public IActionResult GetAllConnectors()
|
||||
{
|
||||
NotificationsContext context = scope.ServiceProvider.GetRequiredService<NotificationsContext>();
|
||||
|
||||
return Ok(context.NotificationConnectors.ToArray());
|
||||
}
|
||||
@ -39,7 +38,6 @@ public class NotificationConnectorController(IServiceScope scope) : Controller
|
||||
[ProducesResponseType(Status404NotFound)]
|
||||
public IActionResult GetConnector(string Name)
|
||||
{
|
||||
NotificationsContext context = scope.ServiceProvider.GetRequiredService<NotificationsContext>();
|
||||
if(context.NotificationConnectors.Find(Name) is not { } connector)
|
||||
return NotFound();
|
||||
|
||||
@ -58,11 +56,10 @@ public class NotificationConnectorController(IServiceScope scope) : Controller
|
||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||
public IActionResult CreateConnector([FromBody]NotificationConnector notificationConnector)
|
||||
{
|
||||
NotificationsContext context = scope.ServiceProvider.GetRequiredService<NotificationsContext>();
|
||||
|
||||
context.NotificationConnectors.Add(notificationConnector);
|
||||
|
||||
if(context.Sync().Result is { success: false } result)
|
||||
if(context.Sync() is { success: false } result)
|
||||
return StatusCode(Status500InternalServerError, result.exceptionMessage);
|
||||
return Created();
|
||||
}
|
||||
@ -150,13 +147,12 @@ public class NotificationConnectorController(IServiceScope scope) : Controller
|
||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||
public IActionResult DeleteConnector(string Name)
|
||||
{
|
||||
NotificationsContext context = scope.ServiceProvider.GetRequiredService<NotificationsContext>();
|
||||
if(context.NotificationConnectors.Find(Name) is not { } connector)
|
||||
return NotFound();
|
||||
|
||||
context.NotificationConnectors.Remove(connector);
|
||||
|
||||
if(context.Sync().Result is { success: false } result)
|
||||
if(context.Sync() is { success: false } result)
|
||||
return StatusCode(Status500InternalServerError, result.exceptionMessage);
|
||||
return Created();
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ namespace API.Controllers;
|
||||
[ApiVersion(2)]
|
||||
[ApiController]
|
||||
[Route("v{v:apiVersion}/[controller]")]
|
||||
public class QueryController(IServiceScope scope) : Controller
|
||||
public class QueryController(MangaContext context) : Controller
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the <see cref="Author"/> with <paramref name="AuthorId"/>
|
||||
@ -22,7 +22,6 @@ public class QueryController(IServiceScope scope) : Controller
|
||||
[ProducesResponseType(Status404NotFound)]
|
||||
public IActionResult GetAuthor(string AuthorId)
|
||||
{
|
||||
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||
if (context.Authors.Find(AuthorId) is not { } author)
|
||||
return NotFound();
|
||||
|
||||
@ -39,7 +38,6 @@ public class QueryController(IServiceScope scope) : Controller
|
||||
[ProducesResponseType<Manga[]>(Status200OK, "application/json")]
|
||||
public IActionResult GetMangaWithAuthorIds(string AuthorId)
|
||||
{
|
||||
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||
if (context.Authors.Find(AuthorId) is not { } author)
|
||||
return NotFound();
|
||||
|
||||
@ -56,7 +54,6 @@ public class QueryController(IServiceScope scope) : Controller
|
||||
[ProducesResponseType<Manga[]>(Status200OK, "application/json")]
|
||||
public IActionResult GetMangasWithTag(string Tag)
|
||||
{
|
||||
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||
if (context.Tags.Find(Tag) is not { } tag)
|
||||
return NotFound();
|
||||
|
||||
@ -73,7 +70,6 @@ public class QueryController(IServiceScope scope) : Controller
|
||||
[ProducesResponseType<Chapter>(Status200OK, "application/json")]
|
||||
public IActionResult GetChapter(string ChapterId)
|
||||
{
|
||||
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||
if (context.Chapters.Find(ChapterId) is not { } chapter)
|
||||
return NotFound();
|
||||
|
||||
|
@ -10,7 +10,7 @@ namespace API.Controllers;
|
||||
[ApiVersion(2)]
|
||||
[ApiController]
|
||||
[Route("v{v:apiVersion}/[controller]")]
|
||||
public class SearchController(IServiceScope scope) : Controller
|
||||
public class SearchController(MangaContext context) : Controller
|
||||
{
|
||||
/// <summary>
|
||||
/// Initiate a search for a <see cref="Manga"/> on <see cref="MangaConnector"/> with searchTerm
|
||||
@ -26,7 +26,6 @@ public class SearchController(IServiceScope scope) : Controller
|
||||
[ProducesResponseType(Status406NotAcceptable)]
|
||||
public IActionResult SearchManga(string MangaConnectorName, string Query)
|
||||
{
|
||||
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||
if(context.MangaConnectors.Find(MangaConnectorName) is not { } connector)
|
||||
return NotFound();
|
||||
if (connector.Enabled is false)
|
||||
@ -36,7 +35,7 @@ public class SearchController(IServiceScope scope) : Controller
|
||||
List<Manga> retMangas = new();
|
||||
foreach ((Manga manga, MangaConnectorId<Manga> mcId) manga in mangas)
|
||||
{
|
||||
if(AddMangaToContext(manga, context) is { } add)
|
||||
if(Tranga.AddMangaToContext(manga, context, out Manga? add))
|
||||
retMangas.Add(add);
|
||||
}
|
||||
|
||||
@ -57,47 +56,15 @@ public class SearchController(IServiceScope scope) : Controller
|
||||
[ProducesResponseType(Status500InternalServerError)]
|
||||
public IActionResult GetMangaFromUrl([FromBody]string url)
|
||||
{
|
||||
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||
if (context.MangaConnectors.Find("Global") is not { } connector)
|
||||
return StatusCode(Status500InternalServerError, "Could not find Global Connector.");
|
||||
|
||||
if(connector.GetMangaFromUrl(url) is not { } manga)
|
||||
return NotFound();
|
||||
|
||||
if(AddMangaToContext(manga, context) is not { } add)
|
||||
if(Tranga.AddMangaToContext(manga, context, out Manga? add) == false)
|
||||
return StatusCode(Status500InternalServerError);
|
||||
|
||||
return Ok(add);
|
||||
}
|
||||
|
||||
private Manga? AddMangaToContext((Manga, MangaConnectorId<Manga>) manga, MangaContext context) => AddMangaToContext(manga.Item1, manga.Item2, context);
|
||||
|
||||
private static Manga? AddMangaToContext(Manga addManga, MangaConnectorId<Manga> addMcId, MangaContext context)
|
||||
{
|
||||
Manga manga = context.Mangas.Find(addManga.Key) ?? addManga;
|
||||
MangaConnectorId<Manga> mcId = context.MangaConnectorToManga.Find(addMcId.Key) ?? addMcId;
|
||||
mcId.Obj = manga;
|
||||
|
||||
IEnumerable<MangaTag> mergedTags = manga.MangaTags.Select(mt =>
|
||||
{
|
||||
MangaTag? inDb = context.Tags.Find(mt.Tag);
|
||||
return inDb ?? mt;
|
||||
});
|
||||
manga.MangaTags = mergedTags.ToList();
|
||||
|
||||
IEnumerable<Author> mergedAuthors = manga.Authors.Select(ma =>
|
||||
{
|
||||
Author? inDb = context.Authors.Find(ma.Key);
|
||||
return inDb ?? ma;
|
||||
});
|
||||
manga.Authors = mergedAuthors.ToList();
|
||||
|
||||
|
||||
if(context.MangaConnectorToManga.Find(addMcId.Key) is null)
|
||||
context.MangaConnectorToManga.Add(mcId);
|
||||
|
||||
if (context.Sync().Result is { success: false } )
|
||||
return null;
|
||||
return manga;
|
||||
}
|
||||
}
|
@ -1,9 +1,6 @@
|
||||
using API.MangaDownloadClients;
|
||||
using API.Schema.MangaContext;
|
||||
using API.Workers;
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using static Microsoft.AspNetCore.Http.StatusCodes;
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
@ -12,17 +9,17 @@ namespace API.Controllers;
|
||||
[ApiVersion(2)]
|
||||
[ApiController]
|
||||
[Route("v{v:apiVersion}/[controller]")]
|
||||
public class SettingsController(IServiceScope scope) : Controller
|
||||
public class SettingsController() : Controller
|
||||
{
|
||||
/// <summary>
|
||||
/// Get all Settings
|
||||
/// Get all <see cref="Tranga.Settings"/>
|
||||
/// </summary>
|
||||
/// <response code="200"></response>
|
||||
[HttpGet]
|
||||
[ProducesResponseType<JObject>(Status200OK, "application/json")]
|
||||
[ProducesResponseType<TrangaSettings>(Status200OK, "application/json")]
|
||||
public IActionResult GetSettings()
|
||||
{
|
||||
return Ok(JObject.Parse(TrangaSettings.Serialize()));
|
||||
return Ok(Tranga.Settings);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -33,7 +30,7 @@ public class SettingsController(IServiceScope scope) : Controller
|
||||
[ProducesResponseType<string>(Status200OK, "text/plain")]
|
||||
public IActionResult GetUserAgent()
|
||||
{
|
||||
return Ok(TrangaSettings.userAgent);
|
||||
return Ok(Tranga.Settings.UserAgent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -44,7 +41,8 @@ public class SettingsController(IServiceScope scope) : Controller
|
||||
[ProducesResponseType(Status200OK)]
|
||||
public IActionResult SetUserAgent([FromBody]string userAgent)
|
||||
{
|
||||
TrangaSettings.UpdateUserAgent(userAgent);
|
||||
//TODO Validate
|
||||
Tranga.Settings.SetUserAgent(userAgent);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
@ -56,7 +54,7 @@ public class SettingsController(IServiceScope scope) : Controller
|
||||
[ProducesResponseType(Status200OK)]
|
||||
public IActionResult ResetUserAgent()
|
||||
{
|
||||
TrangaSettings.UpdateUserAgent(TrangaSettings.DefaultUserAgent);
|
||||
Tranga.Settings.SetUserAgent(TrangaSettings.DefaultUserAgent);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
@ -68,7 +66,7 @@ public class SettingsController(IServiceScope scope) : Controller
|
||||
[ProducesResponseType<Dictionary<RequestType,int>>(Status200OK, "application/json")]
|
||||
public IActionResult GetRequestLimits()
|
||||
{
|
||||
return Ok(TrangaSettings.requestLimits);
|
||||
return Ok(Tranga.Settings.RequestLimits);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -96,7 +94,7 @@ public class SettingsController(IServiceScope scope) : Controller
|
||||
{
|
||||
if (requestLimit <= 0)
|
||||
return BadRequest();
|
||||
TrangaSettings.UpdateRequestLimit(RequestType, requestLimit);
|
||||
Tranga.Settings.SetRequestLimit(RequestType, requestLimit);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
@ -108,7 +106,7 @@ public class SettingsController(IServiceScope scope) : Controller
|
||||
[ProducesResponseType<string>(Status200OK)]
|
||||
public IActionResult ResetRequestLimits(RequestType RequestType)
|
||||
{
|
||||
TrangaSettings.UpdateRequestLimit(RequestType, TrangaSettings.DefaultRequestLimits[RequestType]);
|
||||
Tranga.Settings.SetRequestLimit(RequestType, TrangaSettings.DefaultRequestLimits[RequestType]);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
@ -120,35 +118,35 @@ public class SettingsController(IServiceScope scope) : Controller
|
||||
[ProducesResponseType<string>(Status200OK)]
|
||||
public IActionResult ResetRequestLimits()
|
||||
{
|
||||
TrangaSettings.ResetRequestLimits();
|
||||
Tranga.Settings.ResetRequestLimits();
|
||||
return Ok();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns Level of Image-Compression for Images
|
||||
/// </summary>
|
||||
/// <response code="200">JPEG compression-level as Integer</response>
|
||||
[HttpGet("ImageCompression")]
|
||||
/// <response code="200">JPEG ImageCompression-level as Integer</response>
|
||||
[HttpGet("ImageCompressionLevel")]
|
||||
[ProducesResponseType<int>(Status200OK, "text/plain")]
|
||||
public IActionResult GetImageCompression()
|
||||
{
|
||||
return Ok(TrangaSettings.compression);
|
||||
return Ok(Tranga.Settings.ImageCompression);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the Image-Compression-Level for Images
|
||||
/// </summary>
|
||||
/// <param name="level">100 to disable, 0-99 for JPEG compression-Level</param>
|
||||
/// <param name="level">100 to disable, 0-99 for JPEG ImageCompression-Level</param>
|
||||
/// <response code="200"></response>
|
||||
/// <response code="400">Level outside permitted range</response>
|
||||
[HttpPatch("ImageCompression")]
|
||||
[HttpPatch("ImageCompressionLevel/{level}")]
|
||||
[ProducesResponseType(Status200OK)]
|
||||
[ProducesResponseType(Status400BadRequest)]
|
||||
public IActionResult SetImageCompression([FromBody]int level)
|
||||
public IActionResult SetImageCompression(int level)
|
||||
{
|
||||
if (level < 1 || level > 100)
|
||||
return BadRequest();
|
||||
TrangaSettings.UpdateCompressImages(level);
|
||||
Tranga.Settings.UpdateImageCompression(level);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
@ -160,7 +158,7 @@ public class SettingsController(IServiceScope scope) : Controller
|
||||
[ProducesResponseType<bool>(Status200OK, "text/plain")]
|
||||
public IActionResult GetBwImagesToggle()
|
||||
{
|
||||
return Ok(TrangaSettings.bwImages);
|
||||
return Ok(Tranga.Settings.BlackWhiteImages);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -168,37 +166,11 @@ public class SettingsController(IServiceScope scope) : Controller
|
||||
/// </summary>
|
||||
/// <param name="enabled">true to enable</param>
|
||||
/// <response code="200"></response>
|
||||
[HttpPatch("BWImages")]
|
||||
[HttpPatch("BWImages/{enabled}")]
|
||||
[ProducesResponseType(Status200OK)]
|
||||
public IActionResult SetBwImagesToggle([FromBody]bool enabled)
|
||||
public IActionResult SetBwImagesToggle(bool enabled)
|
||||
{
|
||||
TrangaSettings.UpdateBwImages(enabled);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get state of April Fools Mode
|
||||
/// </summary>
|
||||
/// <remarks>April Fools Mode disables all downloads on April 1st</remarks>
|
||||
/// <response code="200">True if enabled</response>
|
||||
[HttpGet("AprilFoolsMode")]
|
||||
[ProducesResponseType<bool>(Status200OK, "text/plain")]
|
||||
public IActionResult GetAprilFoolsMode()
|
||||
{
|
||||
return Ok(TrangaSettings.aprilFoolsMode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enable/Disable April Fools Mode
|
||||
/// </summary>
|
||||
/// <remarks>April Fools Mode disables all downloads on April 1st</remarks>
|
||||
/// <param name="enabled">true to enable</param>
|
||||
/// <response code="200"></response>
|
||||
[HttpPatch("AprilFoolsMode")]
|
||||
[ProducesResponseType(Status200OK)]
|
||||
public IActionResult SetAprilFoolsMode([FromBody]bool enabled)
|
||||
{
|
||||
TrangaSettings.UpdateAprilFoolsMode(enabled);
|
||||
Tranga.Settings.SetBlackWhiteImageEnabled(enabled);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
@ -224,7 +196,7 @@ public class SettingsController(IServiceScope scope) : Controller
|
||||
[ProducesResponseType<string>(Status200OK, "text/plain")]
|
||||
public IActionResult GetCustomNamingScheme()
|
||||
{
|
||||
return Ok(TrangaSettings.chapterNamingScheme);
|
||||
return Ok(Tranga.Settings.ChapterNamingScheme);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -247,13 +219,8 @@ public class SettingsController(IServiceScope scope) : Controller
|
||||
[ProducesResponseType(Status200OK)]
|
||||
public IActionResult SetCustomNamingScheme([FromBody]string namingScheme)
|
||||
{
|
||||
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||
|
||||
Dictionary<Chapter, string> oldPaths = context.Chapters.ToDictionary(c => c, c => c.FullArchiveFilePath);
|
||||
TrangaSettings.UpdateChapterNamingScheme(namingScheme);
|
||||
MoveFileOrFolderWorker[] newJobs = oldPaths
|
||||
.Select(kv => new MoveFileOrFolderWorker(kv.Value, kv.Key.FullArchiveFilePath)).ToArray();
|
||||
Tranga.AddWorkers(newJobs);
|
||||
//TODO Move old Chapters
|
||||
Tranga.Settings.SetChapterNamingScheme(namingScheme);
|
||||
|
||||
return Ok();
|
||||
}
|
||||
@ -267,7 +234,7 @@ public class SettingsController(IServiceScope scope) : Controller
|
||||
[ProducesResponseType(Status200OK)]
|
||||
public IActionResult SetFlareSolverrUrl([FromBody]string flareSolverrUrl)
|
||||
{
|
||||
TrangaSettings.UpdateFlareSolverrUrl(flareSolverrUrl);
|
||||
Tranga.Settings.SetFlareSolverrUrl(flareSolverrUrl);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
@ -279,7 +246,7 @@ public class SettingsController(IServiceScope scope) : Controller
|
||||
[ProducesResponseType(Status200OK)]
|
||||
public IActionResult ClearFlareSolverrUrl()
|
||||
{
|
||||
TrangaSettings.UpdateFlareSolverrUrl(string.Empty);
|
||||
Tranga.Settings.SetFlareSolverrUrl(string.Empty);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
@ -298,4 +265,28 @@ public class SettingsController(IServiceScope scope) : Controller
|
||||
RequestResult result = client.MakeRequestInternal(knownProtectedUrl);
|
||||
return (int)result.statusCode >= 200 && (int)result.statusCode < 300 ? Ok() : StatusCode(500, result.statusCode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the language in which Manga are downloaded
|
||||
/// </summary>
|
||||
/// <response code="200"></response>
|
||||
[HttpGet("DownloadLanguage")]
|
||||
[ProducesResponseType<string>(Status200OK, "text/plain")]
|
||||
public IActionResult GetDownloadLanguage()
|
||||
{
|
||||
return Ok(Tranga.Settings.DownloadLanguage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the language in which Manga are downloaded
|
||||
/// </summary>
|
||||
/// <response code="200"></response>
|
||||
[HttpPatch("DownloadLanguage/{Language}")]
|
||||
[ProducesResponseType(Status200OK)]
|
||||
public IActionResult SetDownloadLanguage(string Language)
|
||||
{
|
||||
//TODO Validation
|
||||
Tranga.Settings.SetDownloadLanguage(Language);
|
||||
return Ok();
|
||||
}
|
||||
}
|
@ -11,7 +11,7 @@ namespace API.Controllers;
|
||||
[ApiVersion(2)]
|
||||
[ApiController]
|
||||
[Route("v{version:apiVersion}/[controller]")]
|
||||
public class WorkerController(ILog Log) : Controller
|
||||
public class WorkerController() : Controller
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns all <see cref="BaseWorker"/>
|
||||
@ -21,7 +21,7 @@ public class WorkerController(ILog Log) : Controller
|
||||
[ProducesResponseType<BaseWorker[]>(Status200OK, "application/json")]
|
||||
public IActionResult GetAllWorkers()
|
||||
{
|
||||
return Ok(Tranga.Workers.ToArray());
|
||||
return Ok(Tranga.AllWorkers.ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -33,7 +33,7 @@ public class WorkerController(ILog Log) : Controller
|
||||
[ProducesResponseType<BaseWorker[]>(Status200OK, "application/json")]
|
||||
public IActionResult GetJobs([FromBody]string[] WorkerIds)
|
||||
{
|
||||
return Ok(Tranga.Workers.Where(worker => WorkerIds.Contains(worker.Key)).ToArray());
|
||||
return Ok(Tranga.AllWorkers.Where(worker => WorkerIds.Contains(worker.Key)).ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -45,7 +45,7 @@ public class WorkerController(ILog Log) : Controller
|
||||
[ProducesResponseType<BaseWorker[]>(Status200OK, "application/json")]
|
||||
public IActionResult GetJobsInState(WorkerExecutionState State)
|
||||
{
|
||||
return Ok(Tranga.Workers.Where(worker => worker.State == State).ToArray());
|
||||
return Ok(Tranga.AllWorkers.Where(worker => worker.State == State).ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -59,7 +59,7 @@ public class WorkerController(ILog Log) : Controller
|
||||
[ProducesResponseType(Status404NotFound)]
|
||||
public IActionResult GetJob(string WorkerId)
|
||||
{
|
||||
if(Tranga.Workers.FirstOrDefault(w => w.Key == WorkerId) is not { } worker)
|
||||
if(Tranga.AllWorkers.FirstOrDefault(w => w.Key == WorkerId) is not { } worker)
|
||||
return NotFound(nameof(WorkerId));
|
||||
return Ok(worker);
|
||||
}
|
||||
@ -75,7 +75,7 @@ public class WorkerController(ILog Log) : Controller
|
||||
[ProducesResponseType(Status404NotFound)]
|
||||
public IActionResult DeleteJob(string WorkerId)
|
||||
{
|
||||
if(Tranga.Workers.FirstOrDefault(w => w.Key == WorkerId) is not { } worker)
|
||||
if(Tranga.AllWorkers.FirstOrDefault(w => w.Key == WorkerId) is not { } worker)
|
||||
return NotFound(nameof(WorkerId));
|
||||
Tranga.RemoveWorker(worker);
|
||||
return Ok();
|
||||
@ -97,7 +97,7 @@ public class WorkerController(ILog Log) : Controller
|
||||
[ProducesResponseType<string>(Status409Conflict, "text/plain")]
|
||||
public IActionResult ModifyJob(string WorkerId, [FromBody]ModifyWorkerRecord modifyWorkerRecord)
|
||||
{
|
||||
if(Tranga.Workers.FirstOrDefault(w => w.Key == WorkerId) is not { } worker)
|
||||
if(Tranga.AllWorkers.FirstOrDefault(w => w.Key == WorkerId) is not { } worker)
|
||||
return NotFound(nameof(WorkerId));
|
||||
|
||||
if(modifyWorkerRecord.IntervalMs is not null && worker is not IPeriodic)
|
||||
@ -121,7 +121,7 @@ public class WorkerController(ILog Log) : Controller
|
||||
[ProducesResponseType<string>(Status412PreconditionFailed, "text/plain")]
|
||||
public IActionResult StartJob(string WorkerId)
|
||||
{
|
||||
if(Tranga.Workers.FirstOrDefault(w => w.Key == WorkerId) is not { } worker)
|
||||
if(Tranga.AllWorkers.FirstOrDefault(w => w.Key == WorkerId) is not { } worker)
|
||||
return NotFound(nameof(WorkerId));
|
||||
|
||||
if (worker.State >= WorkerExecutionState.Waiting)
|
||||
@ -142,7 +142,7 @@ public class WorkerController(ILog Log) : Controller
|
||||
[ProducesResponseType(Status501NotImplemented)]
|
||||
public IActionResult StopJob(string WorkerId)
|
||||
{
|
||||
if(Tranga.Workers.FirstOrDefault(w => w.Key == WorkerId) is not { } worker)
|
||||
if(Tranga.AllWorkers.FirstOrDefault(w => w.Key == WorkerId) is not { } worker)
|
||||
return NotFound(nameof(WorkerId));
|
||||
|
||||
if(worker.State is < WorkerExecutionState.Running or >= WorkerExecutionState.Completed)
|
||||
|
@ -16,14 +16,14 @@ public abstract class DownloadClient
|
||||
public RequestResult MakeRequest(string url, RequestType requestType, string? referrer = null, string? clickButton = null)
|
||||
{
|
||||
Log.Debug($"Requesting {requestType} {url}");
|
||||
if (!TrangaSettings.requestLimits.ContainsKey(requestType))
|
||||
if (!Tranga.Settings.RequestLimits.ContainsKey(requestType))
|
||||
{
|
||||
return new RequestResult(HttpStatusCode.NotAcceptable, null, Stream.Null);
|
||||
}
|
||||
|
||||
int rateLimit = TrangaSettings.userAgent == TrangaSettings.DefaultUserAgent
|
||||
int rateLimit = Tranga.Settings.UserAgent == TrangaSettings.DefaultUserAgent
|
||||
? TrangaSettings.DefaultRequestLimits[requestType]
|
||||
: TrangaSettings.requestLimits[requestType];
|
||||
: Tranga.Settings.RequestLimits[requestType];
|
||||
|
||||
TimeSpan timeBetweenRequests = TimeSpan.FromMinutes(1).Divide(rateLimit);
|
||||
DateTime now = DateTime.Now;
|
||||
|
@ -18,13 +18,13 @@ public class FlareSolverrDownloadClient : DownloadClient
|
||||
Log.Warn("Client can not click button");
|
||||
if(referrer is not null)
|
||||
Log.Warn("Client can not set referrer");
|
||||
if (TrangaSettings.flareSolverrUrl == string.Empty)
|
||||
if (Tranga.Settings.FlareSolverrUrl == string.Empty)
|
||||
{
|
||||
Log.Error("FlareSolverr URL is empty");
|
||||
return new(HttpStatusCode.InternalServerError, null, Stream.Null);
|
||||
}
|
||||
|
||||
Uri flareSolverrUri = new (TrangaSettings.flareSolverrUrl);
|
||||
Uri flareSolverrUri = new (Tranga.Settings.FlareSolverrUrl);
|
||||
if (flareSolverrUri.Segments.Last() != "v1")
|
||||
flareSolverrUri = new UriBuilder(flareSolverrUri)
|
||||
{
|
||||
@ -35,7 +35,7 @@ public class FlareSolverrDownloadClient : DownloadClient
|
||||
{
|
||||
Timeout = TimeSpan.FromSeconds(10),
|
||||
DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrHigher,
|
||||
DefaultRequestHeaders = { { "User-Agent", TrangaSettings.userAgent } }
|
||||
DefaultRequestHeaders = { { "User-Agent", Tranga.Settings.UserAgent } }
|
||||
};
|
||||
|
||||
JObject requestObj = new()
|
||||
|
@ -12,7 +12,7 @@ internal class HttpDownloadClient : DownloadClient
|
||||
HttpClient client = new();
|
||||
client.Timeout = TimeSpan.FromSeconds(10);
|
||||
client.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrHigher;
|
||||
client.DefaultRequestHeaders.Add("User-Agent", TrangaSettings.userAgent);
|
||||
client.DefaultRequestHeaders.Add("User-Agent", Tranga.Settings.UserAgent);
|
||||
HttpResponseMessage? response;
|
||||
Uri uri = new(url);
|
||||
HttpRequestMessage requestMessage = new(HttpMethod.Get, uri);
|
||||
|
70
API/Migrations/Library/20250703191925_Initial.Designer.cs
generated
Normal file
70
API/Migrations/Library/20250703191925_Initial.Designer.cs
generated
Normal file
@ -0,0 +1,70 @@
|
||||
// <auto-generated />
|
||||
using API.Schema.LibraryContext;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace API.Migrations.Library
|
||||
{
|
||||
[DbContext(typeof(LibraryContext))]
|
||||
[Migration("20250703191925_Initial")]
|
||||
partial class Initial
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "9.0.5")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("API.Schema.LibraryContext.LibraryConnectors.LibraryConnector", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Auth")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("BaseUrl")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<byte>("LibraryType")
|
||||
.HasColumnType("smallint");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.ToTable("LibraryConnectors");
|
||||
|
||||
b.HasDiscriminator<byte>("LibraryType");
|
||||
|
||||
b.UseTphMappingStrategy();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.LibraryContext.LibraryConnectors.Kavita", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.LibraryContext.LibraryConnectors.LibraryConnector");
|
||||
|
||||
b.HasDiscriminator().HasValue((byte)1);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.LibraryContext.LibraryConnectors.Komga", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.LibraryContext.LibraryConnectors.LibraryConnector");
|
||||
|
||||
b.HasDiscriminator().HasValue((byte)0);
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
35
API/Migrations/Library/20250703191925_Initial.cs
Normal file
35
API/Migrations/Library/20250703191925_Initial.cs
Normal file
@ -0,0 +1,35 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace API.Migrations.Library
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Initial : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "LibraryConnectors",
|
||||
columns: table => new
|
||||
{
|
||||
Key = table.Column<string>(type: "text", nullable: false),
|
||||
LibraryType = table.Column<byte>(type: "smallint", nullable: false),
|
||||
BaseUrl = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||
Auth = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_LibraryConnectors", x => x.Key);
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "LibraryConnectors");
|
||||
}
|
||||
}
|
||||
}
|
67
API/Migrations/Library/LibraryContextModelSnapshot.cs
Normal file
67
API/Migrations/Library/LibraryContextModelSnapshot.cs
Normal file
@ -0,0 +1,67 @@
|
||||
// <auto-generated />
|
||||
using API.Schema.LibraryContext;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace API.Migrations.Library
|
||||
{
|
||||
[DbContext(typeof(LibraryContext))]
|
||||
partial class LibraryContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "9.0.5")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("API.Schema.LibraryContext.LibraryConnectors.LibraryConnector", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Auth")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("BaseUrl")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<byte>("LibraryType")
|
||||
.HasColumnType("smallint");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.ToTable("LibraryConnectors");
|
||||
|
||||
b.HasDiscriminator<byte>("LibraryType");
|
||||
|
||||
b.UseTphMappingStrategy();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.LibraryContext.LibraryConnectors.Kavita", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.LibraryContext.LibraryConnectors.LibraryConnector");
|
||||
|
||||
b.HasDiscriminator().HasValue((byte)1);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.LibraryContext.LibraryConnectors.Komga", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.LibraryContext.LibraryConnectors.LibraryConnector");
|
||||
|
||||
b.HasDiscriminator().HasValue((byte)0);
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
547
API/Migrations/Manga/20250703192023_Initial.Designer.cs
generated
Normal file
547
API/Migrations/Manga/20250703192023_Initial.Designer.cs
generated
Normal file
@ -0,0 +1,547 @@
|
||||
// <auto-generated />
|
||||
using API.Schema.MangaContext;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace API.Migrations.Manga
|
||||
{
|
||||
[DbContext(typeof(MangaContext))]
|
||||
[Migration("20250703192023_Initial")]
|
||||
partial class Initial
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "9.0.5")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaContext.Author", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("AuthorName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.ToTable("Authors");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaContext.Chapter", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ChapterNumber")
|
||||
.IsRequired()
|
||||
.HasMaxLength(10)
|
||||
.HasColumnType("character varying(10)");
|
||||
|
||||
b.Property<bool>("Downloaded")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("FileName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("ParentMangaId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<int?>("VolumeNumber")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.HasIndex("ParentMangaId");
|
||||
|
||||
b.ToTable("Chapters");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaContext.FileLibrary", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("BasePath")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("LibraryName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.ToTable("FileLibraries");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaContext.Manga", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("CoverFileNameInCache")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)");
|
||||
|
||||
b.Property<string>("CoverUrl")
|
||||
.IsRequired()
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("DirectoryName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)");
|
||||
|
||||
b.Property<float>("IgnoreChaptersBefore")
|
||||
.HasColumnType("real");
|
||||
|
||||
b.Property<string>("LibraryId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)");
|
||||
|
||||
b.Property<string>("OriginalLanguage")
|
||||
.HasMaxLength(8)
|
||||
.HasColumnType("character varying(8)");
|
||||
|
||||
b.Property<byte>("ReleaseStatus")
|
||||
.HasColumnType("smallint");
|
||||
|
||||
b.Property<long?>("Year")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.HasIndex("LibraryId");
|
||||
|
||||
b.ToTable("Mangas");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaContext.MangaConnectorId<API.Schema.MangaContext.Chapter>", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("IdOnConnectorSite")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("MangaConnectorName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)");
|
||||
|
||||
b.Property<string>("ObjId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<bool>("UseForDownload")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("WebsiteUrl")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.HasIndex("MangaConnectorName");
|
||||
|
||||
b.HasIndex("ObjId");
|
||||
|
||||
b.ToTable("MangaConnectorToChapter");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaContext.MangaConnectorId<API.Schema.MangaContext.Manga>", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("IdOnConnectorSite")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("MangaConnectorName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)");
|
||||
|
||||
b.Property<string>("ObjId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<bool>("UseForDownload")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("WebsiteUrl")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.HasIndex("MangaConnectorName");
|
||||
|
||||
b.HasIndex("ObjId");
|
||||
|
||||
b.ToTable("MangaConnectorToManga");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaContext.MangaConnectors.MangaConnector", b =>
|
||||
{
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)");
|
||||
|
||||
b.PrimitiveCollection<string[]>("BaseUris")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("text[]");
|
||||
|
||||
b.Property<bool>("Enabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("IconUrl")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)");
|
||||
|
||||
b.PrimitiveCollection<string[]>("SupportedLanguages")
|
||||
.IsRequired()
|
||||
.HasMaxLength(8)
|
||||
.HasColumnType("text[]");
|
||||
|
||||
b.HasKey("Name");
|
||||
|
||||
b.ToTable("MangaConnectors");
|
||||
|
||||
b.HasDiscriminator<string>("Name").HasValue("MangaConnector");
|
||||
|
||||
b.UseTphMappingStrategy();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaContext.MangaTag", b =>
|
||||
{
|
||||
b.Property<string>("Tag")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasKey("Tag");
|
||||
|
||||
b.ToTable("Tags");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaContext.MetadataFetchers.MetadataEntry", b =>
|
||||
{
|
||||
b.Property<string>("MetadataFetcherName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Identifier")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("MangaId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("MetadataFetcherName", "Identifier");
|
||||
|
||||
b.HasIndex("MangaId");
|
||||
|
||||
b.ToTable("MetadataEntries");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaContext.MetadataFetchers.MetadataFetcher", b =>
|
||||
{
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("MetadataEntry")
|
||||
.IsRequired()
|
||||
.HasMaxLength(21)
|
||||
.HasColumnType("character varying(21)");
|
||||
|
||||
b.HasKey("Name");
|
||||
|
||||
b.ToTable("MetadataFetcher");
|
||||
|
||||
b.HasDiscriminator<string>("MetadataEntry").HasValue("MetadataFetcher");
|
||||
|
||||
b.UseTphMappingStrategy();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AuthorToManga", b =>
|
||||
{
|
||||
b.Property<string>("AuthorIds")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("MangaIds")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("AuthorIds", "MangaIds");
|
||||
|
||||
b.HasIndex("MangaIds");
|
||||
|
||||
b.ToTable("AuthorToManga");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MangaTagToManga", b =>
|
||||
{
|
||||
b.Property<string>("MangaTagIds")
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("MangaIds")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("MangaTagIds", "MangaIds");
|
||||
|
||||
b.HasIndex("MangaIds");
|
||||
|
||||
b.ToTable("MangaTagToManga");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaContext.MangaConnectors.ComickIo", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.MangaContext.MangaConnectors.MangaConnector");
|
||||
|
||||
b.HasDiscriminator().HasValue("ComickIo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaContext.MangaConnectors.Global", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.MangaContext.MangaConnectors.MangaConnector");
|
||||
|
||||
b.HasDiscriminator().HasValue("Global");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaContext.MangaConnectors.MangaDex", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.MangaContext.MangaConnectors.MangaConnector");
|
||||
|
||||
b.HasDiscriminator().HasValue("MangaDex");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaContext.MetadataFetchers.MyAnimeList", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.MangaContext.MetadataFetchers.MetadataFetcher");
|
||||
|
||||
b.HasDiscriminator().HasValue("MyAnimeList");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaContext.Chapter", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.MangaContext.Manga", "ParentManga")
|
||||
.WithMany("Chapters")
|
||||
.HasForeignKey("ParentMangaId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ParentManga");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaContext.Manga", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.MangaContext.FileLibrary", "Library")
|
||||
.WithMany()
|
||||
.HasForeignKey("LibraryId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.OwnsMany("API.Schema.MangaContext.AltTitle", "AltTitles", b1 =>
|
||||
{
|
||||
b1.Property<string>("Key")
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<string>("Language")
|
||||
.IsRequired()
|
||||
.HasMaxLength(8)
|
||||
.HasColumnType("character varying(8)");
|
||||
|
||||
b1.Property<string>("MangaKey")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b1.HasKey("Key");
|
||||
|
||||
b1.HasIndex("MangaKey");
|
||||
|
||||
b1.ToTable("AltTitle");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("MangaKey");
|
||||
});
|
||||
|
||||
b.OwnsMany("API.Schema.MangaContext.Link", "Links", b1 =>
|
||||
{
|
||||
b1.Property<string>("Key")
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<string>("LinkProvider")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b1.Property<string>("LinkUrl")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)");
|
||||
|
||||
b1.Property<string>("MangaKey")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.HasKey("Key");
|
||||
|
||||
b1.HasIndex("MangaKey");
|
||||
|
||||
b1.ToTable("Link");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("MangaKey");
|
||||
});
|
||||
|
||||
b.Navigation("AltTitles");
|
||||
|
||||
b.Navigation("Library");
|
||||
|
||||
b.Navigation("Links");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaContext.MangaConnectorId<API.Schema.MangaContext.Chapter>", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.MangaContext.MangaConnectors.MangaConnector", "MangaConnector")
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaConnectorName")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("API.Schema.MangaContext.Chapter", "Obj")
|
||||
.WithMany("MangaConnectorIds")
|
||||
.HasForeignKey("ObjId")
|
||||
.OnDelete(DeleteBehavior.NoAction)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("MangaConnector");
|
||||
|
||||
b.Navigation("Obj");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaContext.MangaConnectorId<API.Schema.MangaContext.Manga>", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.MangaContext.MangaConnectors.MangaConnector", "MangaConnector")
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaConnectorName")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("API.Schema.MangaContext.Manga", "Obj")
|
||||
.WithMany("MangaConnectorIds")
|
||||
.HasForeignKey("ObjId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("MangaConnector");
|
||||
|
||||
b.Navigation("Obj");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaContext.MetadataFetchers.MetadataEntry", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.MangaContext.Manga", "Manga")
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("API.Schema.MangaContext.MetadataFetchers.MetadataFetcher", "MetadataFetcher")
|
||||
.WithMany()
|
||||
.HasForeignKey("MetadataFetcherName")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Manga");
|
||||
|
||||
b.Navigation("MetadataFetcher");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AuthorToManga", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.MangaContext.Author", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("AuthorIds")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("API.Schema.MangaContext.Manga", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaIds")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MangaTagToManga", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.MangaContext.Manga", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaIds")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("API.Schema.MangaContext.MangaTag", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaTagIds")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaContext.Chapter", b =>
|
||||
{
|
||||
b.Navigation("MangaConnectorIds");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaContext.Manga", b =>
|
||||
{
|
||||
b.Navigation("Chapters");
|
||||
|
||||
b.Navigation("MangaConnectorIds");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
396
API/Migrations/Manga/20250703192023_Initial.cs
Normal file
396
API/Migrations/Manga/20250703192023_Initial.cs
Normal file
@ -0,0 +1,396 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace API.Migrations.Manga
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Initial : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Authors",
|
||||
columns: table => new
|
||||
{
|
||||
Key = table.Column<string>(type: "text", nullable: false),
|
||||
AuthorName = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Authors", x => x.Key);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "FileLibraries",
|
||||
columns: table => new
|
||||
{
|
||||
Key = table.Column<string>(type: "text", nullable: false),
|
||||
BasePath = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||
LibraryName = table.Column<string>(type: "character varying(512)", maxLength: 512, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_FileLibraries", x => x.Key);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "MangaConnectors",
|
||||
columns: table => new
|
||||
{
|
||||
Name = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
|
||||
SupportedLanguages = table.Column<string[]>(type: "text[]", maxLength: 8, nullable: false),
|
||||
IconUrl = table.Column<string>(type: "character varying(2048)", maxLength: 2048, nullable: false),
|
||||
BaseUris = table.Column<string[]>(type: "text[]", maxLength: 256, nullable: false),
|
||||
Enabled = table.Column<bool>(type: "boolean", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_MangaConnectors", x => x.Name);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "MetadataFetcher",
|
||||
columns: table => new
|
||||
{
|
||||
Name = table.Column<string>(type: "text", nullable: false),
|
||||
MetadataEntry = table.Column<string>(type: "character varying(21)", maxLength: 21, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_MetadataFetcher", x => x.Name);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Tags",
|
||||
columns: table => new
|
||||
{
|
||||
Tag = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Tags", x => x.Tag);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Mangas",
|
||||
columns: table => new
|
||||
{
|
||||
Key = table.Column<string>(type: "text", nullable: false),
|
||||
Name = table.Column<string>(type: "character varying(512)", maxLength: 512, nullable: false),
|
||||
Description = table.Column<string>(type: "text", nullable: false),
|
||||
CoverUrl = table.Column<string>(type: "character varying(512)", maxLength: 512, nullable: false),
|
||||
ReleaseStatus = table.Column<byte>(type: "smallint", nullable: false),
|
||||
LibraryId = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
|
||||
IgnoreChaptersBefore = table.Column<float>(type: "real", nullable: false),
|
||||
DirectoryName = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||
CoverFileNameInCache = table.Column<string>(type: "character varying(512)", maxLength: 512, nullable: true),
|
||||
Year = table.Column<long>(type: "bigint", nullable: true),
|
||||
OriginalLanguage = table.Column<string>(type: "character varying(8)", maxLength: 8, nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Mangas", x => x.Key);
|
||||
table.ForeignKey(
|
||||
name: "FK_Mangas_FileLibraries_LibraryId",
|
||||
column: x => x.LibraryId,
|
||||
principalTable: "FileLibraries",
|
||||
principalColumn: "Key",
|
||||
onDelete: ReferentialAction.SetNull);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "AltTitle",
|
||||
columns: table => new
|
||||
{
|
||||
Key = table.Column<string>(type: "text", nullable: false),
|
||||
Language = table.Column<string>(type: "character varying(8)", maxLength: 8, nullable: false),
|
||||
Title = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||
MangaKey = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_AltTitle", x => x.Key);
|
||||
table.ForeignKey(
|
||||
name: "FK_AltTitle_Mangas_MangaKey",
|
||||
column: x => x.MangaKey,
|
||||
principalTable: "Mangas",
|
||||
principalColumn: "Key",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "AuthorToManga",
|
||||
columns: table => new
|
||||
{
|
||||
AuthorIds = table.Column<string>(type: "text", nullable: false),
|
||||
MangaIds = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_AuthorToManga", x => new { x.AuthorIds, x.MangaIds });
|
||||
table.ForeignKey(
|
||||
name: "FK_AuthorToManga_Authors_AuthorIds",
|
||||
column: x => x.AuthorIds,
|
||||
principalTable: "Authors",
|
||||
principalColumn: "Key",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_AuthorToManga_Mangas_MangaIds",
|
||||
column: x => x.MangaIds,
|
||||
principalTable: "Mangas",
|
||||
principalColumn: "Key",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Chapters",
|
||||
columns: table => new
|
||||
{
|
||||
Key = table.Column<string>(type: "text", nullable: false),
|
||||
ParentMangaId = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
|
||||
VolumeNumber = table.Column<int>(type: "integer", nullable: true),
|
||||
ChapterNumber = table.Column<string>(type: "character varying(10)", maxLength: 10, nullable: false),
|
||||
Title = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
|
||||
FileName = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||
Downloaded = table.Column<bool>(type: "boolean", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Chapters", x => x.Key);
|
||||
table.ForeignKey(
|
||||
name: "FK_Chapters_Mangas_ParentMangaId",
|
||||
column: x => x.ParentMangaId,
|
||||
principalTable: "Mangas",
|
||||
principalColumn: "Key",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Link",
|
||||
columns: table => new
|
||||
{
|
||||
Key = table.Column<string>(type: "text", nullable: false),
|
||||
LinkProvider = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
|
||||
LinkUrl = table.Column<string>(type: "character varying(2048)", maxLength: 2048, nullable: false),
|
||||
MangaKey = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Link", x => x.Key);
|
||||
table.ForeignKey(
|
||||
name: "FK_Link_Mangas_MangaKey",
|
||||
column: x => x.MangaKey,
|
||||
principalTable: "Mangas",
|
||||
principalColumn: "Key",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "MangaConnectorToManga",
|
||||
columns: table => new
|
||||
{
|
||||
Key = table.Column<string>(type: "text", nullable: false),
|
||||
ObjId = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
|
||||
MangaConnectorName = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
|
||||
IdOnConnectorSite = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||
WebsiteUrl = table.Column<string>(type: "character varying(512)", maxLength: 512, nullable: true),
|
||||
UseForDownload = table.Column<bool>(type: "boolean", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_MangaConnectorToManga", x => x.Key);
|
||||
table.ForeignKey(
|
||||
name: "FK_MangaConnectorToManga_MangaConnectors_MangaConnectorName",
|
||||
column: x => x.MangaConnectorName,
|
||||
principalTable: "MangaConnectors",
|
||||
principalColumn: "Name",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_MangaConnectorToManga_Mangas_ObjId",
|
||||
column: x => x.ObjId,
|
||||
principalTable: "Mangas",
|
||||
principalColumn: "Key",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "MangaTagToManga",
|
||||
columns: table => new
|
||||
{
|
||||
MangaTagIds = table.Column<string>(type: "character varying(64)", nullable: false),
|
||||
MangaIds = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_MangaTagToManga", x => new { x.MangaTagIds, x.MangaIds });
|
||||
table.ForeignKey(
|
||||
name: "FK_MangaTagToManga_Mangas_MangaIds",
|
||||
column: x => x.MangaIds,
|
||||
principalTable: "Mangas",
|
||||
principalColumn: "Key",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_MangaTagToManga_Tags_MangaTagIds",
|
||||
column: x => x.MangaTagIds,
|
||||
principalTable: "Tags",
|
||||
principalColumn: "Tag",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "MetadataEntries",
|
||||
columns: table => new
|
||||
{
|
||||
MetadataFetcherName = table.Column<string>(type: "text", nullable: false),
|
||||
Identifier = table.Column<string>(type: "text", nullable: false),
|
||||
MangaId = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_MetadataEntries", x => new { x.MetadataFetcherName, x.Identifier });
|
||||
table.ForeignKey(
|
||||
name: "FK_MetadataEntries_Mangas_MangaId",
|
||||
column: x => x.MangaId,
|
||||
principalTable: "Mangas",
|
||||
principalColumn: "Key",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_MetadataEntries_MetadataFetcher_MetadataFetcherName",
|
||||
column: x => x.MetadataFetcherName,
|
||||
principalTable: "MetadataFetcher",
|
||||
principalColumn: "Name",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "MangaConnectorToChapter",
|
||||
columns: table => new
|
||||
{
|
||||
Key = table.Column<string>(type: "text", nullable: false),
|
||||
ObjId = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
|
||||
MangaConnectorName = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
|
||||
IdOnConnectorSite = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||
WebsiteUrl = table.Column<string>(type: "character varying(512)", maxLength: 512, nullable: true),
|
||||
UseForDownload = table.Column<bool>(type: "boolean", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_MangaConnectorToChapter", x => x.Key);
|
||||
table.ForeignKey(
|
||||
name: "FK_MangaConnectorToChapter_Chapters_ObjId",
|
||||
column: x => x.ObjId,
|
||||
principalTable: "Chapters",
|
||||
principalColumn: "Key");
|
||||
table.ForeignKey(
|
||||
name: "FK_MangaConnectorToChapter_MangaConnectors_MangaConnectorName",
|
||||
column: x => x.MangaConnectorName,
|
||||
principalTable: "MangaConnectors",
|
||||
principalColumn: "Name",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_AltTitle_MangaKey",
|
||||
table: "AltTitle",
|
||||
column: "MangaKey");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_AuthorToManga_MangaIds",
|
||||
table: "AuthorToManga",
|
||||
column: "MangaIds");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Chapters_ParentMangaId",
|
||||
table: "Chapters",
|
||||
column: "ParentMangaId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Link_MangaKey",
|
||||
table: "Link",
|
||||
column: "MangaKey");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_MangaConnectorToChapter_MangaConnectorName",
|
||||
table: "MangaConnectorToChapter",
|
||||
column: "MangaConnectorName");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_MangaConnectorToChapter_ObjId",
|
||||
table: "MangaConnectorToChapter",
|
||||
column: "ObjId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_MangaConnectorToManga_MangaConnectorName",
|
||||
table: "MangaConnectorToManga",
|
||||
column: "MangaConnectorName");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_MangaConnectorToManga_ObjId",
|
||||
table: "MangaConnectorToManga",
|
||||
column: "ObjId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Mangas_LibraryId",
|
||||
table: "Mangas",
|
||||
column: "LibraryId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_MangaTagToManga_MangaIds",
|
||||
table: "MangaTagToManga",
|
||||
column: "MangaIds");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_MetadataEntries_MangaId",
|
||||
table: "MetadataEntries",
|
||||
column: "MangaId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "AltTitle");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "AuthorToManga");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Link");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "MangaConnectorToChapter");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "MangaConnectorToManga");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "MangaTagToManga");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "MetadataEntries");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Authors");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Chapters");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "MangaConnectors");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Tags");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "MetadataFetcher");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Mangas");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "FileLibraries");
|
||||
}
|
||||
}
|
||||
}
|
544
API/Migrations/Manga/MangaContextModelSnapshot.cs
Normal file
544
API/Migrations/Manga/MangaContextModelSnapshot.cs
Normal file
@ -0,0 +1,544 @@
|
||||
// <auto-generated />
|
||||
using API.Schema.MangaContext;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace API.Migrations.Manga
|
||||
{
|
||||
[DbContext(typeof(MangaContext))]
|
||||
partial class MangaContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "9.0.5")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaContext.Author", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("AuthorName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.ToTable("Authors");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaContext.Chapter", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ChapterNumber")
|
||||
.IsRequired()
|
||||
.HasMaxLength(10)
|
||||
.HasColumnType("character varying(10)");
|
||||
|
||||
b.Property<bool>("Downloaded")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("FileName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("ParentMangaId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<int?>("VolumeNumber")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.HasIndex("ParentMangaId");
|
||||
|
||||
b.ToTable("Chapters");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaContext.FileLibrary", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("BasePath")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("LibraryName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.ToTable("FileLibraries");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaContext.Manga", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("CoverFileNameInCache")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)");
|
||||
|
||||
b.Property<string>("CoverUrl")
|
||||
.IsRequired()
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("DirectoryName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)");
|
||||
|
||||
b.Property<float>("IgnoreChaptersBefore")
|
||||
.HasColumnType("real");
|
||||
|
||||
b.Property<string>("LibraryId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)");
|
||||
|
||||
b.Property<string>("OriginalLanguage")
|
||||
.HasMaxLength(8)
|
||||
.HasColumnType("character varying(8)");
|
||||
|
||||
b.Property<byte>("ReleaseStatus")
|
||||
.HasColumnType("smallint");
|
||||
|
||||
b.Property<long?>("Year")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.HasIndex("LibraryId");
|
||||
|
||||
b.ToTable("Mangas");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaContext.MangaConnectorId<API.Schema.MangaContext.Chapter>", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("IdOnConnectorSite")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("MangaConnectorName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)");
|
||||
|
||||
b.Property<string>("ObjId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<bool>("UseForDownload")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("WebsiteUrl")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.HasIndex("MangaConnectorName");
|
||||
|
||||
b.HasIndex("ObjId");
|
||||
|
||||
b.ToTable("MangaConnectorToChapter");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaContext.MangaConnectorId<API.Schema.MangaContext.Manga>", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("IdOnConnectorSite")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("MangaConnectorName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)");
|
||||
|
||||
b.Property<string>("ObjId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<bool>("UseForDownload")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("WebsiteUrl")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.HasIndex("MangaConnectorName");
|
||||
|
||||
b.HasIndex("ObjId");
|
||||
|
||||
b.ToTable("MangaConnectorToManga");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaContext.MangaConnectors.MangaConnector", b =>
|
||||
{
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)");
|
||||
|
||||
b.PrimitiveCollection<string[]>("BaseUris")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("text[]");
|
||||
|
||||
b.Property<bool>("Enabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("IconUrl")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)");
|
||||
|
||||
b.PrimitiveCollection<string[]>("SupportedLanguages")
|
||||
.IsRequired()
|
||||
.HasMaxLength(8)
|
||||
.HasColumnType("text[]");
|
||||
|
||||
b.HasKey("Name");
|
||||
|
||||
b.ToTable("MangaConnectors");
|
||||
|
||||
b.HasDiscriminator<string>("Name").HasValue("MangaConnector");
|
||||
|
||||
b.UseTphMappingStrategy();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaContext.MangaTag", b =>
|
||||
{
|
||||
b.Property<string>("Tag")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasKey("Tag");
|
||||
|
||||
b.ToTable("Tags");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaContext.MetadataFetchers.MetadataEntry", b =>
|
||||
{
|
||||
b.Property<string>("MetadataFetcherName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Identifier")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("MangaId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("MetadataFetcherName", "Identifier");
|
||||
|
||||
b.HasIndex("MangaId");
|
||||
|
||||
b.ToTable("MetadataEntries");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaContext.MetadataFetchers.MetadataFetcher", b =>
|
||||
{
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("MetadataEntry")
|
||||
.IsRequired()
|
||||
.HasMaxLength(21)
|
||||
.HasColumnType("character varying(21)");
|
||||
|
||||
b.HasKey("Name");
|
||||
|
||||
b.ToTable("MetadataFetcher");
|
||||
|
||||
b.HasDiscriminator<string>("MetadataEntry").HasValue("MetadataFetcher");
|
||||
|
||||
b.UseTphMappingStrategy();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AuthorToManga", b =>
|
||||
{
|
||||
b.Property<string>("AuthorIds")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("MangaIds")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("AuthorIds", "MangaIds");
|
||||
|
||||
b.HasIndex("MangaIds");
|
||||
|
||||
b.ToTable("AuthorToManga");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MangaTagToManga", b =>
|
||||
{
|
||||
b.Property<string>("MangaTagIds")
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("MangaIds")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("MangaTagIds", "MangaIds");
|
||||
|
||||
b.HasIndex("MangaIds");
|
||||
|
||||
b.ToTable("MangaTagToManga");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaContext.MangaConnectors.ComickIo", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.MangaContext.MangaConnectors.MangaConnector");
|
||||
|
||||
b.HasDiscriminator().HasValue("ComickIo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaContext.MangaConnectors.Global", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.MangaContext.MangaConnectors.MangaConnector");
|
||||
|
||||
b.HasDiscriminator().HasValue("Global");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaContext.MangaConnectors.MangaDex", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.MangaContext.MangaConnectors.MangaConnector");
|
||||
|
||||
b.HasDiscriminator().HasValue("MangaDex");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaContext.MetadataFetchers.MyAnimeList", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.MangaContext.MetadataFetchers.MetadataFetcher");
|
||||
|
||||
b.HasDiscriminator().HasValue("MyAnimeList");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaContext.Chapter", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.MangaContext.Manga", "ParentManga")
|
||||
.WithMany("Chapters")
|
||||
.HasForeignKey("ParentMangaId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ParentManga");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaContext.Manga", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.MangaContext.FileLibrary", "Library")
|
||||
.WithMany()
|
||||
.HasForeignKey("LibraryId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.OwnsMany("API.Schema.MangaContext.AltTitle", "AltTitles", b1 =>
|
||||
{
|
||||
b1.Property<string>("Key")
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<string>("Language")
|
||||
.IsRequired()
|
||||
.HasMaxLength(8)
|
||||
.HasColumnType("character varying(8)");
|
||||
|
||||
b1.Property<string>("MangaKey")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b1.HasKey("Key");
|
||||
|
||||
b1.HasIndex("MangaKey");
|
||||
|
||||
b1.ToTable("AltTitle");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("MangaKey");
|
||||
});
|
||||
|
||||
b.OwnsMany("API.Schema.MangaContext.Link", "Links", b1 =>
|
||||
{
|
||||
b1.Property<string>("Key")
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<string>("LinkProvider")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b1.Property<string>("LinkUrl")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)");
|
||||
|
||||
b1.Property<string>("MangaKey")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.HasKey("Key");
|
||||
|
||||
b1.HasIndex("MangaKey");
|
||||
|
||||
b1.ToTable("Link");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("MangaKey");
|
||||
});
|
||||
|
||||
b.Navigation("AltTitles");
|
||||
|
||||
b.Navigation("Library");
|
||||
|
||||
b.Navigation("Links");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaContext.MangaConnectorId<API.Schema.MangaContext.Chapter>", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.MangaContext.MangaConnectors.MangaConnector", "MangaConnector")
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaConnectorName")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("API.Schema.MangaContext.Chapter", "Obj")
|
||||
.WithMany("MangaConnectorIds")
|
||||
.HasForeignKey("ObjId")
|
||||
.OnDelete(DeleteBehavior.NoAction)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("MangaConnector");
|
||||
|
||||
b.Navigation("Obj");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaContext.MangaConnectorId<API.Schema.MangaContext.Manga>", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.MangaContext.MangaConnectors.MangaConnector", "MangaConnector")
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaConnectorName")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("API.Schema.MangaContext.Manga", "Obj")
|
||||
.WithMany("MangaConnectorIds")
|
||||
.HasForeignKey("ObjId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("MangaConnector");
|
||||
|
||||
b.Navigation("Obj");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaContext.MetadataFetchers.MetadataEntry", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.MangaContext.Manga", "Manga")
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("API.Schema.MangaContext.MetadataFetchers.MetadataFetcher", "MetadataFetcher")
|
||||
.WithMany()
|
||||
.HasForeignKey("MetadataFetcherName")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Manga");
|
||||
|
||||
b.Navigation("MetadataFetcher");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AuthorToManga", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.MangaContext.Author", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("AuthorIds")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("API.Schema.MangaContext.Manga", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaIds")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MangaTagToManga", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.MangaContext.Manga", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaIds")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("API.Schema.MangaContext.MangaTag", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaTagIds")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaContext.Chapter", b =>
|
||||
{
|
||||
b.Navigation("MangaConnectorIds");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaContext.Manga", b =>
|
||||
{
|
||||
b.Navigation("Chapters");
|
||||
|
||||
b.Navigation("MangaConnectorIds");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
91
API/Migrations/Notifications/20250703191820_Initial.Designer.cs
generated
Normal file
91
API/Migrations/Notifications/20250703191820_Initial.Designer.cs
generated
Normal file
@ -0,0 +1,91 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using API.Schema.NotificationsContext;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace API.Migrations.Notifications
|
||||
{
|
||||
[DbContext(typeof(NotificationsContext))]
|
||||
[Migration("20250703191820_Initial")]
|
||||
partial class Initial
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "9.0.5")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "hstore");
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("API.Schema.NotificationsContext.Notification", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<DateTime>("Date")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<bool>("IsSent")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("Message")
|
||||
.IsRequired()
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b.Property<byte>("Urgency")
|
||||
.HasColumnType("smallint");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.ToTable("Notifications");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.NotificationsContext.NotificationConnectors.NotificationConnector", b =>
|
||||
{
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("Body")
|
||||
.IsRequired()
|
||||
.HasMaxLength(4096)
|
||||
.HasColumnType("character varying(4096)");
|
||||
|
||||
b.Property<Dictionary<string, string>>("Headers")
|
||||
.IsRequired()
|
||||
.HasColumnType("hstore");
|
||||
|
||||
b.Property<string>("HttpMethod")
|
||||
.IsRequired()
|
||||
.HasMaxLength(8)
|
||||
.HasColumnType("character varying(8)");
|
||||
|
||||
b.Property<string>("Url")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)");
|
||||
|
||||
b.HasKey("Name");
|
||||
|
||||
b.ToTable("NotificationConnectors");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
60
API/Migrations/Notifications/20250703191820_Initial.cs
Normal file
60
API/Migrations/Notifications/20250703191820_Initial.cs
Normal file
@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace API.Migrations.Notifications
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Initial : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterDatabase()
|
||||
.Annotation("Npgsql:PostgresExtension:hstore", ",,");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "NotificationConnectors",
|
||||
columns: table => new
|
||||
{
|
||||
Name = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
|
||||
Url = table.Column<string>(type: "character varying(2048)", maxLength: 2048, nullable: false),
|
||||
Headers = table.Column<Dictionary<string, string>>(type: "hstore", nullable: false),
|
||||
HttpMethod = table.Column<string>(type: "character varying(8)", maxLength: 8, nullable: false),
|
||||
Body = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_NotificationConnectors", x => x.Name);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Notifications",
|
||||
columns: table => new
|
||||
{
|
||||
Key = table.Column<string>(type: "text", nullable: false),
|
||||
Urgency = table.Column<byte>(type: "smallint", nullable: false),
|
||||
Title = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
|
||||
Message = table.Column<string>(type: "character varying(512)", maxLength: 512, nullable: false),
|
||||
Date = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
IsSent = table.Column<bool>(type: "boolean", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Notifications", x => x.Key);
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "NotificationConnectors");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Notifications");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using API.Schema.NotificationsContext;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace API.Migrations.Notifications
|
||||
{
|
||||
[DbContext(typeof(NotificationsContext))]
|
||||
partial class NotificationsContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "9.0.5")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "hstore");
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("API.Schema.NotificationsContext.Notification", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<DateTime>("Date")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<bool>("IsSent")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("Message")
|
||||
.IsRequired()
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b.Property<byte>("Urgency")
|
||||
.HasColumnType("smallint");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.ToTable("Notifications");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.NotificationsContext.NotificationConnectors.NotificationConnector", b =>
|
||||
{
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("Body")
|
||||
.IsRequired()
|
||||
.HasMaxLength(4096)
|
||||
.HasColumnType("character varying(4096)");
|
||||
|
||||
b.Property<Dictionary<string, string>>("Headers")
|
||||
.IsRequired()
|
||||
.HasColumnType("hstore");
|
||||
|
||||
b.Property<string>("HttpMethod")
|
||||
.IsRequired()
|
||||
.HasMaxLength(8)
|
||||
.HasColumnType("character varying(8)");
|
||||
|
||||
b.Property<string>("Url")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)");
|
||||
|
||||
b.HasKey("Name");
|
||||
|
||||
b.ToTable("NotificationConnectors");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
@ -97,8 +97,7 @@ app.MapControllers()
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI(options =>
|
||||
{
|
||||
options.SwaggerEndpoint(
|
||||
$"/swagger/v2/swagger.json", "v2");
|
||||
options.SwaggerEndpoint($"/swagger/v2/swagger.json", "v2");
|
||||
});
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
@ -119,7 +118,7 @@ using (IServiceScope scope = app.Services.CreateScope())
|
||||
MangaConnector[] newConnectors = connectors.Where(c => !context.MangaConnectors.Contains(c)).ToArray();
|
||||
context.MangaConnectors.AddRange(newConnectors);
|
||||
if (!context.FileLibraries.Any())
|
||||
context.FileLibraries.Add(new FileLibrary(TrangaSettings.downloadLocation, "Default FileLibrary"));
|
||||
context.FileLibraries.Add(new FileLibrary(Tranga.Settings.DownloadLocation, "Default FileLibrary"));
|
||||
|
||||
context.Sync();
|
||||
}
|
||||
@ -128,7 +127,8 @@ using (IServiceScope scope = app.Services.CreateScope())
|
||||
{
|
||||
NotificationsContext context = scope.ServiceProvider.GetRequiredService<NotificationsContext>();
|
||||
context.Database.Migrate();
|
||||
|
||||
|
||||
context.Notifications.RemoveRange(context.Notifications);
|
||||
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));
|
||||
|
||||
@ -143,7 +143,6 @@ using (IServiceScope scope = app.Services.CreateScope())
|
||||
context.Sync();
|
||||
}
|
||||
|
||||
TrangaSettings.Load();
|
||||
Tranga.StartLogger();
|
||||
|
||||
Tranga.PeriodicWorkerStarterThread.Start(app.Services);
|
||||
|
@ -6,7 +6,7 @@ using Newtonsoft.Json;
|
||||
|
||||
namespace API.Schema.LibraryContext.LibraryConnectors;
|
||||
|
||||
[PrimaryKey("LibraryConnectorId")]
|
||||
[PrimaryKey("Key")]
|
||||
public abstract class LibraryConnector : Identifiable
|
||||
{
|
||||
[Required]
|
||||
|
@ -108,7 +108,7 @@ public class Chapter : Identifiable, IComparable<Chapter>
|
||||
private static readonly Regex ReplaceRexx = new(@"%([a-zA-Z])|(.+?)");
|
||||
private string GetArchiveFilePath()
|
||||
{
|
||||
string archiveNamingScheme = TrangaSettings.chapterNamingScheme;
|
||||
string archiveNamingScheme = Tranga.Settings.ChapterNamingScheme;
|
||||
StringBuilder stringBuilder = new();
|
||||
foreach (Match nullable in NullableRex.Matches(archiveNamingScheme))
|
||||
{
|
||||
|
@ -38,22 +38,24 @@ public class MangaConnectorId<T> : Identifiable where T : Identifiable
|
||||
|
||||
[StringLength(256)] [Required] public string IdOnConnectorSite { get; init; }
|
||||
[Url] [StringLength(512)] public string? WebsiteUrl { get; internal init; }
|
||||
public bool UseForDownload { get; internal set; }
|
||||
|
||||
private readonly ILazyLoader _lazyLoader = null!;
|
||||
|
||||
public MangaConnectorId(T obj, MangaConnector mangaConnector, string idOnConnectorSite, string? websiteUrl)
|
||||
public MangaConnectorId(T obj, MangaConnector mangaConnector, string idOnConnectorSite, string? websiteUrl, bool useForDownload = false)
|
||||
: base(TokenGen.CreateToken(typeof(MangaConnectorId<T>), mangaConnector.Name, idOnConnectorSite))
|
||||
{
|
||||
this.Obj = obj;
|
||||
this.MangaConnector = mangaConnector;
|
||||
this.IdOnConnectorSite = idOnConnectorSite;
|
||||
this.WebsiteUrl = websiteUrl;
|
||||
this.UseForDownload = useForDownload;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EF CORE ONLY!!!
|
||||
/// </summary>
|
||||
public MangaConnectorId(ILazyLoader lazyLoader, string key, string objId, string mangaConnectorName, string idOnConnectorSite, string? websiteUrl)
|
||||
public MangaConnectorId(ILazyLoader lazyLoader, string key, string objId, string mangaConnectorName, string idOnConnectorSite, bool useForDownload, string? websiteUrl)
|
||||
: base(key)
|
||||
{
|
||||
this._lazyLoader = lazyLoader;
|
||||
@ -61,6 +63,7 @@ public class MangaConnectorId<T> : Identifiable where T : Identifiable
|
||||
this.MangaConnectorName = mangaConnectorName;
|
||||
this.IdOnConnectorSite = idOnConnectorSite;
|
||||
this.WebsiteUrl = websiteUrl;
|
||||
this.UseForDownload = useForDownload;
|
||||
}
|
||||
|
||||
public override string ToString() => $"{base.ToString()} {_obj}";
|
||||
|
@ -10,8 +10,5 @@ public class MangaTag(string tag)
|
||||
[Required]
|
||||
public string Tag { get; init; } = tag;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Tag}";
|
||||
}
|
||||
public override string ToString() => Tag;
|
||||
}
|
@ -3,7 +3,7 @@ using Newtonsoft.Json;
|
||||
|
||||
namespace API.Schema.MangaContext.MetadataFetchers;
|
||||
|
||||
[PrimaryKey("Name", "Identifier")]
|
||||
[PrimaryKey("MetadataFetcherName", "Identifier")]
|
||||
public class MetadataEntry
|
||||
{
|
||||
[JsonIgnore]
|
||||
|
@ -67,7 +67,7 @@ public class MyAnimeList : MetadataFetcher
|
||||
dbManga.Authors.Clear();
|
||||
dbManga.Authors = resultData.Authors.Select(a => new Author(a.Name)).ToList();
|
||||
|
||||
dbContext.SaveChanges();
|
||||
dbContext.Sync();
|
||||
}
|
||||
catch (DbUpdateException e)
|
||||
{
|
||||
|
@ -19,6 +19,8 @@ public class Notification : Identifiable
|
||||
|
||||
[Required]
|
||||
public DateTime Date { get; init; }
|
||||
|
||||
public bool IsSent { get; internal set; }
|
||||
|
||||
public Notification(string title, string message = "", NotificationUrgency urgency = NotificationUrgency.Normal, DateTime? date = null)
|
||||
: base(TokenGen.CreateToken("Notification"))
|
||||
@ -27,21 +29,23 @@ public class Notification : Identifiable
|
||||
this.Message = message;
|
||||
this.Urgency = urgency;
|
||||
this.Date = date ?? DateTime.UtcNow;
|
||||
this.IsSent = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EF ONLY!!!
|
||||
/// </summary>
|
||||
public Notification(string key, string title, string message, NotificationUrgency urgency, DateTime date)
|
||||
public Notification(string key, string title, string message, NotificationUrgency urgency, DateTime date, bool isSent)
|
||||
: base(key)
|
||||
{
|
||||
this.Title = title;
|
||||
this.Message = message;
|
||||
this.Urgency = urgency;
|
||||
this.Date = date;
|
||||
this.IsSent = isSent;
|
||||
}
|
||||
|
||||
public override string ToString() => $"{base.ToString()} {Urgency} {Title}";
|
||||
public override string ToString() => $"{base.ToString()} {Urgency} {Title} {Message}";
|
||||
}
|
||||
|
||||
public enum NotificationUrgency : byte
|
||||
|
@ -34,7 +34,7 @@ public class NotificationConnector(string name, string url, Dictionary<string, s
|
||||
[NotMapped]
|
||||
private readonly HttpClient Client = new()
|
||||
{
|
||||
DefaultRequestHeaders = { { "User-Agent", TrangaSettings.userAgent } }
|
||||
DefaultRequestHeaders = { { "User-Agent", Tranga.Settings.UserAgent } }
|
||||
};
|
||||
|
||||
[JsonIgnore]
|
||||
@ -79,4 +79,6 @@ public class NotificationConnector(string name, string url, Dictionary<string, s
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString() => $"{GetType().Name} {Name}";
|
||||
}
|
@ -22,11 +22,11 @@ public abstract class TrangaBaseContext<T> : DbContext where T : DbContext
|
||||
}, Array.Empty<string>(), LogLevel.Warning, DbContextLoggerOptions.Level | DbContextLoggerOptions.Category | DbContextLoggerOptions.UtcTime);
|
||||
}
|
||||
|
||||
internal async Task<(bool success, string? exceptionMessage)> Sync()
|
||||
internal (bool success, string? exceptionMessage) Sync()
|
||||
{
|
||||
try
|
||||
{
|
||||
await this.SaveChangesAsync();
|
||||
this.SaveChanges();
|
||||
return (true, null);
|
||||
}
|
||||
catch (Exception e)
|
||||
@ -35,4 +35,6 @@ public abstract class TrangaBaseContext<T> : DbContext where T : DbContext
|
||||
return (false, e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString() => $"{GetType().Name} {typeof(T).Name}";
|
||||
}
|
142
API/Tranga.cs
142
API/Tranga.cs
@ -1,8 +1,12 @@
|
||||
using API.Schema.MangaContext.MetadataFetchers;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using API.Schema.LibraryContext;
|
||||
using API.Schema.MangaContext;
|
||||
using API.Schema.MangaContext.MetadataFetchers;
|
||||
using API.Schema.NotificationsContext;
|
||||
using API.Workers;
|
||||
using API.Workers.MaintenanceWorkers;
|
||||
using log4net;
|
||||
using log4net.Config;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace API;
|
||||
|
||||
@ -21,16 +25,33 @@ public static class Tranga
|
||||
public static Thread PeriodicWorkerStarterThread { get; } = new (WorkerStarter);
|
||||
private static readonly ILog Log = LogManager.GetLogger(typeof(Tranga));
|
||||
internal static readonly MetadataFetcher[] MetadataFetchers = [new MyAnimeList()];
|
||||
internal static TrangaSettings Settings = TrangaSettings.Load();
|
||||
|
||||
internal static readonly UpdateMetadataWorker UpdateMetadataWorker = new ();
|
||||
internal static readonly SendNotificationsWorker SendNotificationsWorker = new();
|
||||
internal static readonly UpdateChaptersDownloadedWorker UpdateChaptersDownloadedWorker = new();
|
||||
internal static readonly CheckForNewChaptersWorker CheckForNewChaptersWorker = new();
|
||||
internal static readonly CleanupMangaCoversWorker CleanupMangaCoversWorker = new();
|
||||
internal static readonly StartNewChapterDownloadsWorker StartNewChapterDownloadsWorker = new();
|
||||
internal static readonly RemoveOldNotificationsWorker RemoveOldNotificationsWorker = new();
|
||||
|
||||
internal static void StartLogger()
|
||||
{
|
||||
BasicConfigurator.Configure();
|
||||
Log.Info("Logger Configured.");
|
||||
Log.Info(TRANGA);
|
||||
|
||||
AddWorker(UpdateMetadataWorker);
|
||||
AddWorker(SendNotificationsWorker);
|
||||
AddWorker(UpdateChaptersDownloadedWorker);
|
||||
AddWorker(CheckForNewChaptersWorker);
|
||||
AddWorker(CleanupMangaCoversWorker);
|
||||
AddWorker(StartNewChapterDownloadsWorker);
|
||||
AddWorker(RemoveOldNotificationsWorker);
|
||||
}
|
||||
|
||||
internal static HashSet<BaseWorker> Workers { get; private set; } = new ();
|
||||
public static void AddWorker(BaseWorker worker) => Workers.Add(worker);
|
||||
internal static HashSet<BaseWorker> AllWorkers { get; private set; } = new ();
|
||||
public static void AddWorker(BaseWorker worker) => AllWorkers.Add(worker);
|
||||
public static void AddWorkers(IEnumerable<BaseWorker> workers)
|
||||
{
|
||||
foreach (BaseWorker baseWorker in workers)
|
||||
@ -39,21 +60,14 @@ public static class Tranga
|
||||
}
|
||||
}
|
||||
|
||||
internal static void StopWorker(BaseWorker worker) => RemoveWorker(worker);
|
||||
|
||||
public static void RemoveWorker(BaseWorker removeWorker)
|
||||
{
|
||||
IEnumerable<BaseWorker> baseWorkers = Workers.Where(w => w.DependenciesAndSelf.Any(worker => worker == removeWorker));
|
||||
IEnumerable<BaseWorker> baseWorkers = AllWorkers.Where(w => w.DependenciesAndSelf.Any(worker => worker == removeWorker));
|
||||
|
||||
foreach (BaseWorker worker in baseWorkers)
|
||||
{
|
||||
worker.Cancel();
|
||||
Workers.Remove(worker);
|
||||
if (RunningWorkers.ContainsKey(worker))
|
||||
{
|
||||
worker.Cancel();
|
||||
RunningWorkers.Remove(worker);
|
||||
}
|
||||
StopWorker(worker);
|
||||
AllWorkers.Remove(worker);
|
||||
}
|
||||
}
|
||||
|
||||
@ -73,30 +87,116 @@ public static class Tranga
|
||||
while (true)
|
||||
{
|
||||
CheckRunningWorkers();
|
||||
|
||||
foreach (BaseWorker baseWorker in AllWorkers.DueWorkers())
|
||||
StartWorkers.Add(baseWorker);
|
||||
|
||||
foreach (BaseWorker worker in StartWorkers)
|
||||
foreach (BaseWorker worker in StartWorkers.ToArray())
|
||||
{
|
||||
if (worker is BaseWorkerWithContext<DbContext> scopedWorker)
|
||||
scopedWorker.SetScope(serviceProvider.CreateScope());
|
||||
RunningWorkers.Add(worker, worker.DoWork());
|
||||
if(RunningWorkers.ContainsKey(worker))
|
||||
continue;
|
||||
if (worker is BaseWorkerWithContext<MangaContext> mangaContextWorker)
|
||||
{
|
||||
mangaContextWorker.SetScope(serviceProvider.CreateScope());
|
||||
RunningWorkers.Add(mangaContextWorker, mangaContextWorker.DoWork());
|
||||
}else if (worker is BaseWorkerWithContext<NotificationsContext> notificationContextWorker)
|
||||
{
|
||||
notificationContextWorker.SetScope(serviceProvider.CreateScope());
|
||||
RunningWorkers.Add(notificationContextWorker, notificationContextWorker.DoWork());
|
||||
}else if (worker is BaseWorkerWithContext<LibraryContext> libraryContextWorker)
|
||||
{
|
||||
libraryContextWorker.SetScope(serviceProvider.CreateScope());
|
||||
RunningWorkers.Add(libraryContextWorker, libraryContextWorker.DoWork());
|
||||
}else
|
||||
RunningWorkers.Add(worker, worker.DoWork());
|
||||
|
||||
StartWorkers.Remove(worker);
|
||||
}
|
||||
Thread.Sleep(TrangaSettings.workCycleTimeout);
|
||||
Thread.Sleep(Settings.WorkCycleTimeoutMs);
|
||||
}
|
||||
}
|
||||
|
||||
private static void CheckRunningWorkers()
|
||||
{
|
||||
KeyValuePair<BaseWorker, Task<BaseWorker[]>>[] done = RunningWorkers.Where(kv => kv.Value.IsCompleted).ToArray();
|
||||
if (done.Length < 1)
|
||||
return;
|
||||
Log.Info($"Done: {done.Length}");
|
||||
Log.Debug(string.Join("\n", done.Select(d => d.ToString())));
|
||||
Log.Debug(string.Join("\n", done.Select(d => d.Key.ToString())));
|
||||
foreach ((BaseWorker worker, Task<BaseWorker[]> task) in done)
|
||||
{
|
||||
RunningWorkers.Remove(worker);
|
||||
foreach (BaseWorker newWorker in task.Result)
|
||||
StartWorkers.Add(newWorker);
|
||||
AllWorkers.Add(newWorker);
|
||||
task.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<BaseWorker> DueWorkers(this IEnumerable<BaseWorker> workers)
|
||||
{
|
||||
return workers.Where(w =>
|
||||
{
|
||||
if (w.State is >= WorkerExecutionState.Running and < WorkerExecutionState.Completed)
|
||||
return false;
|
||||
if (w is IPeriodic periodicWorker)
|
||||
return periodicWorker.IsDue;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
internal static void MarkWorkerForStart(BaseWorker worker) => StartWorkers.Add(worker);
|
||||
|
||||
internal static void StopWorker(BaseWorker worker)
|
||||
{
|
||||
StartWorkers.Remove(worker);
|
||||
worker.Cancel();
|
||||
RunningWorkers.Remove(worker);
|
||||
}
|
||||
|
||||
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 addManga, MangaConnectorId<Manga> addMcId, MangaContext context, [NotNullWhen(true)]out Manga? manga)
|
||||
{
|
||||
manga = context.Mangas.Find(addManga.Key) ?? addManga;
|
||||
MangaConnectorId<Manga> mcId = context.MangaConnectorToManga.Find(addMcId.Key) ?? addMcId;
|
||||
mcId.Obj = manga;
|
||||
|
||||
IEnumerable<MangaTag> mergedTags = manga.MangaTags.Select(mt =>
|
||||
{
|
||||
MangaTag? inDb = context.Tags.Find(mt.Tag);
|
||||
return inDb ?? mt;
|
||||
});
|
||||
manga.MangaTags = mergedTags.ToList();
|
||||
|
||||
IEnumerable<Author> mergedAuthors = manga.Authors.Select(ma =>
|
||||
{
|
||||
Author? inDb = context.Authors.Find(ma.Key);
|
||||
return inDb ?? ma;
|
||||
});
|
||||
manga.Authors = mergedAuthors.ToList();
|
||||
|
||||
if(context.MangaConnectorToManga.Find(addMcId.Key) is null)
|
||||
context.MangaConnectorToManga.Add(mcId);
|
||||
|
||||
if (context.Sync() is { success: false })
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static bool AddChapterToContext((Chapter, MangaConnectorId<Chapter>) addChapter, MangaContext context,
|
||||
[NotNullWhen(true)] out Chapter? chapter) => AddChapterToContext(addChapter.Item1, addChapter.Item2, context, out chapter);
|
||||
|
||||
internal static bool AddChapterToContext(Chapter addChapter, MangaConnectorId<Chapter> addChId, MangaContext context, [NotNullWhen(true)] out Chapter? chapter)
|
||||
{
|
||||
chapter = context.Chapters.Find(addChapter.Key) ?? addChapter;
|
||||
MangaConnectorId<Chapter> chId = context.MangaConnectorToChapter.Find(addChId.Key) ?? addChId;
|
||||
chId.Obj = chapter;
|
||||
|
||||
if(context.MangaConnectorToChapter.Find(chId.Key) is null)
|
||||
context.MangaConnectorToChapter.Add(chId);
|
||||
|
||||
if (context.Sync() is { success: false })
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
}
|
@ -1,21 +1,25 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using API.MangaDownloadClients;
|
||||
using API.Schema.NotificationsContext;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace API;
|
||||
|
||||
public static class TrangaSettings
|
||||
public struct TrangaSettings()
|
||||
{
|
||||
public static string downloadLocation { get; private set; } = (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "/Obj" : Path.Join(Directory.GetCurrentDirectory(), "Downloads"));
|
||||
public static string workingDirectory { get; private set; } = Path.Join(RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "/usr/share" : Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "tranga-api");
|
||||
|
||||
[JsonIgnore]
|
||||
public static string workingDirectory => Path.Join(RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "/usr/share" : Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "tranga-api");
|
||||
[JsonIgnore]
|
||||
public static string settingsFilePath => Path.Join(workingDirectory, "settings.json");
|
||||
[JsonIgnore]
|
||||
public static string coverImageCache => Path.Join(workingDirectory, "imageCache");
|
||||
public string DownloadLocation => RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "/Manga" : Path.Join(Directory.GetCurrentDirectory(), "Manga");
|
||||
[JsonIgnore]
|
||||
internal static readonly string DefaultUserAgent = $"Tranga/2.0 ({Enum.GetName(Environment.OSVersion.Platform)}; {(Environment.Is64BitOperatingSystem ? "x64" : "")})";
|
||||
public static string userAgent { get; private set; } = DefaultUserAgent;
|
||||
public static int compression{ get; private set; } = 40;
|
||||
public static bool bwImages { get; private set; } = false;
|
||||
public static string flareSolverrUrl { get; private set; } = string.Empty;
|
||||
public string UserAgent { get; set; } = DefaultUserAgent;
|
||||
public int ImageCompression{ get; set; } = 40;
|
||||
public bool BlackWhiteImages { get; set; } = false;
|
||||
public string FlareSolverrUrl { get; set; } = string.Empty;
|
||||
/// <summary>
|
||||
/// Placeholders:
|
||||
/// %M Obj Name
|
||||
@ -30,13 +34,8 @@ public static class TrangaSettings
|
||||
/// ?_(...) replace _ with a value from above:
|
||||
/// Everything inside the braces will only be added if the value of %_ is not null
|
||||
/// </summary>
|
||||
public static string chapterNamingScheme { get; private set; } = "%M - ?V(Vol.%V )Ch.%C?T( - %T)";
|
||||
[JsonIgnore]
|
||||
public static string settingsFilePath => Path.Join(workingDirectory, "settings.json");
|
||||
[JsonIgnore]
|
||||
public static string coverImageCache => Path.Join(workingDirectory, "imageCache");
|
||||
public static bool aprilFoolsMode { get; private set; } = true;
|
||||
public static int workCycleTimeout { get; private set; } = 20000;
|
||||
public string ChapterNamingScheme { get; set; } = "%M - ?V(Vol.%V )Ch.%C?T( - %T)";
|
||||
public int WorkCycleTimeoutMs { get; set; } = 20000;
|
||||
[JsonIgnore]
|
||||
internal static readonly Dictionary<RequestType, int> DefaultRequestLimits = new ()
|
||||
{
|
||||
@ -47,142 +46,67 @@ public static class TrangaSettings
|
||||
{RequestType.MangaCover, 60},
|
||||
{RequestType.Default, 60}
|
||||
};
|
||||
public static Dictionary<RequestType, int> requestLimits { get; private set; } = DefaultRequestLimits;
|
||||
public Dictionary<RequestType, int> RequestLimits { get; set; } = DefaultRequestLimits;
|
||||
|
||||
public static TimeSpan NotificationUrgencyDelay(NotificationUrgency urgency) => urgency switch
|
||||
public string DownloadLanguage { get; set; } = "en";
|
||||
|
||||
public static TrangaSettings Load()
|
||||
{
|
||||
NotificationUrgency.High => TimeSpan.Zero,
|
||||
NotificationUrgency.Normal => TimeSpan.FromMinutes(5),
|
||||
NotificationUrgency.Low => TimeSpan.FromMinutes(10),
|
||||
_ => TimeSpan.FromHours(1)
|
||||
}; //TODO make this a setting?
|
||||
|
||||
public static void Load()
|
||||
{
|
||||
if(File.Exists(settingsFilePath))
|
||||
Deserialize(File.ReadAllText(settingsFilePath));
|
||||
else return;
|
||||
|
||||
Directory.CreateDirectory(downloadLocation);
|
||||
ExportSettings();
|
||||
if (!File.Exists(settingsFilePath))
|
||||
new TrangaSettings().Save();
|
||||
return JsonConvert.DeserializeObject<TrangaSettings>(File.ReadAllText(settingsFilePath));
|
||||
}
|
||||
|
||||
public static void UpdateAprilFoolsMode(bool enabled)
|
||||
public void Save()
|
||||
{
|
||||
aprilFoolsMode = enabled;
|
||||
ExportSettings();
|
||||
File.WriteAllText(settingsFilePath, JsonConvert.SerializeObject(this));
|
||||
}
|
||||
|
||||
public static void UpdateCompressImages(int value)
|
||||
public void SetUserAgent(string value)
|
||||
{
|
||||
compression = int.Clamp(value, 1, 100);
|
||||
ExportSettings();
|
||||
this.UserAgent = value;
|
||||
Save();
|
||||
}
|
||||
|
||||
public static void UpdateBwImages(bool enabled)
|
||||
public void SetRequestLimit(RequestType type, int value)
|
||||
{
|
||||
bwImages = enabled;
|
||||
ExportSettings();
|
||||
this.RequestLimits[type] = value;
|
||||
Save();
|
||||
}
|
||||
|
||||
public static void UpdateUserAgent(string? customUserAgent)
|
||||
public void ResetRequestLimits()
|
||||
{
|
||||
userAgent = customUserAgent ?? DefaultUserAgent;
|
||||
ExportSettings();
|
||||
this.RequestLimits = DefaultRequestLimits;
|
||||
Save();
|
||||
}
|
||||
|
||||
public static void UpdateRequestLimit(RequestType requestType, int newLimit)
|
||||
public void UpdateImageCompression(int value)
|
||||
{
|
||||
requestLimits[requestType] = newLimit;
|
||||
ExportSettings();
|
||||
this.ImageCompression = value;
|
||||
Save();
|
||||
}
|
||||
|
||||
public static void UpdateChapterNamingScheme(string namingScheme)
|
||||
public void SetBlackWhiteImageEnabled(bool enabled)
|
||||
{
|
||||
chapterNamingScheme = namingScheme;
|
||||
ExportSettings();
|
||||
this.BlackWhiteImages = enabled;
|
||||
Save();
|
||||
}
|
||||
|
||||
public static void UpdateFlareSolverrUrl(string url)
|
||||
public void SetChapterNamingScheme(string scheme)
|
||||
{
|
||||
flareSolverrUrl = url;
|
||||
ExportSettings();
|
||||
this.ChapterNamingScheme = scheme;
|
||||
Save();
|
||||
}
|
||||
|
||||
public static void ResetRequestLimits()
|
||||
public void SetFlareSolverrUrl(string url)
|
||||
{
|
||||
requestLimits = DefaultRequestLimits;
|
||||
ExportSettings();
|
||||
this.FlareSolverrUrl = url;
|
||||
Save();
|
||||
}
|
||||
|
||||
public static void ExportSettings()
|
||||
public void SetDownloadLanguage(string language)
|
||||
{
|
||||
if (File.Exists(settingsFilePath))
|
||||
{
|
||||
while(IsFileInUse(settingsFilePath))
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
else
|
||||
Directory.CreateDirectory(new FileInfo(settingsFilePath).DirectoryName!);
|
||||
File.WriteAllText(settingsFilePath, Serialize());
|
||||
}
|
||||
|
||||
internal static bool IsFileInUse(string filePath)
|
||||
{
|
||||
if (!File.Exists(filePath))
|
||||
return false;
|
||||
try
|
||||
{
|
||||
using FileStream stream = new (filePath, FileMode.Open, FileAccess.Read, FileShare.None);
|
||||
stream.Close();
|
||||
return false;
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static JObject AsJObject()
|
||||
{
|
||||
JObject jobj = new ();
|
||||
jobj.Add("downloadLocation", JToken.FromObject(downloadLocation));
|
||||
jobj.Add("workingDirectory", JToken.FromObject(workingDirectory));
|
||||
jobj.Add("userAgent", JToken.FromObject(userAgent));
|
||||
jobj.Add("aprilFoolsMode", JToken.FromObject(aprilFoolsMode));
|
||||
jobj.Add("requestLimits", JToken.FromObject(requestLimits));
|
||||
jobj.Add("compression", JToken.FromObject(compression));
|
||||
jobj.Add("bwImages", JToken.FromObject(bwImages));
|
||||
jobj.Add("workCycleTimeout", JToken.FromObject(workCycleTimeout));
|
||||
jobj.Add("chapterNamingScheme", JToken.FromObject(chapterNamingScheme));
|
||||
jobj.Add("flareSolverrUrl", JToken.FromObject(flareSolverrUrl));
|
||||
return jobj;
|
||||
}
|
||||
|
||||
public static string Serialize() => AsJObject().ToString();
|
||||
|
||||
public static void Deserialize(string serialized)
|
||||
{
|
||||
JObject jobj = JObject.Parse(serialized);
|
||||
if (jobj.TryGetValue("downloadLocation", out JToken? dl))
|
||||
downloadLocation = dl.Value<string>()!;
|
||||
if (jobj.TryGetValue("workingDirectory", out JToken? wd))
|
||||
workingDirectory = wd.Value<string>()!;
|
||||
if (jobj.TryGetValue("userAgent", out JToken? ua))
|
||||
userAgent = ua.Value<string>()!;
|
||||
if (jobj.TryGetValue("aprilFoolsMode", out JToken? afm))
|
||||
aprilFoolsMode = afm.Value<bool>()!;
|
||||
if (jobj.TryGetValue("requestLimits", out JToken? rl))
|
||||
requestLimits = rl.ToObject<Dictionary<RequestType, int>>()!;
|
||||
if (jobj.TryGetValue("compression", out JToken? ci))
|
||||
compression = ci.Value<int>()!;
|
||||
if (jobj.TryGetValue("bwImages", out JToken? bwi))
|
||||
bwImages = bwi.Value<bool>()!;
|
||||
if (jobj.TryGetValue("workCycleTimeout", out JToken? snjt))
|
||||
workCycleTimeout = snjt.Value<int>()!;
|
||||
if (jobj.TryGetValue("chapterNamingScheme", out JToken? cns))
|
||||
chapterNamingScheme = cns.Value<string>()!;
|
||||
if (jobj.TryGetValue("flareSolverrUrl", out JToken? fsu))
|
||||
flareSolverrUrl = fsu.Value<string>()!;
|
||||
this.DownloadLanguage = language;
|
||||
Save();
|
||||
}
|
||||
}
|
@ -88,6 +88,8 @@ public abstract class BaseWorker : Identifiable
|
||||
DateTime endTime = DateTime.UtcNow;
|
||||
Log.Info($"Completed {this}\n\t{endTime.Subtract(startTime).TotalMilliseconds} ms");
|
||||
this.State = WorkerExecutionState.Completed;
|
||||
if(this is IPeriodic periodic)
|
||||
periodic.LastExecution = DateTime.UtcNow;
|
||||
});
|
||||
task.Start();
|
||||
this.State = WorkerExecutionState.Running;
|
||||
@ -101,7 +103,7 @@ public abstract class BaseWorker : Identifiable
|
||||
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())
|
||||
{
|
||||
Thread.Sleep(TrangaSettings.workCycleTimeout);
|
||||
Thread.Sleep(Tranga.Settings.WorkCycleTimeoutMs);
|
||||
}
|
||||
return [this];
|
||||
}
|
||||
|
@ -6,7 +6,13 @@ namespace API.Workers;
|
||||
public abstract class BaseWorkerWithContext<T>(IEnumerable<BaseWorker>? dependsOn = null) : BaseWorker(dependsOn) where T : DbContext
|
||||
{
|
||||
protected T DbContext = null!;
|
||||
public void SetScope(IServiceScope scope) => DbContext = scope.ServiceProvider.GetRequiredService<T>();
|
||||
private IServiceScope? _scope;
|
||||
|
||||
public void SetScope(IServiceScope scope)
|
||||
{
|
||||
this._scope = scope;
|
||||
this.DbContext = scope.ServiceProvider.GetRequiredService<T>();
|
||||
}
|
||||
|
||||
/// <exception cref="ConfigurationErrorsException">Scope has not been set. <see cref="SetScope"/></exception>
|
||||
public new Task<BaseWorker[]> DoWork()
|
||||
|
@ -2,7 +2,7 @@ namespace API.Workers;
|
||||
|
||||
public interface IPeriodic
|
||||
{
|
||||
protected DateTime LastExecution { get; set; }
|
||||
internal DateTime LastExecution { get; set; }
|
||||
public TimeSpan Interval { get; set; }
|
||||
public DateTime NextExecution => LastExecution.Add(Interval);
|
||||
public bool IsDue => NextExecution <= DateTime.UtcNow;
|
||||
|
@ -1,19 +0,0 @@
|
||||
using API.Schema.MangaContext;
|
||||
namespace API.Workers;
|
||||
|
||||
public class UpdateChaptersDownloadedWorker(Manga manga, IEnumerable<BaseWorker>? dependsOn = null)
|
||||
: BaseWorkerWithContext<MangaContext>(dependsOn), IPeriodic
|
||||
{
|
||||
public DateTime LastExecution { get; set; } = DateTime.UtcNow;
|
||||
public TimeSpan Interval { get; set; } = TimeSpan.FromMinutes(60);
|
||||
protected override BaseWorker[] DoWorkInternal()
|
||||
{
|
||||
foreach (Chapter mangaChapter in manga.Chapters)
|
||||
{
|
||||
mangaChapter.Downloaded = mangaChapter.CheckDownloaded();
|
||||
}
|
||||
|
||||
DbContext.Sync();
|
||||
return [];
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ using System.IO.Compression;
|
||||
using System.Runtime.InteropServices;
|
||||
using API.MangaDownloadClients;
|
||||
using API.Schema.MangaContext;
|
||||
using API.Schema.MangaContext.MangaConnectors;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.Formats.Jpeg;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
@ -10,21 +11,23 @@ using static System.IO.UnixFileMode;
|
||||
|
||||
namespace API.Workers;
|
||||
|
||||
public class DownloadChapterFromMangaconnectorWorker(Chapter chapter, IEnumerable<BaseWorker>? dependsOn = null)
|
||||
public class DownloadChapterFromMangaconnectorWorker(MangaConnectorId<Chapter> chId, IEnumerable<BaseWorker>? dependsOn = null)
|
||||
: BaseWorkerWithContext<MangaContext>(dependsOn)
|
||||
{
|
||||
internal readonly string MangaConnectorIdId = chId.Key;
|
||||
protected override BaseWorker[] DoWorkInternal()
|
||||
{
|
||||
if (DbContext.MangaConnectorToChapter.Find(MangaConnectorIdId) is not { } MangaConnectorId)
|
||||
return []; //TODO Exception?
|
||||
MangaConnector mangaConnector = MangaConnectorId.MangaConnector;
|
||||
Chapter chapter = MangaConnectorId.Obj;
|
||||
if (chapter.Downloaded)
|
||||
{
|
||||
Log.Info("Chapter was already downloaded.");
|
||||
return [];
|
||||
}
|
||||
|
||||
//TODO MangaConnector Selection
|
||||
MangaConnectorId<Chapter> mcId = chapter.MangaConnectorIds.First();
|
||||
|
||||
string[] imageUrls = mcId.MangaConnector.GetChapterImageUrls(mcId);
|
||||
string[] imageUrls = mangaConnector.GetChapterImageUrls(MangaConnectorId);
|
||||
if (imageUrls.Length < 1)
|
||||
{
|
||||
Log.Info($"No imageUrls for chapter {chapter}");
|
||||
@ -96,7 +99,7 @@ public class DownloadChapterFromMangaconnectorWorker(Chapter chapter, IEnumerabl
|
||||
|
||||
private void ProcessImage(string imagePath)
|
||||
{
|
||||
if (!TrangaSettings.bwImages && TrangaSettings.compression == 100)
|
||||
if (!Tranga.Settings.BlackWhiteImages && Tranga.Settings.ImageCompression == 100)
|
||||
{
|
||||
Log.Debug("No processing requested for image");
|
||||
return;
|
||||
@ -107,12 +110,12 @@ public class DownloadChapterFromMangaconnectorWorker(Chapter chapter, IEnumerabl
|
||||
try
|
||||
{
|
||||
using Image image = Image.Load(imagePath);
|
||||
if (TrangaSettings.bwImages)
|
||||
if (Tranga.Settings.BlackWhiteImages)
|
||||
image.Mutate(i => i.ApplyProcessor(new AdaptiveThresholdProcessor()));
|
||||
File.Delete(imagePath);
|
||||
image.SaveAsJpeg(imagePath, new JpegEncoder()
|
||||
{
|
||||
Quality = TrangaSettings.compression
|
||||
Quality = Tranga.Settings.ImageCompression
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
@ -160,21 +163,23 @@ public class DownloadChapterFromMangaconnectorWorker(Chapter chapter, IEnumerabl
|
||||
File.SetUnixFileMode(newFilePath, GroupRead | GroupWrite | UserRead | UserWrite | OtherRead | OtherWrite);
|
||||
Log.Debug($"Copied cover from {fileInCache} to {newFilePath}");
|
||||
}
|
||||
|
||||
|
||||
private bool DownloadImage(string imageUrl, string savePath)
|
||||
{
|
||||
HttpDownloadClient downloadClient = new();
|
||||
RequestResult requestResult = downloadClient.MakeRequest(imageUrl, RequestType.MangaImage);
|
||||
|
||||
|
||||
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300)
|
||||
return false;
|
||||
if (requestResult.result == Stream.Null)
|
||||
return false;
|
||||
|
||||
FileStream fs = new (savePath, FileMode.Create, FileAccess.Write, FileShare.None);
|
||||
FileStream fs = new(savePath, FileMode.Create, FileAccess.Write, FileShare.None);
|
||||
requestResult.result.CopyTo(fs);
|
||||
fs.Close();
|
||||
ProcessImage(savePath);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override string ToString() => $"{base.ToString()} {MangaConnectorIdId}";
|
||||
}
|
@ -6,9 +6,11 @@ namespace API.Workers;
|
||||
public class DownloadCoverFromMangaconnectorWorker(MangaConnectorId<Manga> mcId, IEnumerable<BaseWorker>? dependsOn = null)
|
||||
: BaseWorkerWithContext<MangaContext>(dependsOn)
|
||||
{
|
||||
public MangaConnectorId<Manga> MangaConnectorId { get; init; } = mcId;
|
||||
internal readonly string MangaConnectorIdId = mcId.Key;
|
||||
protected override BaseWorker[] DoWorkInternal()
|
||||
{
|
||||
if (DbContext.MangaConnectorToManga.Find(MangaConnectorIdId) is not { } MangaConnectorId)
|
||||
return []; //TODO Exception?
|
||||
MangaConnector mangaConnector = MangaConnectorId.MangaConnector;
|
||||
Manga manga = MangaConnectorId.Obj;
|
||||
|
||||
@ -17,4 +19,6 @@ public class DownloadCoverFromMangaconnectorWorker(MangaConnectorId<Manga> mcId,
|
||||
DbContext.Sync();
|
||||
return [];
|
||||
}
|
||||
|
||||
public override string ToString() => $"{base.ToString()} {MangaConnectorIdId}";
|
||||
}
|
@ -6,26 +6,30 @@ namespace API.Workers;
|
||||
public class RetrieveMangaChaptersFromMangaconnectorWorker(MangaConnectorId<Manga> mcId, string language, IEnumerable<BaseWorker>? dependsOn = null)
|
||||
: BaseWorkerWithContext<MangaContext>(dependsOn)
|
||||
{
|
||||
public MangaConnectorId<Manga> MangaConnectorId { get; init; } = mcId;
|
||||
internal readonly string MangaConnectorIdId = mcId.Key;
|
||||
protected override BaseWorker[] DoWorkInternal()
|
||||
{
|
||||
if (DbContext.MangaConnectorToManga.Find(MangaConnectorIdId) is not { } MangaConnectorId)
|
||||
return []; //TODO Exception?
|
||||
MangaConnector mangaConnector = MangaConnectorId.MangaConnector;
|
||||
Manga manga = MangaConnectorId.Obj;
|
||||
// This gets all chapters that are not downloaded
|
||||
(Chapter, MangaConnectorId<Chapter>)[] allChapters =
|
||||
mangaConnector.GetChapters(MangaConnectorId, language).DistinctBy(c => c.Item1.Key).ToArray();
|
||||
(Chapter, MangaConnectorId<Chapter>)[] newChapters = allChapters.Where(chapter =>
|
||||
manga.Chapters.Any(ch => chapter.Item1.Key == ch.Key && ch.Downloaded) == false).ToArray();
|
||||
Log.Info($"{manga.Chapters.Count} existing + {newChapters.Length} new chapters.");
|
||||
|
||||
foreach ((Chapter chapter, MangaConnectorId<Chapter> mcId) newChapter in newChapters)
|
||||
int addedChapters = 0;
|
||||
foreach ((Chapter chapter, MangaConnectorId<Chapter> mcId) newChapter in allChapters)
|
||||
{
|
||||
manga.Chapters.Add(newChapter.chapter);
|
||||
DbContext.MangaConnectorToChapter.Add(newChapter.mcId);
|
||||
if (Tranga.AddChapterToContext(newChapter, DbContext, out Chapter? addedChapter) == false)
|
||||
continue;
|
||||
manga.Chapters.Add(addedChapter);
|
||||
}
|
||||
Log.Info($"{manga.Chapters.Count} existing + {addedChapters} new chapters.");
|
||||
|
||||
DbContext.Sync();
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
public override string ToString() => $"{base.ToString()} {MangaConnectorIdId}";
|
||||
}
|
@ -44,4 +44,6 @@ public class MoveFileOrFolderWorker(string toLocation, string fromLocation, IEnu
|
||||
{
|
||||
File.Move(from.FullName, toLocation);
|
||||
}
|
||||
|
||||
public override string ToString() => $"{base.ToString()} {FromLocation} {ToLocation}";
|
||||
}
|
@ -2,17 +2,25 @@ using API.Schema.MangaContext;
|
||||
|
||||
namespace API.Workers;
|
||||
|
||||
public class MoveMangaLibraryWorker(Manga manga, FileLibrary toLibrary, IServiceScope scope, IEnumerable<BaseWorker>? dependsOn = null)
|
||||
public class MoveMangaLibraryWorker(Manga manga, FileLibrary toLibrary, IEnumerable<BaseWorker>? dependsOn = null)
|
||||
: BaseWorkerWithContext<MangaContext>(dependsOn)
|
||||
{
|
||||
internal readonly string MangaId = manga.Key;
|
||||
internal readonly string LibraryId = toLibrary.Key;
|
||||
protected override BaseWorker[] DoWorkInternal()
|
||||
{
|
||||
if (DbContext.Mangas.Find(MangaId) is not { } manga)
|
||||
return []; //TODO Exception?
|
||||
if (DbContext.FileLibraries.Find(LibraryId) is not { } toLibrary)
|
||||
return []; //TODO Exception?
|
||||
Dictionary<Chapter, string> oldPath = manga.Chapters.ToDictionary(c => c, c => c.FullArchiveFilePath);
|
||||
manga.Library = toLibrary;
|
||||
|
||||
if (DbContext.Sync().Result is { success: false })
|
||||
if (DbContext.Sync() is { success: false })
|
||||
return [];
|
||||
|
||||
return manga.Chapters.Select(c => new MoveFileOrFolderWorker(c.FullArchiveFilePath, oldPath[c])).ToArray<BaseWorker>();
|
||||
}
|
||||
|
||||
public override string ToString() => $"{base.ToString()} {MangaId} {LibraryId}";
|
||||
}
|
22
API/Workers/PeriodicWorkers/CheckForNewChaptersWorker.cs
Normal file
22
API/Workers/PeriodicWorkers/CheckForNewChaptersWorker.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using API.Schema.MangaContext;
|
||||
|
||||
namespace API.Workers;
|
||||
|
||||
public class CheckForNewChaptersWorker(TimeSpan? interval = null, IEnumerable<BaseWorker>? dependsOn = null)
|
||||
: BaseWorkerWithContext<MangaContext>(dependsOn), IPeriodic
|
||||
{
|
||||
public DateTime LastExecution { get; set; } = DateTime.UnixEpoch;
|
||||
public TimeSpan Interval { get; set; } = interval??TimeSpan.FromMinutes(60);
|
||||
|
||||
protected override BaseWorker[] DoWorkInternal()
|
||||
{
|
||||
IQueryable<MangaConnectorId<Manga>> connectorIdsManga = DbContext.MangaConnectorToManga.Where(id => id.UseForDownload);
|
||||
|
||||
List<BaseWorker> newWorkers = new();
|
||||
foreach (MangaConnectorId<Manga> mangaConnectorId in connectorIdsManga)
|
||||
newWorkers.Add(new RetrieveMangaChaptersFromMangaconnectorWorker(mangaConnectorId, Tranga.Settings.DownloadLanguage));
|
||||
|
||||
return newWorkers.ToArray();
|
||||
}
|
||||
|
||||
}
|
@ -2,10 +2,11 @@ using API.Schema.MangaContext;
|
||||
|
||||
namespace API.Workers.MaintenanceWorkers;
|
||||
|
||||
public class CleanupMangaCoversWorker(IEnumerable<BaseWorker>? dependsOn = null) : BaseWorkerWithContext<MangaContext>(dependsOn), IPeriodic
|
||||
public class CleanupMangaCoversWorker(TimeSpan? interval = null, IEnumerable<BaseWorker>? dependsOn = null)
|
||||
: BaseWorkerWithContext<MangaContext>(dependsOn), IPeriodic
|
||||
{
|
||||
public DateTime LastExecution { get; set; } = DateTime.UtcNow;
|
||||
public TimeSpan Interval { get; set; } = TimeSpan.FromMinutes(60);
|
||||
public DateTime LastExecution { get; set; } = DateTime.UnixEpoch;
|
||||
public TimeSpan Interval { get; set; } = interval ?? TimeSpan.FromHours(24);
|
||||
|
||||
protected override BaseWorker[] DoWorkInternal()
|
||||
{
|
@ -0,0 +1,19 @@
|
||||
using API.Schema.NotificationsContext;
|
||||
|
||||
namespace API.Workers.MaintenanceWorkers;
|
||||
|
||||
public class RemoveOldNotificationsWorker(TimeSpan? interval = null, IEnumerable<BaseWorker>? dependsOn = null)
|
||||
: BaseWorkerWithContext<NotificationsContext>(dependsOn), IPeriodic
|
||||
{
|
||||
public DateTime LastExecution { get; set; } = DateTime.UnixEpoch;
|
||||
public TimeSpan Interval { get; set; } = interval ?? TimeSpan.FromHours(1);
|
||||
|
||||
protected override BaseWorker[] DoWorkInternal()
|
||||
{
|
||||
IQueryable<Notification> toRemove = DbContext.Notifications.Where(n => n.IsSent || DateTime.UtcNow - n.Date > Interval);
|
||||
DbContext.RemoveRange(toRemove);
|
||||
DbContext.Sync();
|
||||
return [];
|
||||
}
|
||||
|
||||
}
|
29
API/Workers/PeriodicWorkers/SendNotificationsWorker.cs
Normal file
29
API/Workers/PeriodicWorkers/SendNotificationsWorker.cs
Normal file
@ -0,0 +1,29 @@
|
||||
using API.Schema.NotificationsContext;
|
||||
using API.Schema.NotificationsContext.NotificationConnectors;
|
||||
|
||||
namespace API.Workers;
|
||||
|
||||
public class SendNotificationsWorker(TimeSpan? interval = null, IEnumerable<BaseWorker>? dependsOn = null)
|
||||
: BaseWorkerWithContext<NotificationsContext>(dependsOn), IPeriodic
|
||||
{
|
||||
public DateTime LastExecution { get; set; } = DateTime.UnixEpoch;
|
||||
public TimeSpan Interval { get; set; } = interval??TimeSpan.FromMinutes(1);
|
||||
protected override BaseWorker[] DoWorkInternal()
|
||||
{
|
||||
NotificationConnector[] connectors = DbContext.NotificationConnectors.ToArray();
|
||||
Notification[] notifications = DbContext.Notifications.Where(n => n.IsSent == false).ToArray();
|
||||
|
||||
foreach (Notification notification in notifications)
|
||||
{
|
||||
foreach (NotificationConnector connector in connectors)
|
||||
{
|
||||
connector.SendNotification(notification.Title, notification.Message);
|
||||
notification.IsSent = true;
|
||||
}
|
||||
}
|
||||
|
||||
DbContext.Sync();
|
||||
return [];
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
using API.Schema.MangaContext;
|
||||
|
||||
namespace API.Workers;
|
||||
|
||||
public class StartNewChapterDownloadsWorker(TimeSpan? interval = null, IEnumerable<BaseWorker>? dependsOn = null)
|
||||
: BaseWorkerWithContext<MangaContext>(dependsOn), IPeriodic
|
||||
{
|
||||
|
||||
public DateTime LastExecution { get; set; } = DateTime.UnixEpoch;
|
||||
public TimeSpan Interval { get; set; } = interval ?? TimeSpan.FromMinutes(1);
|
||||
protected override BaseWorker[] DoWorkInternal()
|
||||
{
|
||||
IQueryable<MangaConnectorId<Chapter>> mangaConnectorIds = DbContext.MangaConnectorToChapter.Where(id => id.Obj.Downloaded == false && id.UseForDownload);
|
||||
|
||||
List<BaseWorker> newWorkers = new();
|
||||
foreach (MangaConnectorId<Chapter> mangaConnectorId in mangaConnectorIds)
|
||||
newWorkers.Add(new DownloadChapterFromMangaconnectorWorker(mangaConnectorId));
|
||||
|
||||
return newWorkers.ToArray();
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
using API.Schema.MangaContext;
|
||||
namespace API.Workers;
|
||||
|
||||
public class UpdateChaptersDownloadedWorker(TimeSpan? interval = null, IEnumerable<BaseWorker>? dependsOn = null)
|
||||
: BaseWorkerWithContext<MangaContext>(dependsOn), IPeriodic
|
||||
{
|
||||
public DateTime LastExecution { get; set; } = DateTime.UnixEpoch;
|
||||
public TimeSpan Interval { get; set; } = interval??TimeSpan.FromMinutes(60);
|
||||
protected override BaseWorker[] DoWorkInternal()
|
||||
{
|
||||
foreach (Chapter dbContextChapter in DbContext.Chapters)
|
||||
dbContextChapter.Downloaded = dbContextChapter.CheckDownloaded();
|
||||
|
||||
DbContext.Sync();
|
||||
return [];
|
||||
}
|
||||
}
|
31
API/Workers/PeriodicWorkers/UpdateMetadataWorker.cs
Normal file
31
API/Workers/PeriodicWorkers/UpdateMetadataWorker.cs
Normal file
@ -0,0 +1,31 @@
|
||||
using API.Schema.MangaContext;
|
||||
using API.Schema.MangaContext.MetadataFetchers;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace API.Workers;
|
||||
|
||||
public class UpdateMetadataWorker(TimeSpan? interval = null, IEnumerable<BaseWorker>? dependsOn = null)
|
||||
: BaseWorkerWithContext<MangaContext>(dependsOn), IPeriodic
|
||||
{
|
||||
|
||||
public DateTime LastExecution { get; set; } = DateTime.UnixEpoch;
|
||||
public TimeSpan Interval { get; set; } = interval ?? TimeSpan.FromHours(12);
|
||||
|
||||
protected override BaseWorker[] DoWorkInternal()
|
||||
{
|
||||
IQueryable<string> mangaIds = DbContext.MangaConnectorToManga
|
||||
.Where(m => m.UseForDownload)
|
||||
.Select(m => m.ObjId);
|
||||
IQueryable<MetadataEntry> metadataEntriesToUpdate = DbContext.MetadataEntries
|
||||
.Include(e => e.MetadataFetcher)
|
||||
.Where(e =>
|
||||
mangaIds.Any(id => id == e.MangaId));
|
||||
|
||||
foreach (MetadataEntry metadataEntry in metadataEntriesToUpdate)
|
||||
metadataEntry.MetadataFetcher.UpdateMetadata(metadataEntry, DbContext);
|
||||
|
||||
DbContext.Sync();
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
using API.Schema.NotificationsContext;
|
||||
|
||||
namespace API.Workers;
|
||||
|
||||
public class SendNotificationsWorker(IEnumerable<BaseWorker>? dependsOn = null)
|
||||
: BaseWorkerWithContext<NotificationsContext>(dependsOn), IPeriodic
|
||||
{
|
||||
public DateTime LastExecution { get; set; } = DateTime.UtcNow;
|
||||
public TimeSpan Interval { get; set; } = TimeSpan.FromMinutes(1);
|
||||
protected override BaseWorker[] DoWorkInternal()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user