mirror of
https://github.com/C9Glax/tranga.git
synced 2025-07-01 00:14:17 +02:00
Compare commits
9 Commits
postgres-S
...
JobQueue-S
Author | SHA1 | Date | |
---|---|---|---|
314700aa8e | |||
b299cc9109 | |||
7e9ba7090a | |||
ea73d03b8f | |||
e9d9bebcd7 | |||
e5937d2654 | |||
7c9e0eddf9 | |||
ae0c6c8240 | |||
2eb0d941b2 |
@ -12,6 +12,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Asp.Versioning.Mvc.ApiExplorer" Version="8.1.0" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.12.0" />
|
||||
<PackageReference Include="JikanDotNet" Version="2.9.1" />
|
||||
<PackageReference Include="log4net" Version="3.0.4" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="9.0.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.3" />
|
||||
@ -32,8 +33,4 @@
|
||||
<PackageReference Include="System.Drawing.Common" Version="9.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Migrations\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -37,7 +37,7 @@ public class JobController(PgsqlContext context, ILog Log) : Controller
|
||||
[ProducesResponseType<Job[]>(Status200OK, "application/json")]
|
||||
public IActionResult GetJobs([FromBody]string[] ids)
|
||||
{
|
||||
Job[] ret = context.Jobs.Where(job => ids.Contains(job.JobId)).ToArray();
|
||||
Job[] ret = context.Jobs.Where(job => ids.Contains(job.Key)).ToArray();
|
||||
return Ok(ret);
|
||||
}
|
||||
|
||||
@ -103,11 +103,11 @@ public class JobController(PgsqlContext context, ILog Log) : Controller
|
||||
/// <summary>
|
||||
/// Create a new DownloadAvailableChaptersJob
|
||||
/// </summary>
|
||||
/// <param name="MangaId">ID of Manga</param>
|
||||
/// <param name="MangaId">ID of Obj</param>
|
||||
/// <param name="record">Job-Configuration</param>
|
||||
/// <response code="201">Job-IDs</response>
|
||||
/// <response code="400">Could not find ToLibrary with ID</response>
|
||||
/// <response code="404">Could not find Manga with ID</response>
|
||||
/// <response code="400">Could not find ToFileLibrary with ID</response>
|
||||
/// <response code="404">Could not find Obj with ID</response>
|
||||
/// <response code="500">Error during Database Operation</response>
|
||||
[HttpPut("DownloadAvailableChaptersJob/{MangaId}")]
|
||||
[ProducesResponseType<string[]>(Status201Created, "application/json")]
|
||||
@ -122,7 +122,7 @@ public class JobController(PgsqlContext context, ILog Log) : Controller
|
||||
{
|
||||
try
|
||||
{
|
||||
LocalLibrary? l = context.LocalLibraries.Find(record.localLibraryId);
|
||||
FileLibrary? l = context.LocalLibraries.Find(record.localLibraryId);
|
||||
if (l is null)
|
||||
return BadRequest();
|
||||
m.Library = l;
|
||||
@ -166,9 +166,9 @@ public class JobController(PgsqlContext context, ILog Log) : Controller
|
||||
/// <summary>
|
||||
/// Create a new UpdateChaptersDownloadedJob
|
||||
/// </summary>
|
||||
/// <param name="MangaId">ID of the Manga</param>
|
||||
/// <param name="MangaId">ID of the Obj</param>
|
||||
/// <response code="201">Job-IDs</response>
|
||||
/// <response code="201">Could not find Manga with ID</response>
|
||||
/// <response code="201">Could not find Obj with ID</response>
|
||||
/// <response code="500">Error during Database Operation</response>
|
||||
[HttpPut("UpdateFilesJob/{MangaId}")]
|
||||
[ProducesResponseType<string[]>(Status201Created, "application/json")]
|
||||
@ -183,7 +183,7 @@ public class JobController(PgsqlContext context, ILog Log) : Controller
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new UpdateMetadataJob for all Manga
|
||||
/// Create a new UpdateMetadataJob for all Obj
|
||||
/// </summary>
|
||||
/// <response code="201">Job-IDs</response>
|
||||
/// <response code="500">Error during Database Operation</response>
|
||||
@ -209,9 +209,9 @@ public class JobController(PgsqlContext context, ILog Log) : Controller
|
||||
/// <summary>
|
||||
/// Not Implemented: Create a new UpdateMetadataJob
|
||||
/// </summary>
|
||||
/// <param name="MangaId">ID of the Manga</param>
|
||||
/// <param name="MangaId">ID of the Obj</param>
|
||||
/// <response code="201">Job-IDs</response>
|
||||
/// <response code="404">Could not find Manga with ID</response>
|
||||
/// <response code="404">Could not find Obj with ID</response>
|
||||
/// <response code="500">Error during Database Operation</response>
|
||||
[HttpPut("UpdateMetadataJob/{MangaId}")]
|
||||
[ProducesResponseType<string[]>(Status201Created, "application/json")]
|
||||
@ -223,7 +223,7 @@ public class JobController(PgsqlContext context, ILog Log) : Controller
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Not Implemented: Create a new UpdateMetadataJob for all Manga
|
||||
/// Not Implemented: Create a new UpdateMetadataJob for all Obj
|
||||
/// </summary>
|
||||
/// <response code="201">Job-IDs</response>
|
||||
/// <response code="500">Error during Database Operation</response>
|
||||
@ -241,7 +241,7 @@ public class JobController(PgsqlContext context, ILog Log) : Controller
|
||||
{
|
||||
context.Jobs.AddRange(jobs);
|
||||
context.SaveChanges();
|
||||
return new CreatedResult((string?)null, jobs.Select(j => j.JobId).ToArray());
|
||||
return new CreatedResult((string?)null, jobs.Select(j => j.Key).ToArray());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@ -279,15 +279,6 @@ public class JobController(PgsqlContext context, ILog Log) : Controller
|
||||
}
|
||||
}
|
||||
|
||||
private IQueryable<Job> GetChildJobs(string parentJobId)
|
||||
{
|
||||
IQueryable<Job> children = context.Jobs.Where(j => j.ParentJobId == parentJobId);
|
||||
foreach (Job child in children)
|
||||
foreach (Job grandChild in GetChildJobs(child.JobId))
|
||||
children.Append(grandChild);
|
||||
return children;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modify Job with ID
|
||||
/// </summary>
|
||||
@ -314,7 +305,7 @@ public class JobController(PgsqlContext context, ILog Log) : Controller
|
||||
ret.Enabled = modifyJobRecord.Enabled ?? ret.Enabled;
|
||||
|
||||
context.SaveChanges();
|
||||
return new AcceptedResult(ret.JobId, ret);
|
||||
return new AcceptedResult(ret.Key, ret);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
@ -1,5 +1,4 @@
|
||||
using API.Schema;
|
||||
using API.Schema.Contexts;
|
||||
using API.Schema.Contexts;
|
||||
using API.Schema.LibraryConnectors;
|
||||
using Asp.Versioning;
|
||||
using log4net;
|
||||
@ -14,7 +13,7 @@ namespace API.Controllers;
|
||||
public class LibraryConnectorController(LibraryContext context, ILog Log) : Controller
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets all configured ToLibrary-Connectors
|
||||
/// Gets all configured ToFileLibrary-Connectors
|
||||
/// </summary>
|
||||
/// <response code="200"></response>
|
||||
[HttpGet]
|
||||
@ -26,9 +25,9 @@ public class LibraryConnectorController(LibraryContext context, ILog Log) : Cont
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns ToLibrary-Connector with requested ID
|
||||
/// Returns ToFileLibrary-Connector with requested ID
|
||||
/// </summary>
|
||||
/// <param name="LibraryControllerId">ToLibrary-Connector-ID</param>
|
||||
/// <param name="LibraryControllerId">ToFileLibrary-Connector-ID</param>
|
||||
/// <response code="200"></response>
|
||||
/// <response code="404">Connector with ID not found.</response>
|
||||
[HttpGet("{LibraryControllerId}")]
|
||||
@ -45,9 +44,9 @@ public class LibraryConnectorController(LibraryContext context, ILog Log) : Cont
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new ToLibrary-Connector
|
||||
/// Creates a new ToFileLibrary-Connector
|
||||
/// </summary>
|
||||
/// <param name="libraryConnector">ToLibrary-Connector</param>
|
||||
/// <param name="libraryConnector">ToFileLibrary-Connector</param>
|
||||
/// <response code="201"></response>
|
||||
/// <response code="500">Error during Database Operation</response>
|
||||
[HttpPut]
|
||||
@ -69,9 +68,9 @@ public class LibraryConnectorController(LibraryContext context, ILog Log) : Cont
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the ToLibrary-Connector with the requested ID
|
||||
/// Deletes the ToFileLibrary-Connector with the requested ID
|
||||
/// </summary>
|
||||
/// <param name="LibraryControllerId">ToLibrary-Connector-ID</param>
|
||||
/// <param name="LibraryControllerId">ToFileLibrary-Connector-ID</param>
|
||||
/// <response code="200"></response>
|
||||
/// <response code="404">Connector with ID not found.</response>
|
||||
/// <response code="500">Error during Database Operation</response>
|
||||
|
@ -14,18 +14,18 @@ namespace API.Controllers;
|
||||
public class LocalLibrariesController(PgsqlContext context, ILog Log) : Controller
|
||||
{
|
||||
[HttpGet]
|
||||
[ProducesResponseType<LocalLibrary[]>(Status200OK, "application/json")]
|
||||
[ProducesResponseType<FileLibrary[]>(Status200OK, "application/json")]
|
||||
public IActionResult GetLocalLibraries()
|
||||
{
|
||||
return Ok(context.LocalLibraries);
|
||||
}
|
||||
|
||||
[HttpGet("{LibraryId}")]
|
||||
[ProducesResponseType<LocalLibrary>(Status200OK, "application/json")]
|
||||
[ProducesResponseType<FileLibrary>(Status200OK, "application/json")]
|
||||
[ProducesResponseType(Status404NotFound)]
|
||||
public IActionResult GetLocalLibrary(string LibraryId)
|
||||
{
|
||||
LocalLibrary? library = context.LocalLibraries.Find(LibraryId);
|
||||
FileLibrary? library = context.LocalLibraries.Find(LibraryId);
|
||||
if (library is null)
|
||||
return NotFound();
|
||||
return Ok(library);
|
||||
@ -38,7 +38,7 @@ public class LocalLibrariesController(PgsqlContext context, ILog Log) : Controll
|
||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||
public IActionResult UpdateLocalLibrary(string LibraryId, [FromBody]NewLibraryRecord record)
|
||||
{
|
||||
LocalLibrary? library = context.LocalLibraries.Find(LibraryId);
|
||||
FileLibrary? library = context.LocalLibraries.Find(LibraryId);
|
||||
if (library is null)
|
||||
return NotFound();
|
||||
if (record.Validate() == false)
|
||||
@ -68,7 +68,7 @@ public class LocalLibrariesController(PgsqlContext context, ILog Log) : Controll
|
||||
{
|
||||
try
|
||||
{
|
||||
LocalLibrary? library = context.LocalLibraries.Find(LibraryId);
|
||||
FileLibrary? library = context.LocalLibraries.Find(LibraryId);
|
||||
if (library is null)
|
||||
return NotFound();
|
||||
|
||||
@ -96,7 +96,7 @@ public class LocalLibrariesController(PgsqlContext context, ILog Log) : Controll
|
||||
{
|
||||
try
|
||||
{
|
||||
LocalLibrary? library = context.LocalLibraries.Find(LibraryId);
|
||||
FileLibrary? library = context.LocalLibraries.Find(LibraryId);
|
||||
if (library is null)
|
||||
return NotFound();
|
||||
|
||||
@ -116,7 +116,7 @@ public class LocalLibrariesController(PgsqlContext context, ILog Log) : Controll
|
||||
}
|
||||
|
||||
[HttpPut]
|
||||
[ProducesResponseType<LocalLibrary>(Status200OK, "application/json")]
|
||||
[ProducesResponseType<FileLibrary>(Status200OK, "application/json")]
|
||||
[ProducesResponseType(Status400BadRequest)]
|
||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||
public IActionResult CreateNewLibrary([FromBody]NewLibraryRecord library)
|
||||
@ -125,11 +125,11 @@ public class LocalLibrariesController(PgsqlContext context, ILog Log) : Controll
|
||||
return BadRequest();
|
||||
try
|
||||
{
|
||||
LocalLibrary newLibrary = new (library.path, library.name);
|
||||
context.LocalLibraries.Add(newLibrary);
|
||||
FileLibrary newFileLibrary = new (library.path, library.name);
|
||||
context.LocalLibraries.Add(newFileLibrary);
|
||||
context.SaveChanges();
|
||||
|
||||
return Ok(newLibrary);
|
||||
return Ok(newFileLibrary);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@ -147,7 +147,7 @@ public class LocalLibrariesController(PgsqlContext context, ILog Log) : Controll
|
||||
|
||||
try
|
||||
{
|
||||
LocalLibrary? library = context.LocalLibraries.Find(LibraryId);
|
||||
FileLibrary? library = context.LocalLibraries.Find(LibraryId);
|
||||
if (library is null)
|
||||
return NotFound();
|
||||
context.Remove(library);
|
||||
|
@ -4,6 +4,7 @@ using API.Schema.Jobs;
|
||||
using Asp.Versioning;
|
||||
using log4net;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.Formats.Jpeg;
|
||||
@ -20,7 +21,7 @@ namespace API.Controllers;
|
||||
public class MangaController(PgsqlContext context, ILog Log) : Controller
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns all cached Manga
|
||||
/// Returns all cached Obj
|
||||
/// </summary>
|
||||
/// <response code="200"></response>
|
||||
[HttpGet]
|
||||
@ -32,24 +33,24 @@ public class MangaController(PgsqlContext context, ILog Log) : Controller
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all cached Manga with IDs
|
||||
/// Returns all cached Obj with IDs
|
||||
/// </summary>
|
||||
/// <param name="ids">Array of Manga-IDs</param>
|
||||
/// <param name="ids">Array of Obj-IDs</param>
|
||||
/// <response code="200"></response>
|
||||
[HttpPost("WithIDs")]
|
||||
[ProducesResponseType<Manga[]>(Status200OK, "application/json")]
|
||||
public IActionResult GetManga([FromBody]string[] ids)
|
||||
{
|
||||
Manga[] ret = context.Mangas.Where(m => ids.Contains(m.MangaId)).ToArray();
|
||||
Manga[] ret = context.Mangas.Where(m => ids.Contains(m.Key)).ToArray();
|
||||
return Ok(ret);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return Manga with ID
|
||||
/// Return Obj with ID
|
||||
/// </summary>
|
||||
/// <param name="MangaId">Manga-ID</param>
|
||||
/// <param name="MangaId">Obj-ID</param>
|
||||
/// <response code="200"></response>
|
||||
/// <response code="404">Manga with ID not found</response>
|
||||
/// <response code="404">Obj with ID not found</response>
|
||||
[HttpGet("{MangaId}")]
|
||||
[ProducesResponseType<Manga>(Status200OK, "application/json")]
|
||||
[ProducesResponseType(Status404NotFound)]
|
||||
@ -62,11 +63,11 @@ public class MangaController(PgsqlContext context, ILog Log) : Controller
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete Manga with ID
|
||||
/// Delete Obj with ID
|
||||
/// </summary>
|
||||
/// <param name="MangaId">Manga-ID</param>
|
||||
/// <param name="MangaId">Obj-ID</param>
|
||||
/// <response code="200"></response>
|
||||
/// <response code="404">Manga with ID not found</response>
|
||||
/// <response code="404">Obj with ID not found</response>
|
||||
/// <response code="500">Error during Database Operation</response>
|
||||
[HttpDelete("{MangaId}")]
|
||||
[ProducesResponseType(Status200OK)]
|
||||
@ -91,16 +92,45 @@ public class MangaController(PgsqlContext context, ILog Log) : Controller
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns Cover of Manga
|
||||
/// Merge two Manga into one. THIS IS NOT REVERSIBLE!
|
||||
/// </summary>
|
||||
/// <param name="MangaId">Manga-ID</param>
|
||||
/// <response code="200"></response>
|
||||
/// <response code="404">MangaId not found</response>
|
||||
/// <response code="500">Error during Database Operation</response>
|
||||
[HttpPatch("{MangaIdFrom}/MergeInto/{MangaIdTo}")]
|
||||
[ProducesResponseType<byte[]>(Status200OK,"image/jpeg")]
|
||||
[ProducesResponseType(Status404NotFound)]
|
||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||
public IActionResult MergeIntoManga(string MangaIdFrom, string MangaIdTo)
|
||||
{
|
||||
if(context.Mangas.Find(MangaIdFrom) is not { } from)
|
||||
return NotFound(MangaIdFrom);
|
||||
if(context.Mangas.Find(MangaIdTo) is not { } to)
|
||||
return NotFound(MangaIdTo);
|
||||
try
|
||||
{
|
||||
to.MergeFrom(from, context);
|
||||
return Ok();
|
||||
}
|
||||
catch (DbUpdateException e)
|
||||
{
|
||||
Log.Error(e);
|
||||
return StatusCode(500, e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns Cover of Obj
|
||||
/// </summary>
|
||||
/// <param name="MangaId">Obj-ID</param>
|
||||
/// <param name="width">If width is provided, height needs to also be provided</param>
|
||||
/// <param name="height">If height is provided, width needs to also be provided</param>
|
||||
/// <response code="200">JPEG Image</response>
|
||||
/// <response code="204">Cover not loaded</response>
|
||||
/// <response code="400">The formatting-request was invalid</response>
|
||||
/// <response code="404">Manga with ID not found</response>
|
||||
/// <response code="404">Obj with ID not found</response>
|
||||
/// <response code="503">Retry later, downloading cover</response>
|
||||
[HttpGet("{MangaId}/Cover")]
|
||||
[ProducesResponseType<byte[]>(Status200OK,"image/jpeg")]
|
||||
@ -115,7 +145,7 @@ public class MangaController(PgsqlContext context, ILog Log) : Controller
|
||||
|
||||
if (!System.IO.File.Exists(m.CoverFileNameInCache))
|
||||
{
|
||||
List<Job> coverDownloadJobs = context.Jobs.Where(j => j.JobType == JobType.DownloadMangaCoverJob).ToList();
|
||||
List<Job> coverDownloadJobs = context.Jobs.Where(j => j.JobType == JobType.DownloadMangaCoverJob).Include(j => ((DownloadMangaCoverJob)j).Manga).ToList();
|
||||
if (coverDownloadJobs.Any(j => j is DownloadMangaCoverJob dmc && dmc.MangaId == MangaId && dmc.state < JobState.Completed))
|
||||
{
|
||||
Response.Headers.Append("Retry-After", $"{TrangaSettings.startNewJobTimeoutMs * coverDownloadJobs.Count() * 2 / 1000:D}");
|
||||
@ -146,11 +176,11 @@ public class MangaController(PgsqlContext context, ILog Log) : Controller
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all Chapters of Manga
|
||||
/// Returns all Chapters of Obj
|
||||
/// </summary>
|
||||
/// <param name="MangaId">Manga-ID</param>
|
||||
/// <param name="MangaId">Obj-ID</param>
|
||||
/// <response code="200"></response>
|
||||
/// <response code="404">Manga with ID not found</response>
|
||||
/// <response code="404">Obj with ID not found</response>
|
||||
[HttpGet("{MangaId}/Chapters")]
|
||||
[ProducesResponseType<Chapter[]>(Status200OK, "application/json")]
|
||||
[ProducesResponseType(Status404NotFound)]
|
||||
@ -164,12 +194,12 @@ public class MangaController(PgsqlContext context, ILog Log) : Controller
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all downloaded Chapters for Manga with ID
|
||||
/// Returns all downloaded Chapters for Obj with ID
|
||||
/// </summary>
|
||||
/// <param name="MangaId">Manga-ID</param>
|
||||
/// <param name="MangaId">Obj-ID</param>
|
||||
/// <response code="200"></response>
|
||||
/// <response code="204">No available chapters</response>
|
||||
/// <response code="404">Manga with ID not found.</response>
|
||||
/// <response code="404">Obj with ID not found.</response>
|
||||
[HttpGet("{MangaId}/Chapters/Downloaded")]
|
||||
[ProducesResponseType<Chapter[]>(Status200OK, "application/json")]
|
||||
[ProducesResponseType(Status204NoContent)]
|
||||
@ -187,12 +217,12 @@ public class MangaController(PgsqlContext context, ILog Log) : Controller
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all Chapters not downloaded for Manga with ID
|
||||
/// Returns all Chapters not downloaded for Obj with ID
|
||||
/// </summary>
|
||||
/// <param name="MangaId">Manga-ID</param>
|
||||
/// <param name="MangaId">Obj-ID</param>
|
||||
/// <response code="200"></response>
|
||||
/// <response code="204">No available chapters</response>
|
||||
/// <response code="404">Manga with ID not found.</response>
|
||||
/// <response code="404">Obj with ID not found.</response>
|
||||
[HttpGet("{MangaId}/Chapters/NotDownloaded")]
|
||||
[ProducesResponseType<Chapter[]>(Status200OK, "application/json")]
|
||||
[ProducesResponseType(Status204NoContent)]
|
||||
@ -210,12 +240,12 @@ public class MangaController(PgsqlContext context, ILog Log) : Controller
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the latest Chapter of requested Manga available on Website
|
||||
/// Returns the latest Chapter of requested Obj available on Website
|
||||
/// </summary>
|
||||
/// <param name="MangaId">Manga-ID</param>
|
||||
/// <param name="MangaId">Obj-ID</param>
|
||||
/// <response code="200"></response>
|
||||
/// <response code="204">No available chapters</response>
|
||||
/// <response code="404">Manga with ID not found.</response>
|
||||
/// <response code="404">Obj with ID not found.</response>
|
||||
/// <response code="500">Could not retrieve the maximum chapter-number</response>
|
||||
/// <response code="503">Retry after timeout, updating value</response>
|
||||
[HttpGet("{MangaId}/Chapter/LatestAvailable")]
|
||||
@ -232,7 +262,7 @@ public class MangaController(PgsqlContext context, ILog Log) : Controller
|
||||
List<Chapter> chapters = m.Chapters.ToList();
|
||||
if (chapters.Count == 0)
|
||||
{
|
||||
List<Job> retrieveChapterJobs = context.Jobs.Where(j => j.JobType == JobType.RetrieveChaptersJob).ToList();
|
||||
List<Job> retrieveChapterJobs = context.Jobs.Where(j => j.JobType == JobType.RetrieveChaptersJob).Include(j => ((RetrieveChaptersJob)j).Manga).ToList();
|
||||
if (retrieveChapterJobs.Any(j => j is RetrieveChaptersJob rcj && rcj.MangaId == MangaId && rcj.state < JobState.Completed))
|
||||
{
|
||||
Response.Headers.Append("Retry-After", $"{TrangaSettings.startNewJobTimeoutMs * retrieveChapterJobs.Count() * 2 / 1000:D}");
|
||||
@ -249,12 +279,12 @@ public class MangaController(PgsqlContext context, ILog Log) : Controller
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the latest Chapter of requested Manga that is downloaded
|
||||
/// Returns the latest Chapter of requested Obj that is downloaded
|
||||
/// </summary>
|
||||
/// <param name="MangaId">Manga-ID</param>
|
||||
/// <param name="MangaId">Obj-ID</param>
|
||||
/// <response code="200"></response>
|
||||
/// <response code="204">No available chapters</response>
|
||||
/// <response code="404">Manga with ID not found.</response>
|
||||
/// <response code="404">Obj with ID not found.</response>
|
||||
/// <response code="500">Could not retrieve the maximum chapter-number</response>
|
||||
/// <response code="503">Retry after timeout, updating value</response>
|
||||
[HttpGet("{MangaId}/Chapter/LatestDownloaded")]
|
||||
@ -271,7 +301,7 @@ public class MangaController(PgsqlContext context, ILog Log) : Controller
|
||||
List<Chapter> chapters = m.Chapters.ToList();
|
||||
if (chapters.Count == 0)
|
||||
{
|
||||
List<Job> retrieveChapterJobs = context.Jobs.Where(j => j.JobType == JobType.RetrieveChaptersJob).ToList();
|
||||
List<Job> retrieveChapterJobs = context.Jobs.Where(j => j.JobType == JobType.RetrieveChaptersJob).Include(j => ((RetrieveChaptersJob)j).Manga).ToList();
|
||||
if (retrieveChapterJobs.Any(j => j is RetrieveChaptersJob rcj && rcj.MangaId == MangaId && rcj.state < JobState.Completed))
|
||||
{
|
||||
Response.Headers.Append("Retry-After", $"{TrangaSettings.startNewJobTimeoutMs * retrieveChapterJobs.Count() * 2 / 1000:D}");
|
||||
@ -288,12 +318,12 @@ public class MangaController(PgsqlContext context, ILog Log) : Controller
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configure the cut-off for Manga
|
||||
/// Configure the cut-off for Obj
|
||||
/// </summary>
|
||||
/// <param name="MangaId">Manga-ID</param>
|
||||
/// <param name="MangaId">Obj-ID</param>
|
||||
/// <param name="chapterThreshold">Threshold (Chapter Number)</param>
|
||||
/// <response code="200"></response>
|
||||
/// <response code="404">Manga with ID not found.</response>
|
||||
/// <response code="404">Obj with ID not found.</response>
|
||||
/// <response code="500">Error during Database Operation</response>
|
||||
[HttpPatch("{MangaId}/IgnoreChaptersBefore")]
|
||||
[ProducesResponseType(Status200OK)]
|
||||
@ -319,10 +349,10 @@ public class MangaController(PgsqlContext context, ILog Log) : Controller
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move Manga to different ToLibrary
|
||||
/// Move Obj to different ToFileLibrary
|
||||
/// </summary>
|
||||
/// <param name="MangaId">Manga-ID</param>
|
||||
/// <param name="LibraryId">ToLibrary-Id</param>
|
||||
/// <param name="MangaId">Obj-ID</param>
|
||||
/// <param name="LibraryId">ToFileLibrary-Id</param>
|
||||
/// <response code="202">Folder is going to be moved</response>
|
||||
/// <response code="404">MangaId or LibraryId not found</response>
|
||||
/// <response code="500">Error during Database Operation</response>
|
||||
|
166
API/Controllers/MetadataFetcherController.cs
Normal file
166
API/Controllers/MetadataFetcherController.cs
Normal file
@ -0,0 +1,166 @@
|
||||
using API.Schema;
|
||||
using API.Schema.Contexts;
|
||||
using API.Schema.MetadataFetchers;
|
||||
using Asp.Versioning;
|
||||
using log4net;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using static Microsoft.AspNetCore.Http.StatusCodes;
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
namespace API.Controllers;
|
||||
|
||||
[ApiVersion(2)]
|
||||
[ApiController]
|
||||
[Route("v{v:apiVersion}/[controller]")]
|
||||
public class MetadataFetcherController(PgsqlContext context, ILog Log) : Controller
|
||||
{
|
||||
/// <summary>
|
||||
/// Get all available Connectors (Metadata-Sites)
|
||||
/// </summary>
|
||||
/// <response code="200">Names of Metadata-Fetchers</response>
|
||||
[HttpGet]
|
||||
[ProducesResponseType<string[]>(Status200OK, "application/json")]
|
||||
public IActionResult GetConnectors()
|
||||
{
|
||||
string[] connectors = Tranga.MetadataFetchers.Select(f => f.MetadataFetcherName).ToArray();
|
||||
return Ok(connectors);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all Mangas which have a linked Metadata-Provider
|
||||
/// </summary>
|
||||
/// <response code="200"></response>
|
||||
[HttpGet("Links")]
|
||||
[ProducesResponseType<MetadataEntry>(Status200OK, "application/json")]
|
||||
public IActionResult GetLinkedEntries()
|
||||
{
|
||||
return Ok(context.MetadataEntries.ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Searches Metadata-Provider for Manga-Metadata
|
||||
/// </summary>
|
||||
/// <param name="searchTerm">Instead of using the Manga for search, use a specific term</param>
|
||||
/// <response code="200"></response>
|
||||
/// <response code="400">Metadata-fetcher with Name does not exist</response>
|
||||
/// <response code="404">Manga with ID not found</response>
|
||||
[HttpPost("{MetadataFetcherName}/SearchManga/{MangaId}")]
|
||||
[ProducesResponseType<MetadataSearchResult[]>(Status200OK, "application/json")]
|
||||
[ProducesResponseType(Status400BadRequest)]
|
||||
[ProducesResponseType(Status404NotFound)]
|
||||
public IActionResult SearchMangaMetadata(string MangaId, string MetadataFetcherName, [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)]string? searchTerm = null)
|
||||
{
|
||||
if(context.Mangas.Find(MangaId) is not { } manga)
|
||||
return NotFound();
|
||||
if(Tranga.MetadataFetchers.FirstOrDefault(f => f.MetadataFetcherName == MetadataFetcherName) is not { } fetcher)
|
||||
return BadRequest();
|
||||
|
||||
MetadataSearchResult[] searchResults = searchTerm is null ? fetcher.SearchMetadataEntry(manga) : fetcher.SearchMetadataEntry(searchTerm);
|
||||
return Ok(searchResults);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Links Metadata-Provider using Provider-Specific Identifier to Manga
|
||||
/// </summary>
|
||||
/// <response code="200"></response>
|
||||
/// <response code="400">Metadata-fetcher with Name does not exist</response>
|
||||
/// <response code="404">Manga with ID not found</response>
|
||||
/// <response code="500">Error during Database Operation</response>
|
||||
[HttpPost("{MetadataFetcherName}/Link/{MangaId}")]
|
||||
[ProducesResponseType(Status200OK)]
|
||||
[ProducesResponseType(Status400BadRequest)]
|
||||
[ProducesResponseType(Status404NotFound)]
|
||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||
public IActionResult LinkMangaMetadata(string MangaId, string MetadataFetcherName, [FromBody]string Identifier)
|
||||
{
|
||||
if(context.Mangas.Find(MangaId) is not { } manga)
|
||||
return NotFound();
|
||||
if(Tranga.MetadataFetchers.FirstOrDefault(f => f.MetadataFetcherName == MetadataFetcherName) is not { } fetcher)
|
||||
return BadRequest();
|
||||
MetadataEntry entry = fetcher.CreateMetadataEntry(manga, Identifier);
|
||||
|
||||
try
|
||||
{
|
||||
context.MetadataEntries.Add(entry);
|
||||
context.SaveChanges();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
return StatusCode(500, e.Message);
|
||||
}
|
||||
return Ok();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Un-Links Metadata-Provider using Provider-Specific Identifier to Manga
|
||||
/// </summary>
|
||||
/// <response code="200"></response>
|
||||
/// <response code="400">Metadata-fetcher with Name does not exist</response>
|
||||
/// <response code="404">Manga with ID not found</response>
|
||||
/// <response code="412">No Entry linking Manga and Metadata-Provider found</response>
|
||||
/// <response code="500">Error during Database Operation</response>
|
||||
[HttpPost("{MetadataFetcherName}/Unlink/{MangaId}")]
|
||||
[ProducesResponseType(Status200OK)]
|
||||
[ProducesResponseType(Status400BadRequest)]
|
||||
[ProducesResponseType(Status404NotFound)]
|
||||
[ProducesResponseType<string>(Status412PreconditionFailed, "text/plain")]
|
||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||
public IActionResult UnlinkMangaMetadata(string MangaId, string MetadataFetcherName)
|
||||
{
|
||||
if(context.Mangas.Find(MangaId) is not { } manga)
|
||||
return NotFound();
|
||||
if(Tranga.MetadataFetchers.FirstOrDefault(f => f.MetadataFetcherName == MetadataFetcherName) is not { } fetcher)
|
||||
return BadRequest();
|
||||
MetadataEntry? entry = context.MetadataEntries.FirstOrDefault(e => e.MangaId == MangaId && e.MetadataFetcherName == MetadataFetcherName);
|
||||
if (entry is null)
|
||||
return StatusCode(Status412PreconditionFailed, "No entry found");
|
||||
try
|
||||
{
|
||||
context.MetadataEntries.Remove(entry);
|
||||
context.SaveChanges();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
return StatusCode(500, e.Message);
|
||||
}
|
||||
return Ok();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries linking a Manga to a Metadata-Provider-Site
|
||||
/// </summary>
|
||||
/// <response code="200"></response>
|
||||
/// <response code="400">MetadataFetcher Name is invalid</response>
|
||||
/// <response code="404">Manga has no linked entry with MetadataFetcher</response>
|
||||
/// <response code="500">Error during Database Operation</response>
|
||||
[HttpPost("{MetadataFetcherName}/{MangaId}/UpdateMetadata")]
|
||||
[ProducesResponseType(Status200OK)]
|
||||
[ProducesResponseType(Status400BadRequest)]
|
||||
[ProducesResponseType(Status404NotFound)]
|
||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||
public IActionResult UpdateMetadata(string MangaId, string MetadataFetcherName)
|
||||
{
|
||||
if(Tranga.MetadataFetchers
|
||||
.FirstOrDefault(f =>
|
||||
f.MetadataFetcherName.Equals(MetadataFetcherName, StringComparison.InvariantCultureIgnoreCase)) is not { } fetcher)
|
||||
return BadRequest();
|
||||
MetadataEntry? entry = context.MetadataEntries
|
||||
.FirstOrDefault(e =>
|
||||
e.MangaId == MangaId && e.MetadataFetcherName.Equals(MetadataFetcherName, StringComparison.InvariantCultureIgnoreCase));
|
||||
if (entry is null)
|
||||
return NotFound();
|
||||
try
|
||||
{
|
||||
fetcher.UpdateMetadata(entry, context);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
return StatusCode(500, e.Message);
|
||||
}
|
||||
return Ok();
|
||||
}
|
||||
}
|
@ -95,7 +95,7 @@ public class NotificationConnectorController(NotificationsContext context, ILog
|
||||
|
||||
NotificationConnector gotifyConnector = new NotificationConnector(TokenGen.CreateToken("Gotify"),
|
||||
gotifyData.endpoint,
|
||||
new Dictionary<string, string>() { { "X-Gotify-Key", gotifyData.appToken } },
|
||||
new Dictionary<string, string>() { { "X-Gotify-IDOnConnector", gotifyData.appToken } },
|
||||
"POST",
|
||||
$"{{\"message\": \"%text\", \"title\": \"%title\", \"priority\": {gotifyData.priority}}}");
|
||||
return CreateConnector(gotifyConnector);
|
||||
|
@ -69,18 +69,18 @@ public class QueryController(PgsqlContext context, ILog Log) : Controller
|
||||
/// <response code="200"></response>
|
||||
/// <response code="404">AltTitle with ID not found</response>
|
||||
[HttpGet("AltTitle/{AltTitleId}")]
|
||||
[ProducesResponseType<MangaAltTitle>(Status200OK, "application/json")]
|
||||
[ProducesResponseType<AltTitle>(Status200OK, "application/json")]
|
||||
[ProducesResponseType(Status404NotFound)]
|
||||
public IActionResult GetAltTitle(string AltTitleId)
|
||||
{
|
||||
MangaAltTitle? ret = context.AltTitles.Find(AltTitleId);
|
||||
AltTitle? ret = context.AltTitles.Find(AltTitleId);
|
||||
if (ret is null)
|
||||
return NotFound();
|
||||
return Ok(ret);
|
||||
}*/
|
||||
|
||||
/// <summary>
|
||||
/// Returns all Manga with Tag
|
||||
/// Returns all Obj with Tag
|
||||
/// </summary>
|
||||
/// <param name="Tag"></param>
|
||||
/// <response code="200"></response>
|
||||
|
@ -1,7 +1,5 @@
|
||||
using API.Schema;
|
||||
using API.Schema.Contexts;
|
||||
using API.Schema.Jobs;
|
||||
using API.Schema.MangaConnectors;
|
||||
using Asp.Versioning;
|
||||
using log4net;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
@ -18,7 +16,7 @@ namespace API.Controllers;
|
||||
public class SearchController(PgsqlContext context, ILog Log) : Controller
|
||||
{
|
||||
/// <summary>
|
||||
/// Initiate a search for a Manga on a specific Connector
|
||||
/// Initiate a search for a Obj on a specific Connector
|
||||
/// </summary>
|
||||
/// <param name="MangaConnectorName"></param>
|
||||
/// <param name="Query"></param>
|
||||
@ -38,9 +36,9 @@ public class SearchController(PgsqlContext context, ILog Log) : Controller
|
||||
else if (connector.Enabled is false)
|
||||
return StatusCode(Status406NotAcceptable);
|
||||
|
||||
Manga[] mangas = connector.SearchManga(Query);
|
||||
(Manga, MangaConnectorId<Manga>)[] mangas = connector.SearchManga(Query);
|
||||
List<Manga> retMangas = new();
|
||||
foreach (Manga manga in mangas)
|
||||
foreach ((Manga manga, MangaConnectorId<Manga> mcId) manga in mangas)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -58,7 +56,7 @@ public class SearchController(PgsqlContext context, ILog Log) : Controller
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Search for a known Manga
|
||||
/// Search for a known Obj
|
||||
/// </summary>
|
||||
/// <param name="Query"></param>
|
||||
/// <response code="200"></response>
|
||||
@ -73,12 +71,12 @@ public class SearchController(PgsqlContext context, ILog Log) : Controller
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns Manga from MangaConnector associated with URL
|
||||
/// Returns Obj from MangaConnector associated with URL
|
||||
/// </summary>
|
||||
/// <param name="url">Manga-Page URL</param>
|
||||
/// <param name="url">Obj-Page URL</param>
|
||||
/// <response code="200"></response>
|
||||
/// <response code="300">Multiple connectors found for URL</response>
|
||||
/// <response code="404">Manga not found</response>
|
||||
/// <response code="404">Obj not found</response>
|
||||
/// <response code="500">Error during Database Operation</response>
|
||||
[HttpPost("Url")]
|
||||
[ProducesResponseType<Manga>(Status200OK, "application/json")]
|
||||
@ -104,12 +102,13 @@ public class SearchController(PgsqlContext context, ILog Log) : Controller
|
||||
}
|
||||
}
|
||||
|
||||
private Manga? AddMangaToContext(Manga manga)
|
||||
private Manga? AddMangaToContext((Manga, MangaConnectorId<Manga>) manga) => AddMangaToContext(manga.Item1, manga.Item2, context);
|
||||
|
||||
internal static Manga? AddMangaToContext(Manga addManga, MangaConnectorId<Manga> addMcId, PgsqlContext context)
|
||||
{
|
||||
context.Mangas.Load();
|
||||
context.Authors.Load();
|
||||
context.Tags.Load();
|
||||
context.MangaConnectors.Load();
|
||||
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 =>
|
||||
{
|
||||
@ -120,26 +119,19 @@ public class SearchController(PgsqlContext context, ILog Log) : Controller
|
||||
|
||||
IEnumerable<Author> mergedAuthors = manga.Authors.Select(ma =>
|
||||
{
|
||||
Author? inDb = context.Authors.Find(ma.AuthorId);
|
||||
Author? inDb = context.Authors.Find(ma.Key);
|
||||
return inDb ?? ma;
|
||||
});
|
||||
manga.Authors = mergedAuthors.ToList();
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
if (context.Mangas.Find(manga.MangaId) is { } r)
|
||||
{
|
||||
context.Mangas.Remove(r);
|
||||
context.SaveChanges();
|
||||
}
|
||||
context.Mangas.Add(manga);
|
||||
context.Jobs.Add(new DownloadMangaCoverJob(manga));
|
||||
if(context.MangaConnectorToManga.Find(addMcId.Key) is null)
|
||||
context.MangaConnectorToManga.Add(mcId);
|
||||
context.SaveChanges();
|
||||
}
|
||||
catch (DbUpdateException e)
|
||||
{
|
||||
Log.Error(e);
|
||||
return null;
|
||||
}
|
||||
return manga;
|
||||
|
@ -1,12 +1,10 @@
|
||||
using System.Net.Http.Headers;
|
||||
using API.MangaDownloadClients;
|
||||
using API.MangaDownloadClients;
|
||||
using API.Schema;
|
||||
using API.Schema.Contexts;
|
||||
using API.Schema.Jobs;
|
||||
using Asp.Versioning;
|
||||
using log4net;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using static Microsoft.AspNetCore.Http.StatusCodes;
|
||||
|
||||
@ -210,14 +208,14 @@ public class SettingsController(PgsqlContext context, ILog Log) : Controller
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Placeholders:
|
||||
/// %M Manga Name
|
||||
/// %M Obj Name
|
||||
/// %V Volume
|
||||
/// %C Chapter
|
||||
/// %T Title
|
||||
/// %A Author (first in list)
|
||||
/// %I Chapter Internal ID
|
||||
/// %i Manga Internal ID
|
||||
/// %Y Year (Manga)
|
||||
/// %i Obj Internal ID
|
||||
/// %Y Year (Obj)
|
||||
///
|
||||
/// ?_(...) replace _ with a value from above:
|
||||
/// Everything inside the braces will only be added if the value of %_ is not null
|
||||
@ -235,14 +233,14 @@ public class SettingsController(PgsqlContext context, ILog Log) : Controller
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Placeholders:
|
||||
/// %M Manga Name
|
||||
/// %M Obj Name
|
||||
/// %V Volume
|
||||
/// %C Chapter
|
||||
/// %T Title
|
||||
/// %A Author (first in list)
|
||||
/// %I Chapter Internal ID
|
||||
/// %i Manga Internal ID
|
||||
/// %Y Year (Manga)
|
||||
/// %i Obj Internal ID
|
||||
/// %Y Year (Obj)
|
||||
///
|
||||
/// ?_(...) replace _ with a value from above:
|
||||
/// Everything inside the braces will only be added if the value of %_ is not null
|
||||
@ -271,7 +269,7 @@ public class SettingsController(PgsqlContext context, ILog Log) : Controller
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a UpdateCoverJob for all Manga
|
||||
/// Creates a UpdateCoverJob for all Obj
|
||||
/// </summary>
|
||||
/// <response code="200">Array of JobIds</response>
|
||||
/// <response code="500">Error during Database Operation</response>
|
||||
@ -285,7 +283,7 @@ public class SettingsController(PgsqlContext context, ILog Log) : Controller
|
||||
Tranga.RemoveStaleFiles(context);
|
||||
List<UpdateCoverJob> newJobs = context.Mangas.ToList().Select(m => new UpdateCoverJob(m, 0)).ToList();
|
||||
context.Jobs.AddRange(newJobs);
|
||||
return Ok(newJobs.Select(j => j.JobId));
|
||||
return Ok(newJobs.Select(j => j.Key));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
@ -43,7 +43,7 @@ internal class ChromiumDownloadClient : DownloadClient
|
||||
{
|
||||
Thread.Sleep(TimeSpan.FromHours(1));
|
||||
Log.Debug("Removing stale pages");
|
||||
foreach ((IPage? key, DateTime value) in _openPages.Where(kv => kv.Value.Subtract(DateTime.Now) > TimeSpan.FromHours(1)))
|
||||
foreach ((IPage key, DateTime _) in _openPages.Where(kv => kv.Value.Subtract(DateTime.Now) > TimeSpan.FromHours(1)))
|
||||
{
|
||||
Log.Debug($"Closing {key.Url}");
|
||||
key.CloseAsync().Wait();
|
||||
|
@ -172,7 +172,7 @@ public class FlareSolverrDownloadClient : DownloadClient
|
||||
jsonString = pre.InnerText;
|
||||
return true;
|
||||
}
|
||||
catch (JsonReaderException)
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
788
API/Migrations/pgsql/20250628204956_AddMAL.Designer.cs
generated
Normal file
788
API/Migrations/pgsql/20250628204956_AddMAL.Designer.cs
generated
Normal file
@ -0,0 +1,788 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using API.Schema.Contexts;
|
||||
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.pgsql
|
||||
{
|
||||
[DbContext(typeof(PgsqlContext))]
|
||||
[Migration("20250628204956_AddMAL")]
|
||||
partial class AddMAL
|
||||
{
|
||||
/// <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.Author", b =>
|
||||
{
|
||||
b.Property<string>("AuthorId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("AuthorName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b.HasKey("AuthorId");
|
||||
|
||||
b.ToTable("Authors");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Chapter", b =>
|
||||
{
|
||||
b.Property<string>("ChapterId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
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>("IdOnConnectorSite")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("ParentMangaId")
|
||||
.IsRequired()
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("Url")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)");
|
||||
|
||||
b.Property<int?>("VolumeNumber")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("ChapterId");
|
||||
|
||||
b.HasIndex("ParentMangaId");
|
||||
|
||||
b.ToTable("Chapters");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.Job", b =>
|
||||
{
|
||||
b.Property<string>("JobId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<bool>("Enabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<byte>("JobType")
|
||||
.HasColumnType("smallint");
|
||||
|
||||
b.Property<DateTime>("LastExecution")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("ParentJobId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<decimal>("RecurrenceMs")
|
||||
.HasColumnType("numeric(20,0)");
|
||||
|
||||
b.Property<byte>("state")
|
||||
.HasColumnType("smallint");
|
||||
|
||||
b.HasKey("JobId");
|
||||
|
||||
b.HasIndex("ParentJobId");
|
||||
|
||||
b.ToTable("Jobs");
|
||||
|
||||
b.HasDiscriminator<byte>("JobType");
|
||||
|
||||
b.UseTphMappingStrategy();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.LocalLibrary", b =>
|
||||
{
|
||||
b.Property<string>("LocalLibraryId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("BasePath")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("LibraryName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)");
|
||||
|
||||
b.HasKey("LocalLibraryId");
|
||||
|
||||
b.ToTable("LocalLibraries");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Manga", b =>
|
||||
{
|
||||
b.Property<string>("MangaId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
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<string>("IdOnConnectorSite")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<float>("IgnoreChaptersBefore")
|
||||
.HasColumnType("real");
|
||||
|
||||
b.Property<string>("LibraryId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("MangaConnectorName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)");
|
||||
|
||||
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<string>("WebsiteUrl")
|
||||
.IsRequired()
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)");
|
||||
|
||||
b.Property<long?>("Year")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.HasKey("MangaId");
|
||||
|
||||
b.HasIndex("LibraryId");
|
||||
|
||||
b.HasIndex("MangaConnectorName");
|
||||
|
||||
b.ToTable("Mangas");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.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.MangaTag", b =>
|
||||
{
|
||||
b.Property<string>("Tag")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasKey("Tag");
|
||||
|
||||
b.ToTable("Tags");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MetadataFetchers.MetadataEntry", b =>
|
||||
{
|
||||
b.Property<string>("MangaId")
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("MetadataFetcherName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Identifier")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("MangaId", "MetadataFetcherName");
|
||||
|
||||
b.HasIndex("MetadataFetcherName");
|
||||
|
||||
b.ToTable("MetadataEntries");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MetadataFetchers.MetadataFetcher", b =>
|
||||
{
|
||||
b.Property<string>("MetadataFetcherName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("MetadataEntry")
|
||||
.IsRequired()
|
||||
.HasMaxLength(21)
|
||||
.HasColumnType("character varying(21)");
|
||||
|
||||
b.HasKey("MetadataFetcherName");
|
||||
|
||||
b.ToTable("MetadataFetcher");
|
||||
|
||||
b.HasDiscriminator<string>("MetadataEntry").HasValue("MetadataFetcher");
|
||||
|
||||
b.UseTphMappingStrategy();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AuthorToManga", b =>
|
||||
{
|
||||
b.Property<string>("AuthorIds")
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("MangaIds")
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasKey("AuthorIds", "MangaIds");
|
||||
|
||||
b.HasIndex("MangaIds");
|
||||
|
||||
b.ToTable("AuthorToManga");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("JobJob", b =>
|
||||
{
|
||||
b.Property<string>("DependsOnJobsJobId")
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("JobId")
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasKey("DependsOnJobsJobId", "JobId");
|
||||
|
||||
b.HasIndex("JobId");
|
||||
|
||||
b.ToTable("JobJob");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MangaTagToManga", b =>
|
||||
{
|
||||
b.Property<string>("MangaTagIds")
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("MangaIds")
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasKey("MangaTagIds", "MangaIds");
|
||||
|
||||
b.HasIndex("MangaIds");
|
||||
|
||||
b.ToTable("MangaTagToManga");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.DownloadAvailableChaptersJob", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.Jobs.Job");
|
||||
|
||||
b.Property<string>("MangaId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasIndex("MangaId");
|
||||
|
||||
b.ToTable("Jobs", t =>
|
||||
{
|
||||
t.Property("MangaId")
|
||||
.HasColumnName("DownloadAvailableChaptersJob_MangaId");
|
||||
});
|
||||
|
||||
b.HasDiscriminator().HasValue((byte)1);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.DownloadMangaCoverJob", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.Jobs.Job");
|
||||
|
||||
b.Property<string>("MangaId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasIndex("MangaId");
|
||||
|
||||
b.HasDiscriminator().HasValue((byte)4);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.DownloadSingleChapterJob", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.Jobs.Job");
|
||||
|
||||
b.Property<string>("ChapterId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasIndex("ChapterId");
|
||||
|
||||
b.HasDiscriminator().HasValue((byte)0);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.MoveFileOrFolderJob", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.Jobs.Job");
|
||||
|
||||
b.Property<string>("FromLocation")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("ToLocation")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.HasDiscriminator().HasValue((byte)3);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.MoveMangaLibraryJob", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.Jobs.Job");
|
||||
|
||||
b.Property<string>("MangaId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("ToLibraryId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasIndex("MangaId");
|
||||
|
||||
b.HasIndex("ToLibraryId");
|
||||
|
||||
b.ToTable("Jobs", t =>
|
||||
{
|
||||
t.Property("MangaId")
|
||||
.HasColumnName("MoveMangaLibraryJob_MangaId");
|
||||
});
|
||||
|
||||
b.HasDiscriminator().HasValue((byte)7);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.RetrieveChaptersJob", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.Jobs.Job");
|
||||
|
||||
b.Property<string>("Language")
|
||||
.IsRequired()
|
||||
.HasMaxLength(8)
|
||||
.HasColumnType("character varying(8)");
|
||||
|
||||
b.Property<string>("MangaId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasIndex("MangaId");
|
||||
|
||||
b.ToTable("Jobs", t =>
|
||||
{
|
||||
t.Property("MangaId")
|
||||
.HasColumnName("RetrieveChaptersJob_MangaId");
|
||||
});
|
||||
|
||||
b.HasDiscriminator().HasValue((byte)5);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.UpdateChaptersDownloadedJob", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.Jobs.Job");
|
||||
|
||||
b.Property<string>("MangaId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasIndex("MangaId");
|
||||
|
||||
b.ToTable("Jobs", t =>
|
||||
{
|
||||
t.Property("MangaId")
|
||||
.HasColumnName("UpdateChaptersDownloadedJob_MangaId");
|
||||
});
|
||||
|
||||
b.HasDiscriminator().HasValue((byte)6);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.UpdateCoverJob", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.Jobs.Job");
|
||||
|
||||
b.Property<string>("MangaId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasIndex("MangaId");
|
||||
|
||||
b.ToTable("Jobs", t =>
|
||||
{
|
||||
t.Property("MangaId")
|
||||
.HasColumnName("UpdateCoverJob_MangaId");
|
||||
});
|
||||
|
||||
b.HasDiscriminator().HasValue((byte)9);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaConnectors.ComickIo", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
||||
|
||||
b.HasDiscriminator().HasValue("ComickIo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaConnectors.Global", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
||||
|
||||
b.HasDiscriminator().HasValue("Global");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaConnectors.MangaDex", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
||||
|
||||
b.HasDiscriminator().HasValue("MangaDex");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MetadataFetchers.MyAnimeList", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.MetadataFetchers.MetadataFetcher");
|
||||
|
||||
b.HasDiscriminator().HasValue("MyAnimeList");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Chapter", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Manga", "ParentManga")
|
||||
.WithMany("Chapters")
|
||||
.HasForeignKey("ParentMangaId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ParentManga");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.Job", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Jobs.Job", "ParentJob")
|
||||
.WithMany()
|
||||
.HasForeignKey("ParentJobId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.Navigation("ParentJob");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Manga", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.LocalLibrary", "Library")
|
||||
.WithMany()
|
||||
.HasForeignKey("LibraryId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.HasOne("API.Schema.MangaConnectors.MangaConnector", "MangaConnector")
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaConnectorName")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.OwnsMany("API.Schema.Link", "Links", b1 =>
|
||||
{
|
||||
b1.Property<string>("LinkId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
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>("MangaId")
|
||||
.IsRequired()
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b1.HasKey("LinkId");
|
||||
|
||||
b1.HasIndex("MangaId");
|
||||
|
||||
b1.ToTable("Link");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("MangaId");
|
||||
});
|
||||
|
||||
b.OwnsMany("API.Schema.MangaAltTitle", "AltTitles", b1 =>
|
||||
{
|
||||
b1.Property<string>("AltTitleId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b1.Property<string>("Language")
|
||||
.IsRequired()
|
||||
.HasMaxLength(8)
|
||||
.HasColumnType("character varying(8)");
|
||||
|
||||
b1.Property<string>("MangaId")
|
||||
.IsRequired()
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b1.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b1.HasKey("AltTitleId");
|
||||
|
||||
b1.HasIndex("MangaId");
|
||||
|
||||
b1.ToTable("MangaAltTitle");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("MangaId");
|
||||
});
|
||||
|
||||
b.Navigation("AltTitles");
|
||||
|
||||
b.Navigation("Library");
|
||||
|
||||
b.Navigation("Links");
|
||||
|
||||
b.Navigation("MangaConnector");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MetadataFetchers.MetadataEntry", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Manga", "Manga")
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("API.Schema.MetadataFetchers.MetadataFetcher", "MetadataFetcher")
|
||||
.WithMany()
|
||||
.HasForeignKey("MetadataFetcherName")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Manga");
|
||||
|
||||
b.Navigation("MetadataFetcher");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AuthorToManga", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Author", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("AuthorIds")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("API.Schema.Manga", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaIds")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("JobJob", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Jobs.Job", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("DependsOnJobsJobId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("API.Schema.Jobs.Job", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("JobId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MangaTagToManga", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Manga", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaIds")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("API.Schema.MangaTag", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaTagIds")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.DownloadAvailableChaptersJob", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Manga", "Manga")
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Manga");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.DownloadMangaCoverJob", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Manga", "Manga")
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Manga");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.DownloadSingleChapterJob", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Chapter", "Chapter")
|
||||
.WithMany()
|
||||
.HasForeignKey("ChapterId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Chapter");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.MoveMangaLibraryJob", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Manga", "Manga")
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("API.Schema.LocalLibrary", "ToLibrary")
|
||||
.WithMany()
|
||||
.HasForeignKey("ToLibraryId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Manga");
|
||||
|
||||
b.Navigation("ToLibrary");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.RetrieveChaptersJob", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Manga", "Manga")
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Manga");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.UpdateChaptersDownloadedJob", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Manga", "Manga")
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Manga");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.UpdateCoverJob", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Manga", "Manga")
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Manga");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Manga", b =>
|
||||
{
|
||||
b.Navigation("Chapters");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
66
API/Migrations/pgsql/20250628204956_AddMAL.cs
Normal file
66
API/Migrations/pgsql/20250628204956_AddMAL.cs
Normal file
@ -0,0 +1,66 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace API.Migrations.pgsql
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddMAL : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "MetadataFetcher",
|
||||
columns: table => new
|
||||
{
|
||||
MetadataFetcherName = 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.MetadataFetcherName);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "MetadataEntries",
|
||||
columns: table => new
|
||||
{
|
||||
MangaId = table.Column<string>(type: "character varying(64)", nullable: false),
|
||||
MetadataFetcherName = table.Column<string>(type: "text", nullable: false),
|
||||
Identifier = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_MetadataEntries", x => new { x.MangaId, x.MetadataFetcherName });
|
||||
table.ForeignKey(
|
||||
name: "FK_MetadataEntries_Mangas_MangaId",
|
||||
column: x => x.MangaId,
|
||||
principalTable: "Mangas",
|
||||
principalColumn: "MangaId",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_MetadataEntries_MetadataFetcher_MetadataFetcherName",
|
||||
column: x => x.MetadataFetcherName,
|
||||
principalTable: "MetadataFetcher",
|
||||
principalColumn: "MetadataFetcherName",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_MetadataEntries_MetadataFetcherName",
|
||||
table: "MetadataEntries",
|
||||
column: "MetadataFetcherName");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "MetadataEntries");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "MetadataFetcher");
|
||||
}
|
||||
}
|
||||
}
|
788
API/Migrations/pgsql/20250629184056_MetadataEntry-PrimaryKeyChange.Designer.cs
generated
Normal file
788
API/Migrations/pgsql/20250629184056_MetadataEntry-PrimaryKeyChange.Designer.cs
generated
Normal file
@ -0,0 +1,788 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using API.Schema.Contexts;
|
||||
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.pgsql
|
||||
{
|
||||
[DbContext(typeof(PgsqlContext))]
|
||||
[Migration("20250629184056_MetadataEntry-PrimaryKeyChange")]
|
||||
partial class MetadataEntryPrimaryKeyChange
|
||||
{
|
||||
/// <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.Author", b =>
|
||||
{
|
||||
b.Property<string>("AuthorId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("AuthorName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b.HasKey("AuthorId");
|
||||
|
||||
b.ToTable("Authors");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Chapter", b =>
|
||||
{
|
||||
b.Property<string>("ChapterId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
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>("IdOnConnectorSite")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("ParentMangaId")
|
||||
.IsRequired()
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("Url")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)");
|
||||
|
||||
b.Property<int?>("VolumeNumber")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("ChapterId");
|
||||
|
||||
b.HasIndex("ParentMangaId");
|
||||
|
||||
b.ToTable("Chapters");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.Job", b =>
|
||||
{
|
||||
b.Property<string>("JobId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<bool>("Enabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<byte>("JobType")
|
||||
.HasColumnType("smallint");
|
||||
|
||||
b.Property<DateTime>("LastExecution")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("ParentJobId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<decimal>("RecurrenceMs")
|
||||
.HasColumnType("numeric(20,0)");
|
||||
|
||||
b.Property<byte>("state")
|
||||
.HasColumnType("smallint");
|
||||
|
||||
b.HasKey("JobId");
|
||||
|
||||
b.HasIndex("ParentJobId");
|
||||
|
||||
b.ToTable("Jobs");
|
||||
|
||||
b.HasDiscriminator<byte>("JobType");
|
||||
|
||||
b.UseTphMappingStrategy();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.LocalLibrary", b =>
|
||||
{
|
||||
b.Property<string>("LocalLibraryId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("BasePath")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("LibraryName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)");
|
||||
|
||||
b.HasKey("LocalLibraryId");
|
||||
|
||||
b.ToTable("LocalLibraries");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Manga", b =>
|
||||
{
|
||||
b.Property<string>("MangaId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
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<string>("IdOnConnectorSite")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<float>("IgnoreChaptersBefore")
|
||||
.HasColumnType("real");
|
||||
|
||||
b.Property<string>("LibraryId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("MangaConnectorName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)");
|
||||
|
||||
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<string>("WebsiteUrl")
|
||||
.IsRequired()
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)");
|
||||
|
||||
b.Property<long?>("Year")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.HasKey("MangaId");
|
||||
|
||||
b.HasIndex("LibraryId");
|
||||
|
||||
b.HasIndex("MangaConnectorName");
|
||||
|
||||
b.ToTable("Mangas");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.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.MangaTag", b =>
|
||||
{
|
||||
b.Property<string>("Tag")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasKey("Tag");
|
||||
|
||||
b.ToTable("Tags");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MetadataFetchers.MetadataEntry", b =>
|
||||
{
|
||||
b.Property<string>("MetadataFetcherName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Identifier")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("MangaId")
|
||||
.IsRequired()
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasKey("MetadataFetcherName", "Identifier");
|
||||
|
||||
b.HasIndex("MangaId");
|
||||
|
||||
b.ToTable("MetadataEntries");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MetadataFetchers.MetadataFetcher", b =>
|
||||
{
|
||||
b.Property<string>("MetadataFetcherName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("MetadataEntry")
|
||||
.IsRequired()
|
||||
.HasMaxLength(21)
|
||||
.HasColumnType("character varying(21)");
|
||||
|
||||
b.HasKey("MetadataFetcherName");
|
||||
|
||||
b.ToTable("MetadataFetcher");
|
||||
|
||||
b.HasDiscriminator<string>("MetadataEntry").HasValue("MetadataFetcher");
|
||||
|
||||
b.UseTphMappingStrategy();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AuthorToManga", b =>
|
||||
{
|
||||
b.Property<string>("AuthorIds")
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("MangaIds")
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasKey("AuthorIds", "MangaIds");
|
||||
|
||||
b.HasIndex("MangaIds");
|
||||
|
||||
b.ToTable("AuthorToManga");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("JobJob", b =>
|
||||
{
|
||||
b.Property<string>("DependsOnJobsJobId")
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("JobId")
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasKey("DependsOnJobsJobId", "JobId");
|
||||
|
||||
b.HasIndex("JobId");
|
||||
|
||||
b.ToTable("JobJob");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MangaTagToManga", b =>
|
||||
{
|
||||
b.Property<string>("MangaTagIds")
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("MangaIds")
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasKey("MangaTagIds", "MangaIds");
|
||||
|
||||
b.HasIndex("MangaIds");
|
||||
|
||||
b.ToTable("MangaTagToManga");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.DownloadAvailableChaptersJob", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.Jobs.Job");
|
||||
|
||||
b.Property<string>("MangaId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasIndex("MangaId");
|
||||
|
||||
b.ToTable("Jobs", t =>
|
||||
{
|
||||
t.Property("MangaId")
|
||||
.HasColumnName("DownloadAvailableChaptersJob_MangaId");
|
||||
});
|
||||
|
||||
b.HasDiscriminator().HasValue((byte)1);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.DownloadMangaCoverJob", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.Jobs.Job");
|
||||
|
||||
b.Property<string>("MangaId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasIndex("MangaId");
|
||||
|
||||
b.HasDiscriminator().HasValue((byte)4);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.DownloadSingleChapterJob", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.Jobs.Job");
|
||||
|
||||
b.Property<string>("ChapterId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasIndex("ChapterId");
|
||||
|
||||
b.HasDiscriminator().HasValue((byte)0);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.MoveFileOrFolderJob", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.Jobs.Job");
|
||||
|
||||
b.Property<string>("FromLocation")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("ToLocation")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.HasDiscriminator().HasValue((byte)3);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.MoveMangaLibraryJob", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.Jobs.Job");
|
||||
|
||||
b.Property<string>("MangaId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("ToLibraryId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasIndex("MangaId");
|
||||
|
||||
b.HasIndex("ToLibraryId");
|
||||
|
||||
b.ToTable("Jobs", t =>
|
||||
{
|
||||
t.Property("MangaId")
|
||||
.HasColumnName("MoveMangaLibraryJob_MangaId");
|
||||
});
|
||||
|
||||
b.HasDiscriminator().HasValue((byte)7);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.RetrieveChaptersJob", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.Jobs.Job");
|
||||
|
||||
b.Property<string>("Language")
|
||||
.IsRequired()
|
||||
.HasMaxLength(8)
|
||||
.HasColumnType("character varying(8)");
|
||||
|
||||
b.Property<string>("MangaId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasIndex("MangaId");
|
||||
|
||||
b.ToTable("Jobs", t =>
|
||||
{
|
||||
t.Property("MangaId")
|
||||
.HasColumnName("RetrieveChaptersJob_MangaId");
|
||||
});
|
||||
|
||||
b.HasDiscriminator().HasValue((byte)5);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.UpdateChaptersDownloadedJob", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.Jobs.Job");
|
||||
|
||||
b.Property<string>("MangaId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasIndex("MangaId");
|
||||
|
||||
b.ToTable("Jobs", t =>
|
||||
{
|
||||
t.Property("MangaId")
|
||||
.HasColumnName("UpdateChaptersDownloadedJob_MangaId");
|
||||
});
|
||||
|
||||
b.HasDiscriminator().HasValue((byte)6);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.UpdateCoverJob", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.Jobs.Job");
|
||||
|
||||
b.Property<string>("MangaId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasIndex("MangaId");
|
||||
|
||||
b.ToTable("Jobs", t =>
|
||||
{
|
||||
t.Property("MangaId")
|
||||
.HasColumnName("UpdateCoverJob_MangaId");
|
||||
});
|
||||
|
||||
b.HasDiscriminator().HasValue((byte)9);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaConnectors.ComickIo", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
||||
|
||||
b.HasDiscriminator().HasValue("ComickIo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaConnectors.Global", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
||||
|
||||
b.HasDiscriminator().HasValue("Global");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaConnectors.MangaDex", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
||||
|
||||
b.HasDiscriminator().HasValue("MangaDex");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MetadataFetchers.MyAnimeList", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.MetadataFetchers.MetadataFetcher");
|
||||
|
||||
b.HasDiscriminator().HasValue("MyAnimeList");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Chapter", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Manga", "ParentManga")
|
||||
.WithMany("Chapters")
|
||||
.HasForeignKey("ParentMangaId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ParentManga");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.Job", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Jobs.Job", "ParentJob")
|
||||
.WithMany()
|
||||
.HasForeignKey("ParentJobId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.Navigation("ParentJob");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Manga", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.LocalLibrary", "Library")
|
||||
.WithMany()
|
||||
.HasForeignKey("LibraryId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.HasOne("API.Schema.MangaConnectors.MangaConnector", "MangaConnector")
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaConnectorName")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.OwnsMany("API.Schema.Link", "Links", b1 =>
|
||||
{
|
||||
b1.Property<string>("LinkId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
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>("MangaId")
|
||||
.IsRequired()
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b1.HasKey("LinkId");
|
||||
|
||||
b1.HasIndex("MangaId");
|
||||
|
||||
b1.ToTable("Link");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("MangaId");
|
||||
});
|
||||
|
||||
b.OwnsMany("API.Schema.MangaAltTitle", "AltTitles", b1 =>
|
||||
{
|
||||
b1.Property<string>("AltTitleId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b1.Property<string>("Language")
|
||||
.IsRequired()
|
||||
.HasMaxLength(8)
|
||||
.HasColumnType("character varying(8)");
|
||||
|
||||
b1.Property<string>("MangaId")
|
||||
.IsRequired()
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b1.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b1.HasKey("AltTitleId");
|
||||
|
||||
b1.HasIndex("MangaId");
|
||||
|
||||
b1.ToTable("MangaAltTitle");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("MangaId");
|
||||
});
|
||||
|
||||
b.Navigation("AltTitles");
|
||||
|
||||
b.Navigation("Library");
|
||||
|
||||
b.Navigation("Links");
|
||||
|
||||
b.Navigation("MangaConnector");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MetadataFetchers.MetadataEntry", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Manga", "Manga")
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("API.Schema.MetadataFetchers.MetadataFetcher", "MetadataFetcher")
|
||||
.WithMany()
|
||||
.HasForeignKey("MetadataFetcherName")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Manga");
|
||||
|
||||
b.Navigation("MetadataFetcher");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AuthorToManga", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Author", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("AuthorIds")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("API.Schema.Manga", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaIds")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("JobJob", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Jobs.Job", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("DependsOnJobsJobId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("API.Schema.Jobs.Job", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("JobId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MangaTagToManga", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Manga", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaIds")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("API.Schema.MangaTag", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaTagIds")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.DownloadAvailableChaptersJob", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Manga", "Manga")
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Manga");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.DownloadMangaCoverJob", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Manga", "Manga")
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Manga");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.DownloadSingleChapterJob", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Chapter", "Chapter")
|
||||
.WithMany()
|
||||
.HasForeignKey("ChapterId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Chapter");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.MoveMangaLibraryJob", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Manga", "Manga")
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("API.Schema.LocalLibrary", "ToLibrary")
|
||||
.WithMany()
|
||||
.HasForeignKey("ToLibraryId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Manga");
|
||||
|
||||
b.Navigation("ToLibrary");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.RetrieveChaptersJob", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Manga", "Manga")
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Manga");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.UpdateChaptersDownloadedJob", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Manga", "Manga")
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Manga");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.UpdateCoverJob", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Manga", "Manga")
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Manga");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Manga", b =>
|
||||
{
|
||||
b.Navigation("Chapters");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace API.Migrations.pgsql
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class MetadataEntryPrimaryKeyChange : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropPrimaryKey(
|
||||
name: "PK_MetadataEntries",
|
||||
table: "MetadataEntries");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_MetadataEntries_MetadataFetcherName",
|
||||
table: "MetadataEntries");
|
||||
|
||||
migrationBuilder.AddPrimaryKey(
|
||||
name: "PK_MetadataEntries",
|
||||
table: "MetadataEntries",
|
||||
columns: new[] { "MetadataFetcherName", "Identifier" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_MetadataEntries_MangaId",
|
||||
table: "MetadataEntries",
|
||||
column: "MangaId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropPrimaryKey(
|
||||
name: "PK_MetadataEntries",
|
||||
table: "MetadataEntries");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_MetadataEntries_MangaId",
|
||||
table: "MetadataEntries");
|
||||
|
||||
migrationBuilder.AddPrimaryKey(
|
||||
name: "PK_MetadataEntries",
|
||||
table: "MetadataEntries",
|
||||
columns: new[] { "MangaId", "MetadataFetcherName" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_MetadataEntries_MetadataFetcherName",
|
||||
table: "MetadataEntries",
|
||||
column: "MetadataFetcherName");
|
||||
}
|
||||
}
|
||||
}
|
795
API/Migrations/pgsql/20250630182650_OofV2.1.Designer.cs
generated
Normal file
795
API/Migrations/pgsql/20250630182650_OofV2.1.Designer.cs
generated
Normal file
@ -0,0 +1,795 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using API.Schema.Contexts;
|
||||
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.pgsql
|
||||
{
|
||||
[DbContext(typeof(PgsqlContext))]
|
||||
[Migration("20250630182650_OofV2.1")]
|
||||
partial class OofV21
|
||||
{
|
||||
/// <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.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.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.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("LocalLibraries");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.Job", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool>("Enabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<byte>("JobType")
|
||||
.HasColumnType("smallint");
|
||||
|
||||
b.Property<DateTime>("LastExecution")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("ParentJobId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<decimal>("RecurrenceMs")
|
||||
.HasColumnType("numeric(20,0)");
|
||||
|
||||
b.Property<byte>("state")
|
||||
.HasColumnType("smallint");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.HasIndex("ParentJobId");
|
||||
|
||||
b.ToTable("Jobs");
|
||||
|
||||
b.HasDiscriminator<byte>("JobType");
|
||||
|
||||
b.UseTphMappingStrategy();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.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.MangaConnectorId<API.Schema.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<string>("WebsiteUrl")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.HasIndex("MangaConnectorName");
|
||||
|
||||
b.HasIndex("ObjId");
|
||||
|
||||
b.ToTable("MangaConnectorToChapter");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaConnectorId<API.Schema.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<string>("WebsiteUrl")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.HasIndex("MangaConnectorName");
|
||||
|
||||
b.HasIndex("ObjId");
|
||||
|
||||
b.ToTable("MangaConnectorToManga");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.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.MangaTag", b =>
|
||||
{
|
||||
b.Property<string>("Tag")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasKey("Tag");
|
||||
|
||||
b.ToTable("Tags");
|
||||
});
|
||||
|
||||
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("JobJob", b =>
|
||||
{
|
||||
b.Property<string>("DependsOnJobsKey")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("JobKey")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("DependsOnJobsKey", "JobKey");
|
||||
|
||||
b.HasIndex("JobKey");
|
||||
|
||||
b.ToTable("JobJob");
|
||||
});
|
||||
|
||||
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.Jobs.DownloadAvailableChaptersJob", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.Jobs.Job");
|
||||
|
||||
b.Property<string>("MangaId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasIndex("MangaId");
|
||||
|
||||
b.ToTable("Jobs", t =>
|
||||
{
|
||||
t.Property("MangaId")
|
||||
.HasColumnName("DownloadAvailableChaptersJob_MangaId");
|
||||
});
|
||||
|
||||
b.HasDiscriminator().HasValue((byte)1);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.DownloadMangaCoverJob", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.Jobs.Job");
|
||||
|
||||
b.Property<string>("MangaId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasIndex("MangaId");
|
||||
|
||||
b.HasDiscriminator().HasValue((byte)4);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.DownloadSingleChapterJob", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.Jobs.Job");
|
||||
|
||||
b.Property<string>("ChapterId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasIndex("ChapterId");
|
||||
|
||||
b.HasDiscriminator().HasValue((byte)0);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.MoveFileOrFolderJob", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.Jobs.Job");
|
||||
|
||||
b.Property<string>("FromLocation")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("ToLocation")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.HasDiscriminator().HasValue((byte)3);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.MoveMangaLibraryJob", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.Jobs.Job");
|
||||
|
||||
b.Property<string>("MangaId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("ToLibraryId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasIndex("MangaId");
|
||||
|
||||
b.HasIndex("ToLibraryId");
|
||||
|
||||
b.ToTable("Jobs", t =>
|
||||
{
|
||||
t.Property("MangaId")
|
||||
.HasColumnName("MoveMangaLibraryJob_MangaId");
|
||||
});
|
||||
|
||||
b.HasDiscriminator().HasValue((byte)7);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.RetrieveChaptersJob", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.Jobs.Job");
|
||||
|
||||
b.Property<string>("Language")
|
||||
.IsRequired()
|
||||
.HasMaxLength(8)
|
||||
.HasColumnType("character varying(8)");
|
||||
|
||||
b.Property<string>("MangaId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasIndex("MangaId");
|
||||
|
||||
b.ToTable("Jobs", t =>
|
||||
{
|
||||
t.Property("MangaId")
|
||||
.HasColumnName("RetrieveChaptersJob_MangaId");
|
||||
});
|
||||
|
||||
b.HasDiscriminator().HasValue((byte)5);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.UpdateChaptersDownloadedJob", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.Jobs.Job");
|
||||
|
||||
b.Property<string>("MangaId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasIndex("MangaId");
|
||||
|
||||
b.ToTable("Jobs", t =>
|
||||
{
|
||||
t.Property("MangaId")
|
||||
.HasColumnName("UpdateChaptersDownloadedJob_MangaId");
|
||||
});
|
||||
|
||||
b.HasDiscriminator().HasValue((byte)6);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.UpdateCoverJob", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.Jobs.Job");
|
||||
|
||||
b.Property<string>("MangaId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasIndex("MangaId");
|
||||
|
||||
b.ToTable("Jobs", t =>
|
||||
{
|
||||
t.Property("MangaId")
|
||||
.HasColumnName("UpdateCoverJob_MangaId");
|
||||
});
|
||||
|
||||
b.HasDiscriminator().HasValue((byte)9);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaConnectors.ComickIo", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
||||
|
||||
b.HasDiscriminator().HasValue("ComickIo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaConnectors.Global", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
||||
|
||||
b.HasDiscriminator().HasValue("Global");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaConnectors.MangaDex", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
||||
|
||||
b.HasDiscriminator().HasValue("MangaDex");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Chapter", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Manga", "ParentManga")
|
||||
.WithMany("Chapters")
|
||||
.HasForeignKey("ParentMangaId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ParentManga");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.Job", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Jobs.Job", "ParentJob")
|
||||
.WithMany()
|
||||
.HasForeignKey("ParentJobId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.Navigation("ParentJob");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Manga", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.FileLibrary", "Library")
|
||||
.WithMany()
|
||||
.HasForeignKey("LibraryId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.OwnsMany("API.Schema.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.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.MangaConnectorId<API.Schema.Chapter>", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.MangaConnectors.MangaConnector", "MangaConnector")
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaConnectorName")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("API.Schema.Chapter", "Obj")
|
||||
.WithMany("MangaConnectorIds")
|
||||
.HasForeignKey("ObjId")
|
||||
.OnDelete(DeleteBehavior.NoAction)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("MangaConnector");
|
||||
|
||||
b.Navigation("Obj");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaConnectorId<API.Schema.Manga>", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.MangaConnectors.MangaConnector", "MangaConnector")
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaConnectorName")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("API.Schema.Manga", "Obj")
|
||||
.WithMany("MangaConnectorIds")
|
||||
.HasForeignKey("ObjId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("MangaConnector");
|
||||
|
||||
b.Navigation("Obj");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AuthorToManga", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Author", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("AuthorIds")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("API.Schema.Manga", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaIds")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("JobJob", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Jobs.Job", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("DependsOnJobsKey")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("API.Schema.Jobs.Job", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("JobKey")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MangaTagToManga", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Manga", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaIds")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("API.Schema.MangaTag", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaTagIds")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.DownloadAvailableChaptersJob", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Manga", "Manga")
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Manga");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.DownloadMangaCoverJob", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Manga", "Manga")
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Manga");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.DownloadSingleChapterJob", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Chapter", "Chapter")
|
||||
.WithMany()
|
||||
.HasForeignKey("ChapterId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Chapter");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.MoveMangaLibraryJob", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Manga", "Manga")
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("API.Schema.FileLibrary", "ToFileLibrary")
|
||||
.WithMany()
|
||||
.HasForeignKey("ToLibraryId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Manga");
|
||||
|
||||
b.Navigation("ToFileLibrary");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.RetrieveChaptersJob", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Manga", "Manga")
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Manga");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.UpdateChaptersDownloadedJob", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Manga", "Manga")
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Manga");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.UpdateCoverJob", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Manga", "Manga")
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Manga");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Chapter", b =>
|
||||
{
|
||||
b.Navigation("MangaConnectorIds");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Manga", b =>
|
||||
{
|
||||
b.Navigation("Chapters");
|
||||
|
||||
b.Navigation("MangaConnectorIds");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
1055
API/Migrations/pgsql/20250630182650_OofV2.1.cs
Normal file
1055
API/Migrations/pgsql/20250630182650_OofV2.1.cs
Normal file
File diff suppressed because it is too large
Load Diff
@ -24,25 +24,23 @@ namespace API.Migrations.pgsql
|
||||
|
||||
modelBuilder.Entity("API.Schema.Author", b =>
|
||||
{
|
||||
b.Property<string>("AuthorId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
b.Property<string>("Key")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("AuthorName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b.HasKey("AuthorId");
|
||||
b.HasKey("Key");
|
||||
|
||||
b.ToTable("Authors");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Chapter", b =>
|
||||
{
|
||||
b.Property<string>("ChapterId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
b.Property<string>("Key")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ChapterNumber")
|
||||
.IsRequired()
|
||||
@ -57,38 +55,49 @@ namespace API.Migrations.pgsql
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("IdOnConnectorSite")
|
||||
.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<string>("Url")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)");
|
||||
|
||||
b.Property<int?>("VolumeNumber")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("ChapterId");
|
||||
b.HasKey("Key");
|
||||
|
||||
b.HasIndex("ParentMangaId");
|
||||
|
||||
b.ToTable("Chapters");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.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("LocalLibraries");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.Job", b =>
|
||||
{
|
||||
b.Property<string>("JobId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
b.Property<string>("Key")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool>("Enabled")
|
||||
.HasColumnType("boolean");
|
||||
@ -109,7 +118,7 @@ namespace API.Migrations.pgsql
|
||||
b.Property<byte>("state")
|
||||
.HasColumnType("smallint");
|
||||
|
||||
b.HasKey("JobId");
|
||||
b.HasKey("Key");
|
||||
|
||||
b.HasIndex("ParentJobId");
|
||||
|
||||
@ -120,32 +129,10 @@ namespace API.Migrations.pgsql
|
||||
b.UseTphMappingStrategy();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.LocalLibrary", b =>
|
||||
{
|
||||
b.Property<string>("LocalLibraryId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("BasePath")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("LibraryName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)");
|
||||
|
||||
b.HasKey("LocalLibraryId");
|
||||
|
||||
b.ToTable("LocalLibraries");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Manga", b =>
|
||||
{
|
||||
b.Property<string>("MangaId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
b.Property<string>("Key")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("CoverFileNameInCache")
|
||||
.HasMaxLength(512)
|
||||
@ -165,11 +152,6 @@ namespace API.Migrations.pgsql
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)");
|
||||
|
||||
b.Property<string>("IdOnConnectorSite")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<float>("IgnoreChaptersBefore")
|
||||
.HasColumnType("real");
|
||||
|
||||
@ -177,11 +159,6 @@ namespace API.Migrations.pgsql
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("MangaConnectorName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(512)
|
||||
@ -194,21 +171,80 @@ namespace API.Migrations.pgsql
|
||||
b.Property<byte>("ReleaseStatus")
|
||||
.HasColumnType("smallint");
|
||||
|
||||
b.Property<string>("WebsiteUrl")
|
||||
.IsRequired()
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)");
|
||||
|
||||
b.Property<long?>("Year")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.HasKey("MangaId");
|
||||
b.HasKey("Key");
|
||||
|
||||
b.HasIndex("LibraryId");
|
||||
|
||||
b.ToTable("Mangas");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaConnectorId<API.Schema.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<string>("WebsiteUrl")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.HasIndex("MangaConnectorName");
|
||||
|
||||
b.ToTable("Mangas");
|
||||
b.HasIndex("ObjId");
|
||||
|
||||
b.ToTable("MangaConnectorToChapter");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaConnectorId<API.Schema.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<string>("WebsiteUrl")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.HasIndex("MangaConnectorName");
|
||||
|
||||
b.HasIndex("ObjId");
|
||||
|
||||
b.ToTable("MangaConnectorToManga");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaConnectors.MangaConnector", b =>
|
||||
@ -255,13 +291,51 @@ namespace API.Migrations.pgsql
|
||||
b.ToTable("Tags");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MetadataFetchers.MetadataEntry", b =>
|
||||
{
|
||||
b.Property<string>("MetadataFetcherName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Identifier")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("MangaId")
|
||||
.IsRequired()
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasKey("MetadataFetcherName", "Identifier");
|
||||
|
||||
b.HasIndex("MangaId");
|
||||
|
||||
b.ToTable("MetadataEntries");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MetadataFetchers.MetadataFetcher", b =>
|
||||
{
|
||||
b.Property<string>("MetadataFetcherName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("MetadataEntry")
|
||||
.IsRequired()
|
||||
.HasMaxLength(21)
|
||||
.HasColumnType("character varying(21)");
|
||||
|
||||
b.HasKey("MetadataFetcherName");
|
||||
|
||||
b.ToTable("MetadataFetcher");
|
||||
|
||||
b.HasDiscriminator<string>("MetadataEntry").HasValue("MetadataFetcher");
|
||||
|
||||
b.UseTphMappingStrategy();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AuthorToManga", b =>
|
||||
{
|
||||
b.Property<string>("AuthorIds")
|
||||
.HasColumnType("character varying(64)");
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("MangaIds")
|
||||
.HasColumnType("character varying(64)");
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("AuthorIds", "MangaIds");
|
||||
|
||||
@ -272,15 +346,15 @@ namespace API.Migrations.pgsql
|
||||
|
||||
modelBuilder.Entity("JobJob", b =>
|
||||
{
|
||||
b.Property<string>("DependsOnJobsJobId")
|
||||
.HasColumnType("character varying(64)");
|
||||
b.Property<string>("DependsOnJobsKey")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("JobId")
|
||||
.HasColumnType("character varying(64)");
|
||||
b.Property<string>("JobKey")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("DependsOnJobsJobId", "JobId");
|
||||
b.HasKey("DependsOnJobsKey", "JobKey");
|
||||
|
||||
b.HasIndex("JobId");
|
||||
b.HasIndex("JobKey");
|
||||
|
||||
b.ToTable("JobJob");
|
||||
});
|
||||
@ -291,7 +365,7 @@ namespace API.Migrations.pgsql
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("MangaIds")
|
||||
.HasColumnType("character varying(64)");
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("MangaTagIds", "MangaIds");
|
||||
|
||||
@ -478,6 +552,13 @@ namespace API.Migrations.pgsql
|
||||
b.HasDiscriminator().HasValue("MangaDex");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MetadataFetchers.MyAnimeList", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.MetadataFetchers.MetadataFetcher");
|
||||
|
||||
b.HasDiscriminator().HasValue("MyAnimeList");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Chapter", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Manga", "ParentManga")
|
||||
@ -501,22 +582,44 @@ namespace API.Migrations.pgsql
|
||||
|
||||
modelBuilder.Entity("API.Schema.Manga", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.LocalLibrary", "Library")
|
||||
b.HasOne("API.Schema.FileLibrary", "Library")
|
||||
.WithMany()
|
||||
.HasForeignKey("LibraryId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.HasOne("API.Schema.MangaConnectors.MangaConnector", "MangaConnector")
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaConnectorName")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
b.OwnsMany("API.Schema.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.Link", "Links", b1 =>
|
||||
{
|
||||
b1.Property<string>("LinkId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
b1.Property<string>("Key")
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<string>("LinkProvider")
|
||||
.IsRequired()
|
||||
@ -528,48 +631,18 @@ namespace API.Migrations.pgsql
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)");
|
||||
|
||||
b1.Property<string>("MangaId")
|
||||
b1.Property<string>("MangaKey")
|
||||
.IsRequired()
|
||||
.HasColumnType("character varying(64)");
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.HasKey("LinkId");
|
||||
b1.HasKey("Key");
|
||||
|
||||
b1.HasIndex("MangaId");
|
||||
b1.HasIndex("MangaKey");
|
||||
|
||||
b1.ToTable("Link");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("MangaId");
|
||||
});
|
||||
|
||||
b.OwnsMany("API.Schema.MangaAltTitle", "AltTitles", b1 =>
|
||||
{
|
||||
b1.Property<string>("AltTitleId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b1.Property<string>("Language")
|
||||
.IsRequired()
|
||||
.HasMaxLength(8)
|
||||
.HasColumnType("character varying(8)");
|
||||
|
||||
b1.Property<string>("MangaId")
|
||||
.IsRequired()
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b1.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b1.HasKey("AltTitleId");
|
||||
|
||||
b1.HasIndex("MangaId");
|
||||
|
||||
b1.ToTable("MangaAltTitle");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("MangaId");
|
||||
.HasForeignKey("MangaKey");
|
||||
});
|
||||
|
||||
b.Navigation("AltTitles");
|
||||
@ -577,8 +650,63 @@ namespace API.Migrations.pgsql
|
||||
b.Navigation("Library");
|
||||
|
||||
b.Navigation("Links");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaConnectorId<API.Schema.Chapter>", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.MangaConnectors.MangaConnector", "MangaConnector")
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaConnectorName")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("API.Schema.Chapter", "Obj")
|
||||
.WithMany("MangaConnectorIds")
|
||||
.HasForeignKey("ObjId")
|
||||
.OnDelete(DeleteBehavior.NoAction)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("MangaConnector");
|
||||
|
||||
b.Navigation("Obj");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaConnectorId<API.Schema.Manga>", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.MangaConnectors.MangaConnector", "MangaConnector")
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaConnectorName")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("API.Schema.Manga", "Obj")
|
||||
.WithMany("MangaConnectorIds")
|
||||
.HasForeignKey("ObjId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("MangaConnector");
|
||||
|
||||
b.Navigation("Obj");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MetadataFetchers.MetadataEntry", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Manga", "Manga")
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("API.Schema.MetadataFetchers.MetadataFetcher", "MetadataFetcher")
|
||||
.WithMany()
|
||||
.HasForeignKey("MetadataFetcherName")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Manga");
|
||||
|
||||
b.Navigation("MetadataFetcher");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AuthorToManga", b =>
|
||||
@ -600,13 +728,13 @@ namespace API.Migrations.pgsql
|
||||
{
|
||||
b.HasOne("API.Schema.Jobs.Job", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("DependsOnJobsJobId")
|
||||
.HasForeignKey("DependsOnJobsKey")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("API.Schema.Jobs.Job", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("JobId")
|
||||
.HasForeignKey("JobKey")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
@ -667,7 +795,7 @@ namespace API.Migrations.pgsql
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("API.Schema.LocalLibrary", "ToLibrary")
|
||||
b.HasOne("API.Schema.FileLibrary", "ToFileLibrary")
|
||||
.WithMany()
|
||||
.HasForeignKey("ToLibraryId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
@ -675,7 +803,7 @@ namespace API.Migrations.pgsql
|
||||
|
||||
b.Navigation("Manga");
|
||||
|
||||
b.Navigation("ToLibrary");
|
||||
b.Navigation("ToFileLibrary");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.RetrieveChaptersJob", b =>
|
||||
@ -711,9 +839,16 @@ namespace API.Migrations.pgsql
|
||||
b.Navigation("Manga");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Chapter", b =>
|
||||
{
|
||||
b.Navigation("MangaConnectorIds");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Manga", b =>
|
||||
{
|
||||
b.Navigation("Chapters");
|
||||
|
||||
b.Navigation("MangaConnectorIds");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System.Reflection;
|
||||
using API;
|
||||
using API.Controllers;
|
||||
using API.Schema;
|
||||
using API.Schema.Contexts;
|
||||
using API.Schema.Jobs;
|
||||
@ -108,7 +109,27 @@ app.UseMiddleware<RequestTimeMiddleware>();
|
||||
using (IServiceScope scope = app.Services.CreateScope())
|
||||
{
|
||||
PgsqlContext context = scope.ServiceProvider.GetRequiredService<PgsqlContext>();
|
||||
context.Database.Migrate();
|
||||
//TODO Remove after migrations complete
|
||||
if (context.Database.GetMigrations().Contains("20250630182650_OofV2.1") == false)
|
||||
{
|
||||
IQueryable<(string, string)> mangas = context.Database.SqlQuery<(string, string)>($"SELECT MangaConnectorName, IdOnConnectorSite as ID FROM Mangas");
|
||||
context.Database.Migrate();
|
||||
foreach ((string mangaConnectorName, string idOnConnectorSite) manga in mangas)
|
||||
{
|
||||
if(context.MangaConnectors.Find(manga.mangaConnectorName) is not { } mangaConnector)
|
||||
continue;
|
||||
if(mangaConnector.GetMangaFromId(manga.idOnConnectorSite) is not { } result)
|
||||
continue;
|
||||
if (SearchController.AddMangaToContext(result.Item1, result.Item2, context) is { } added)
|
||||
{
|
||||
RetrieveChaptersJob retrieveChaptersJob = new (added, "en", 0);
|
||||
UpdateChaptersDownloadedJob update = new(added, 0, null, [retrieveChaptersJob]);
|
||||
context.Jobs.AddRange([retrieveChaptersJob, update]);
|
||||
}
|
||||
}
|
||||
} else
|
||||
context.Database.Migrate();
|
||||
|
||||
|
||||
MangaConnector[] connectors =
|
||||
[
|
||||
@ -119,7 +140,7 @@ using (IServiceScope scope = app.Services.CreateScope())
|
||||
MangaConnector[] newConnectors = connectors.Where(c => !context.MangaConnectors.Contains(c)).ToArray();
|
||||
context.MangaConnectors.AddRange(newConnectors);
|
||||
if (!context.LocalLibraries.Any())
|
||||
context.LocalLibraries.Add(new LocalLibrary(TrangaSettings.downloadLocation, "Default Library"));
|
||||
context.LocalLibraries.Add(new FileLibrary(TrangaSettings.downloadLocation, "Default FileLibrary"));
|
||||
|
||||
context.Jobs.AddRange(context.Jobs.Where(j => j.JobType == JobType.DownloadAvailableChaptersJob)
|
||||
.Include(downloadAvailableChaptersJob => ((DownloadAvailableChaptersJob)downloadAvailableChaptersJob).Manga)
|
||||
|
17
API/Schema/AltTitle.cs
Normal file
17
API/Schema/AltTitle.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace API.Schema;
|
||||
|
||||
[PrimaryKey("Key")]
|
||||
public class AltTitle(string language, string title) : Identifiable(TokenGen.CreateToken("AltTitle"))
|
||||
{
|
||||
[StringLength(8)]
|
||||
[Required]
|
||||
public string Language { get; init; } = language;
|
||||
[StringLength(256)]
|
||||
[Required]
|
||||
public string Title { get; init; } = title;
|
||||
|
||||
public override string ToString() => $"{base.ToString()} {Language} {Title}";
|
||||
}
|
@ -3,18 +3,12 @@ using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace API.Schema;
|
||||
|
||||
[PrimaryKey("AuthorId")]
|
||||
public class Author(string authorName)
|
||||
[PrimaryKey("Key")]
|
||||
public class Author(string authorName) : Identifiable(TokenGen.CreateToken(typeof(Author), authorName))
|
||||
{
|
||||
[StringLength(64)]
|
||||
[Required]
|
||||
public string AuthorId { get; init; } = TokenGen.CreateToken(typeof(Author), authorName);
|
||||
[StringLength(128)]
|
||||
[Required]
|
||||
public string AuthorName { get; init; } = authorName;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{AuthorId} {AuthorName}";
|
||||
}
|
||||
public override string ToString() => $"{base.ToString()} {AuthorName}";
|
||||
}
|
@ -4,24 +4,43 @@ using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace API.Schema;
|
||||
|
||||
[PrimaryKey("ChapterId")]
|
||||
public class Chapter : IComparable<Chapter>
|
||||
[PrimaryKey("Key")]
|
||||
public class Chapter : Identifiable, IComparable<Chapter>
|
||||
{
|
||||
[StringLength(64)] [Required] public string ChapterId { get; init; }
|
||||
[StringLength(64)] [Required] public string ParentMangaId { get; init; } = null!;
|
||||
private Manga? _parentManga;
|
||||
|
||||
[StringLength(256)]public string? IdOnConnectorSite { get; init; }
|
||||
public string ParentMangaId { get; init; }
|
||||
[JsonIgnore] public Manga ParentManga { get; init; } = null!;
|
||||
[JsonIgnore]
|
||||
public Manga ParentManga
|
||||
{
|
||||
get => _lazyLoader.Load(this, ref _parentManga) ?? throw new InvalidOperationException();
|
||||
init
|
||||
{
|
||||
ParentMangaId = value.Key;
|
||||
_parentManga = value;
|
||||
}
|
||||
}
|
||||
|
||||
[NotMapped]
|
||||
public Dictionary<string, string> IdsOnMangaConnectors =>
|
||||
MangaConnectorIds.ToDictionary(id => id.MangaConnectorName, id => id.IdOnConnectorSite);
|
||||
|
||||
private ICollection<MangaConnectorId<Chapter>>? _mangaConnectorIds;
|
||||
[JsonIgnore]
|
||||
public ICollection<MangaConnectorId<Chapter>> MangaConnectorIds
|
||||
{
|
||||
get => _lazyLoader.Load(this, ref _mangaConnectorIds) ?? throw new InvalidOperationException();
|
||||
init => _mangaConnectorIds = value;
|
||||
}
|
||||
|
||||
public int? VolumeNumber { get; private set; }
|
||||
[StringLength(10)] [Required] public string ChapterNumber { get; private set; }
|
||||
|
||||
[StringLength(2048)] [Required] [Url] public string Url { get; internal set; }
|
||||
|
||||
[StringLength(256)] public string? Title { get; private set; }
|
||||
|
||||
[StringLength(256)] [Required] public string FileName { get; private set; }
|
||||
@ -29,15 +48,16 @@ public class Chapter : IComparable<Chapter>
|
||||
[Required] public bool Downloaded { get; internal set; }
|
||||
[NotMapped] public string FullArchiveFilePath => Path.Join(ParentManga.FullDirectoryPath, FileName);
|
||||
|
||||
public Chapter(Manga parentManga, string url, string chapterNumber, int? volumeNumber = null, string? idOnConnectorSite = null, string? title = null)
|
||||
private readonly ILazyLoader _lazyLoader = null!;
|
||||
|
||||
public Chapter(Manga parentManga, string chapterNumber,
|
||||
int? volumeNumber, string? title = null)
|
||||
: base(TokenGen.CreateToken(typeof(Chapter), parentManga.Key, chapterNumber))
|
||||
{
|
||||
this.ChapterId = TokenGen.CreateToken(typeof(Chapter), parentManga.MangaId, chapterNumber);
|
||||
this.IdOnConnectorSite = idOnConnectorSite;
|
||||
this.ParentMangaId = parentManga.MangaId;
|
||||
this.ParentManga = parentManga;
|
||||
this.MangaConnectorIds = [];
|
||||
this.VolumeNumber = volumeNumber;
|
||||
this.ChapterNumber = chapterNumber;
|
||||
this.Url = url;
|
||||
this.Title = title;
|
||||
this.FileName = GetArchiveFilePath();
|
||||
this.Downloaded = false;
|
||||
@ -46,14 +66,12 @@ public class Chapter : IComparable<Chapter>
|
||||
/// <summary>
|
||||
/// EF ONLY!!!
|
||||
/// </summary>
|
||||
internal Chapter(string chapterId, string parentMangaId, int? volumeNumber, string chapterNumber, string url, string? idOnConnectorSite, string? title, string fileName, bool downloaded)
|
||||
internal Chapter(ILazyLoader lazyLoader, string key, int? volumeNumber, string chapterNumber, string? title, string fileName, bool downloaded)
|
||||
: base(key)
|
||||
{
|
||||
this.ChapterId = chapterId;
|
||||
this.IdOnConnectorSite = idOnConnectorSite;
|
||||
this.ParentMangaId = parentMangaId;
|
||||
this._lazyLoader = lazyLoader;
|
||||
this.VolumeNumber = volumeNumber;
|
||||
this.ChapterNumber = chapterNumber;
|
||||
this.Url = url;
|
||||
this.Title = title;
|
||||
this.FileName = fileName;
|
||||
this.Downloaded = downloaded;
|
||||
@ -78,14 +96,14 @@ public class Chapter : IComparable<Chapter>
|
||||
public bool CheckDownloaded() => File.Exists(FullArchiveFilePath);
|
||||
|
||||
/// Placeholders:
|
||||
/// %M Manga Name
|
||||
/// %M Obj Name
|
||||
/// %V Volume
|
||||
/// %C Chapter
|
||||
/// %T Title
|
||||
/// %A Author (first in list)
|
||||
/// %I Chapter Internal ID
|
||||
/// %i Manga Internal ID
|
||||
/// %Y Year (Manga)
|
||||
/// %i Obj Internal ID
|
||||
/// %Y Year (Obj)
|
||||
private static readonly Regex NullableRex = new(@"\?([a-zA-Z])\(([^\)]*)\)|(.+?)");
|
||||
private static readonly Regex ReplaceRexx = new(@"%([a-zA-Z])|(.+?)");
|
||||
private string GetArchiveFilePath()
|
||||
@ -108,8 +126,6 @@ public class Chapter : IComparable<Chapter>
|
||||
'C' => ChapterNumber is null,
|
||||
'T' => Title is null,
|
||||
'A' => ParentManga?.Authors?.FirstOrDefault()?.AuthorName is null,
|
||||
'I' => ChapterId is null,
|
||||
'i' => ParentManga?.MangaId is null,
|
||||
'Y' => ParentManga?.Year is null,
|
||||
_ => true
|
||||
};
|
||||
@ -136,8 +152,6 @@ public class Chapter : IComparable<Chapter>
|
||||
'C' => ChapterNumber,
|
||||
'T' => Title,
|
||||
'A' => ParentManga?.Authors?.FirstOrDefault()?.AuthorName,
|
||||
'I' => ChapterId,
|
||||
'i' => ParentManga?.MangaId,
|
||||
'Y' => ParentManga?.Year.ToString(),
|
||||
_ => null
|
||||
};
|
||||
@ -192,8 +206,5 @@ public class Chapter : IComparable<Chapter>
|
||||
return comicInfo.ToString();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{ChapterId} Vol.{VolumeNumber} Ch.{ChapterNumber} - {Title}";
|
||||
}
|
||||
public override string ToString() => $"{base.ToString()} Vol.{VolumeNumber} Ch.{ChapterNumber} - {Title}";
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
using API.Schema.Jobs;
|
||||
using API.Schema.MangaConnectors;
|
||||
using API.Schema.MetadataFetchers;
|
||||
using log4net;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Diagnostics;
|
||||
@ -11,10 +12,13 @@ public class PgsqlContext(DbContextOptions<PgsqlContext> options) : DbContext(op
|
||||
public DbSet<Job> Jobs { get; set; }
|
||||
public DbSet<MangaConnector> MangaConnectors { get; set; }
|
||||
public DbSet<Manga> Mangas { get; set; }
|
||||
public DbSet<LocalLibrary> LocalLibraries { get; set; }
|
||||
public DbSet<FileLibrary> LocalLibraries { get; set; }
|
||||
public DbSet<Chapter> Chapters { get; set; }
|
||||
public DbSet<Author> Authors { get; set; }
|
||||
public DbSet<MangaTag> Tags { get; set; }
|
||||
public DbSet<MangaConnectorId<Manga>> MangaConnectorToManga { get; set; }
|
||||
public DbSet<MangaConnectorId<Chapter>> MangaConnectorToChapter { get; set; }
|
||||
public DbSet<MetadataEntry> MetadataEntries { get; set; }
|
||||
private ILog Log => LogManager.GetLogger(GetType());
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
@ -40,8 +44,7 @@ public class PgsqlContext(DbContextOptions<PgsqlContext> options) : DbContext(op
|
||||
.HasValue<RetrieveChaptersJob>(JobType.RetrieveChaptersJob)
|
||||
.HasValue<UpdateCoverJob>(JobType.UpdateCoverJob)
|
||||
.HasValue<UpdateChaptersDownloadedJob>(JobType.UpdateChaptersDownloadedJob);
|
||||
|
||||
//Job specification
|
||||
|
||||
modelBuilder.Entity<DownloadAvailableChaptersJob>()
|
||||
.HasOne<Manga>(j => j.Manga)
|
||||
.WithMany()
|
||||
@ -75,12 +78,12 @@ public class PgsqlContext(DbContextOptions<PgsqlContext> options) : DbContext(op
|
||||
.Navigation(j => j.Manga)
|
||||
.EnableLazyLoading();
|
||||
modelBuilder.Entity<MoveMangaLibraryJob>()
|
||||
.HasOne<LocalLibrary>(j => j.ToLibrary)
|
||||
.HasOne<FileLibrary>(j => j.ToFileLibrary)
|
||||
.WithMany()
|
||||
.HasForeignKey(j => j.ToLibraryId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
modelBuilder.Entity<MoveMangaLibraryJob>()
|
||||
.Navigation(j => j.ToLibrary)
|
||||
.Navigation(j => j.ToFileLibrary)
|
||||
.EnableLazyLoading();
|
||||
modelBuilder.Entity<RetrieveChaptersJob>()
|
||||
.HasOne<Manga>(j => j.Manga)
|
||||
@ -103,15 +106,17 @@ public class PgsqlContext(DbContextOptions<PgsqlContext> options) : DbContext(op
|
||||
modelBuilder.Entity<Job>()
|
||||
.HasOne<Job>(childJob => childJob.ParentJob)
|
||||
.WithMany()
|
||||
.HasForeignKey(childjob => childjob.ParentJobId)
|
||||
.HasForeignKey(childJob => childJob.ParentJobId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
modelBuilder.Entity<Job>()
|
||||
.Navigation(childJob => childJob.ParentJob)
|
||||
.EnableLazyLoading();
|
||||
//Job might be dependent on other Jobs
|
||||
modelBuilder.Entity<Job>()
|
||||
.HasMany<Job>(root => root.DependsOnJobs)
|
||||
.WithMany();
|
||||
modelBuilder.Entity<Job>()
|
||||
.Navigation(j => j.DependsOnJobs)
|
||||
.AutoInclude(false)
|
||||
.EnableLazyLoading();
|
||||
|
||||
//MangaConnector Types
|
||||
@ -120,15 +125,6 @@ public class PgsqlContext(DbContextOptions<PgsqlContext> options) : DbContext(op
|
||||
.HasValue<Global>("Global")
|
||||
.HasValue<MangaDex>("MangaDex")
|
||||
.HasValue<ComickIo>("ComickIo");
|
||||
//MangaConnector is responsible for many Manga
|
||||
modelBuilder.Entity<MangaConnector>()
|
||||
.HasMany<Manga>()
|
||||
.WithOne(m => m.MangaConnector)
|
||||
.HasForeignKey(m => m.MangaConnectorName)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
modelBuilder.Entity<Manga>()
|
||||
.Navigation(m => m.MangaConnector)
|
||||
.AutoInclude();
|
||||
|
||||
//Manga has many Chapters
|
||||
modelBuilder.Entity<Manga>()
|
||||
@ -136,16 +132,29 @@ public class PgsqlContext(DbContextOptions<PgsqlContext> options) : DbContext(op
|
||||
.WithOne(c => c.ParentManga)
|
||||
.HasForeignKey(c => c.ParentMangaId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
modelBuilder.Entity<Chapter>()
|
||||
.Navigation(c => c.ParentManga)
|
||||
.AutoInclude();
|
||||
modelBuilder.Entity<Manga>()
|
||||
.Navigation(m => m.Chapters)
|
||||
.AutoInclude(false)
|
||||
.EnableLazyLoading();
|
||||
modelBuilder.Entity<Chapter>()
|
||||
.Navigation(c => c.ParentManga)
|
||||
.EnableLazyLoading();
|
||||
//Chapter has MangaConnectorIds
|
||||
modelBuilder.Entity<Chapter>()
|
||||
.HasMany<MangaConnectorId<Chapter>>(c => c.MangaConnectorIds)
|
||||
.WithOne(id => id.Obj)
|
||||
.HasForeignKey(id => id.ObjId)
|
||||
.OnDelete(DeleteBehavior.NoAction);
|
||||
modelBuilder.Entity<MangaConnectorId<Chapter>>()
|
||||
.HasOne<MangaConnector>(id => id.MangaConnector)
|
||||
.WithMany()
|
||||
.HasForeignKey(id => id.MangaConnectorName)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
modelBuilder.Entity<MangaConnectorId<Chapter>>()
|
||||
.Navigation(entry => entry.MangaConnector)
|
||||
.EnableLazyLoading();
|
||||
//Manga owns MangaAltTitles
|
||||
modelBuilder.Entity<Manga>()
|
||||
.OwnsMany<MangaAltTitle>(m => m.AltTitles)
|
||||
.OwnsMany<AltTitle>(m => m.AltTitles)
|
||||
.WithOwner();
|
||||
modelBuilder.Entity<Manga>()
|
||||
.Navigation(m => m.AltTitles)
|
||||
@ -157,39 +166,70 @@ public class PgsqlContext(DbContextOptions<PgsqlContext> options) : DbContext(op
|
||||
modelBuilder.Entity<Manga>()
|
||||
.Navigation(m => m.Links)
|
||||
.AutoInclude();
|
||||
//Manga has many Tags associated with many Manga
|
||||
//Manga has many Tags associated with many Obj
|
||||
modelBuilder.Entity<Manga>()
|
||||
.HasMany<MangaTag>(m => m.MangaTags)
|
||||
.WithMany()
|
||||
.UsingEntity("MangaTagToManga",
|
||||
l=> l.HasOne(typeof(MangaTag)).WithMany().HasForeignKey("MangaTagIds").HasPrincipalKey(nameof(MangaTag.Tag)),
|
||||
r => r.HasOne(typeof(Manga)).WithMany().HasForeignKey("MangaIds").HasPrincipalKey(nameof(Manga.MangaId)),
|
||||
r => r.HasOne(typeof(Manga)).WithMany().HasForeignKey("MangaIds").HasPrincipalKey(nameof(Manga.Key)),
|
||||
j => j.HasKey("MangaTagIds", "MangaIds")
|
||||
);
|
||||
modelBuilder.Entity<Manga>()
|
||||
.Navigation(m => m.MangaTags)
|
||||
.AutoInclude();
|
||||
//Manga has many Authors associated with many Manga
|
||||
//Manga has many Authors associated with many Obj
|
||||
modelBuilder.Entity<Manga>()
|
||||
.HasMany<Author>(m => m.Authors)
|
||||
.WithMany()
|
||||
.UsingEntity("AuthorToManga",
|
||||
l=> l.HasOne(typeof(Author)).WithMany().HasForeignKey("AuthorIds").HasPrincipalKey(nameof(Author.AuthorId)),
|
||||
r => r.HasOne(typeof(Manga)).WithMany().HasForeignKey("MangaIds").HasPrincipalKey(nameof(Manga.MangaId)),
|
||||
l=> l.HasOne(typeof(Author)).WithMany().HasForeignKey("AuthorIds").HasPrincipalKey(nameof(Author.Key)),
|
||||
r => r.HasOne(typeof(Manga)).WithMany().HasForeignKey("MangaIds").HasPrincipalKey(nameof(Manga.Key)),
|
||||
j => j.HasKey("AuthorIds", "MangaIds")
|
||||
);
|
||||
modelBuilder.Entity<Manga>()
|
||||
.Navigation(m => m.Authors)
|
||||
.AutoInclude();
|
||||
//Manga has many MangaIds
|
||||
modelBuilder.Entity<Manga>()
|
||||
.HasMany<MangaConnectorId<Manga>>(m => m.MangaConnectorIds)
|
||||
.WithOne(id => id.Obj)
|
||||
.HasForeignKey(id => id.ObjId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
modelBuilder.Entity<Manga>()
|
||||
.Navigation(m => m.MangaConnectorIds)
|
||||
.EnableLazyLoading();
|
||||
modelBuilder.Entity<MangaConnectorId<Manga>>()
|
||||
.HasOne<MangaConnector>(id => id.MangaConnector)
|
||||
.WithMany()
|
||||
.HasForeignKey(id => id.MangaConnectorName)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
modelBuilder.Entity<MangaConnectorId<Manga>>()
|
||||
.Navigation(entry => entry.MangaConnector)
|
||||
.EnableLazyLoading();
|
||||
|
||||
//LocalLibrary has many Mangas
|
||||
modelBuilder.Entity<LocalLibrary>()
|
||||
|
||||
//FileLibrary has many Mangas
|
||||
modelBuilder.Entity<FileLibrary>()
|
||||
.HasMany<Manga>()
|
||||
.WithOne(m => m.Library)
|
||||
.HasForeignKey(m => m.LibraryId)
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
modelBuilder.Entity<Manga>()
|
||||
.Navigation(m => m.Library)
|
||||
.AutoInclude();
|
||||
.EnableLazyLoading();
|
||||
|
||||
modelBuilder.Entity<MetadataFetcher>()
|
||||
.HasDiscriminator<string>(nameof(MetadataEntry))
|
||||
.HasValue<MyAnimeList>(nameof(MyAnimeList));
|
||||
//MetadataEntry
|
||||
modelBuilder.Entity<MetadataEntry>()
|
||||
.HasOne<Manga>(entry => entry.Manga)
|
||||
.WithMany()
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
modelBuilder.Entity<MetadataEntry>()
|
||||
.HasOne<MetadataFetcher>(entry => entry.MetadataFetcher)
|
||||
.WithMany()
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
19
API/Schema/FileLibrary.cs
Normal file
19
API/Schema/FileLibrary.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace API.Schema;
|
||||
|
||||
[PrimaryKey("Key")]
|
||||
public class FileLibrary(string basePath, string libraryName)
|
||||
: Identifiable(TokenGen.CreateToken(typeof(FileLibrary), basePath))
|
||||
{
|
||||
[StringLength(256)]
|
||||
[Required]
|
||||
public string BasePath { get; internal set; } = basePath;
|
||||
|
||||
[StringLength(512)]
|
||||
[Required]
|
||||
public string LibraryName { get; internal set; } = libraryName;
|
||||
|
||||
public override string ToString() => $"{base.ToString()} {LibraryName} - {BasePath}";
|
||||
}
|
11
API/Schema/Identifiable.cs
Normal file
11
API/Schema/Identifiable.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace API.Schema;
|
||||
|
||||
[PrimaryKey("Key")]
|
||||
public abstract class Identifiable(string key)
|
||||
{
|
||||
public string Key { get; init; } = key;
|
||||
|
||||
public override string ToString() => Key;
|
||||
}
|
@ -5,38 +5,46 @@ using Newtonsoft.Json;
|
||||
|
||||
namespace API.Schema.Jobs;
|
||||
|
||||
public class DownloadAvailableChaptersJob : Job
|
||||
public class DownloadAvailableChaptersJob : JobWithDownloading
|
||||
{
|
||||
[StringLength(64)] [Required] public string MangaId { get; init; }
|
||||
[StringLength(64)] [Required] public string MangaId { get; init; } = null!;
|
||||
private Manga? _manga;
|
||||
|
||||
private Manga _manga = null!;
|
||||
|
||||
[JsonIgnore]
|
||||
public Manga Manga
|
||||
public Manga Manga
|
||||
{
|
||||
get => LazyLoader.Load(this, ref _manga);
|
||||
init => _manga = value;
|
||||
get => LazyLoader.Load(this, ref _manga) ?? throw new InvalidOperationException();
|
||||
init
|
||||
{
|
||||
MangaId = value.Key;
|
||||
_manga = value;
|
||||
}
|
||||
}
|
||||
|
||||
public DownloadAvailableChaptersJob(Manga manga, ulong recurrenceMs, Job? parentJob = null, ICollection<Job>? dependsOnJobs = null)
|
||||
: base(TokenGen.CreateToken(typeof(DownloadAvailableChaptersJob)), JobType.DownloadAvailableChaptersJob, recurrenceMs, parentJob, dependsOnJobs)
|
||||
{
|
||||
this.MangaId = manga.MangaId;
|
||||
this.Manga = manga;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EF ONLY!!!
|
||||
/// </summary>
|
||||
internal DownloadAvailableChaptersJob(ILazyLoader lazyLoader, string jobId, ulong recurrenceMs, string mangaId, string? parentJobId)
|
||||
: base(lazyLoader, jobId, JobType.DownloadAvailableChaptersJob, recurrenceMs, parentJobId)
|
||||
internal DownloadAvailableChaptersJob(ILazyLoader lazyLoader, string key, string mangaId, ulong recurrenceMs, string? parentJobId)
|
||||
: base(lazyLoader, key, JobType.DownloadAvailableChaptersJob, recurrenceMs, parentJobId)
|
||||
{
|
||||
this.MangaId = mangaId;
|
||||
}
|
||||
|
||||
protected override IEnumerable<Job> RunInternal(PgsqlContext context)
|
||||
{
|
||||
context.Entry(Manga).Reference<LocalLibrary>(m => m.Library).Load();
|
||||
return Manga.Chapters.Where(c => c.Downloaded == false).Select(chapter => new DownloadSingleChapterJob(chapter, this));
|
||||
// Chapters that aren't downloaded and for which no downloading-Job exists
|
||||
IEnumerable<Chapter> newChapters = Manga.Chapters
|
||||
.Where(c =>
|
||||
c.Downloaded == false &&
|
||||
context.Jobs.Any(j =>
|
||||
j.JobType == JobType.DownloadSingleChapterJob &&
|
||||
((DownloadSingleChapterJob)j).Chapter.ParentMangaId == MangaId) == false);
|
||||
return newChapters.Select(c => new DownloadSingleChapterJob(c, this));
|
||||
}
|
||||
}
|
@ -6,40 +6,44 @@ using Newtonsoft.Json;
|
||||
|
||||
namespace API.Schema.Jobs;
|
||||
|
||||
public class DownloadMangaCoverJob : Job
|
||||
public class DownloadMangaCoverJob : JobWithDownloading
|
||||
{
|
||||
[StringLength(64)] [Required] public string MangaId { get; init; }
|
||||
[StringLength(64)] [Required] public string MangaId { get; init; } = null!;
|
||||
private Manga? _manga;
|
||||
|
||||
private Manga _manga = null!;
|
||||
|
||||
[JsonIgnore]
|
||||
public Manga Manga
|
||||
public Manga Manga
|
||||
{
|
||||
get => LazyLoader.Load(this, ref _manga);
|
||||
init => _manga = value;
|
||||
get => LazyLoader.Load(this, ref _manga) ?? throw new InvalidOperationException();
|
||||
init
|
||||
{
|
||||
MangaId = value.Key;
|
||||
_manga = value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public DownloadMangaCoverJob(Manga manga, Job? parentJob = null, ICollection<Job>? dependsOnJobs = null)
|
||||
: base(TokenGen.CreateToken(typeof(DownloadMangaCoverJob)), JobType.DownloadMangaCoverJob, 0, parentJob, dependsOnJobs)
|
||||
{
|
||||
this.MangaId = manga.MangaId;
|
||||
this.Manga = manga;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EF ONLY!!!
|
||||
/// </summary>
|
||||
internal DownloadMangaCoverJob(ILazyLoader lazyLoader, string jobId, ulong recurrenceMs, string mangaId, string? parentJobId)
|
||||
: base(lazyLoader, jobId, JobType.DownloadMangaCoverJob, recurrenceMs, parentJobId)
|
||||
internal DownloadMangaCoverJob(ILazyLoader lazyLoader, string key, string mangaId, ulong recurrenceMs, string? parentJobId)
|
||||
: base(lazyLoader, key, JobType.DownloadMangaCoverJob, recurrenceMs, parentJobId)
|
||||
{
|
||||
this.MangaId = mangaId;
|
||||
}
|
||||
|
||||
protected override IEnumerable<Job> RunInternal(PgsqlContext context)
|
||||
{
|
||||
//TODO MangaConnector Selection
|
||||
MangaConnectorId<Manga> mcId = Manga.MangaConnectorIds.First();
|
||||
try
|
||||
{
|
||||
Manga.CoverFileNameInCache = Manga.MangaConnector.SaveCoverImageToCache(Manga);
|
||||
Manga.CoverFileNameInCache = mcId.MangaConnector.SaveCoverImageToCache(mcId);
|
||||
context.SaveChanges();
|
||||
}
|
||||
catch (DbUpdateException e)
|
||||
|
@ -3,7 +3,6 @@ using System.IO.Compression;
|
||||
using System.Runtime.InteropServices;
|
||||
using API.MangaDownloadClients;
|
||||
using API.Schema.Contexts;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Newtonsoft.Json;
|
||||
using SixLabors.ImageSharp;
|
||||
@ -14,31 +13,33 @@ using static System.IO.UnixFileMode;
|
||||
|
||||
namespace API.Schema.Jobs;
|
||||
|
||||
public class DownloadSingleChapterJob : Job
|
||||
public class DownloadSingleChapterJob : JobWithDownloading
|
||||
{
|
||||
[StringLength(64)] [Required] public string ChapterId { get; init; }
|
||||
[StringLength(64)] [Required] public string ChapterId { get; init; } = null!;
|
||||
private Chapter? _chapter;
|
||||
|
||||
private Chapter _chapter = null!;
|
||||
|
||||
[JsonIgnore]
|
||||
public Chapter Chapter
|
||||
public Chapter Chapter
|
||||
{
|
||||
get => LazyLoader.Load(this, ref _chapter);
|
||||
init => _chapter = value;
|
||||
get => LazyLoader.Load(this, ref _chapter) ?? throw new InvalidOperationException();
|
||||
init
|
||||
{
|
||||
ChapterId = value.Key;
|
||||
_chapter = value;
|
||||
}
|
||||
}
|
||||
|
||||
public DownloadSingleChapterJob(Chapter chapter, Job? parentJob = null, ICollection<Job>? dependsOnJobs = null)
|
||||
: base(TokenGen.CreateToken(typeof(DownloadSingleChapterJob)), JobType.DownloadSingleChapterJob, 0, parentJob, dependsOnJobs)
|
||||
{
|
||||
this.ChapterId = chapter.ChapterId;
|
||||
this.Chapter = chapter;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EF ONLY!!!
|
||||
/// </summary>
|
||||
internal DownloadSingleChapterJob(ILazyLoader lazyLoader, string jobId, ulong recurrenceMs, string chapterId, string? parentJobId)
|
||||
: base(lazyLoader, jobId, JobType.DownloadSingleChapterJob, recurrenceMs, parentJobId)
|
||||
internal DownloadSingleChapterJob(ILazyLoader lazyLoader, string key, string chapterId, ulong recurrenceMs, string? parentJobId)
|
||||
: base(lazyLoader, key, JobType.DownloadSingleChapterJob, recurrenceMs, parentJobId)
|
||||
{
|
||||
this.ChapterId = chapterId;
|
||||
}
|
||||
@ -50,13 +51,16 @@ public class DownloadSingleChapterJob : Job
|
||||
Log.Info("Chapter was already downloaded.");
|
||||
return [];
|
||||
}
|
||||
string[] imageUrls = Chapter.ParentManga.MangaConnector.GetChapterImageUrls(Chapter);
|
||||
|
||||
//TODO MangaConnector Selection
|
||||
MangaConnectorId<Chapter> mcId = Chapter.MangaConnectorIds.First();
|
||||
|
||||
string[] imageUrls = mcId.MangaConnector.GetChapterImageUrls(mcId);
|
||||
if (imageUrls.Length < 1)
|
||||
{
|
||||
Log.Info($"No imageUrls for chapter {ChapterId}");
|
||||
Log.Info($"No imageUrls for chapter {Chapter}");
|
||||
return [];
|
||||
}
|
||||
context.Entry(Chapter.ParentManga).Reference<LocalLibrary>(m => m.Library).Load(); //Need to explicitly load, because we are not accessing navigation directly...
|
||||
string saveArchiveFilePath = Chapter.FullArchiveFilePath;
|
||||
Log.Debug($"Chapter path: {saveArchiveFilePath}");
|
||||
|
||||
@ -88,7 +92,7 @@ public class DownloadSingleChapterJob : Job
|
||||
string tempFolder = Directory.CreateTempSubdirectory("trangatemp").FullName;
|
||||
Log.Debug($"Created temp folder: {tempFolder}");
|
||||
|
||||
Log.Info($"Downloading images: {ChapterId}");
|
||||
Log.Info($"Downloading images: {Chapter}");
|
||||
int chapterNum = 0;
|
||||
//Download all Images to temporary Folder
|
||||
foreach (string imageUrl in imageUrls)
|
||||
@ -105,10 +109,10 @@ public class DownloadSingleChapterJob : Job
|
||||
|
||||
CopyCoverFromCacheToDownloadLocation(Chapter.ParentManga);
|
||||
|
||||
Log.Debug($"Creating ComicInfo.xml {ChapterId}");
|
||||
Log.Debug($"Creating ComicInfo.xml {Chapter}");
|
||||
File.WriteAllText(Path.Join(tempFolder, "ComicInfo.xml"), Chapter.GetComicInfoXmlString());
|
||||
|
||||
Log.Debug($"Packaging images to archive {ChapterId}");
|
||||
Log.Debug($"Packaging images to archive {Chapter}");
|
||||
//ZIP-it and ship-it
|
||||
ZipFile.CreateFromDirectory(tempFolder, saveArchiveFilePath);
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
@ -123,7 +127,7 @@ public class DownloadSingleChapterJob : Job
|
||||
if (j.JobType != JobType.UpdateChaptersDownloadedJob)
|
||||
return false;
|
||||
UpdateChaptersDownloadedJob job = (UpdateChaptersDownloadedJob)j;
|
||||
return job.MangaId == this.Chapter.ParentMangaId;
|
||||
return job.MangaId == Chapter.ParentMangaId;
|
||||
}))
|
||||
return [];
|
||||
|
||||
@ -178,9 +182,12 @@ public class DownloadSingleChapterJob : Job
|
||||
Log.Debug($"Cover already exists at {publicationFolder}");
|
||||
return;
|
||||
}
|
||||
|
||||
//TODO MangaConnector Selection
|
||||
MangaConnectorId<Manga> mcId = manga.MangaConnectorIds.First();
|
||||
|
||||
Log.Info($"Copying cover to {publicationFolder}");
|
||||
string? fileInCache = manga.CoverFileNameInCache ?? manga.MangaConnector.SaveCoverImageToCache(manga);
|
||||
string? fileInCache = manga.CoverFileNameInCache ?? mcId.MangaConnector.SaveCoverImageToCache(mcId);
|
||||
if (fileInCache is null)
|
||||
{
|
||||
Log.Error($"File {fileInCache} does not exist");
|
||||
|
@ -8,19 +8,15 @@ using Newtonsoft.Json;
|
||||
|
||||
namespace API.Schema.Jobs;
|
||||
|
||||
[PrimaryKey("JobId")]
|
||||
public abstract class Job
|
||||
[PrimaryKey("Key")]
|
||||
public abstract class Job : Identifiable, IComparable<Job>
|
||||
{
|
||||
[StringLength(64)]
|
||||
[Required]
|
||||
public string JobId { get; init; }
|
||||
|
||||
[StringLength(64)] public string? ParentJobId { get; private set; }
|
||||
[JsonIgnore] public Job? ParentJob { get; internal set; }
|
||||
private ICollection<Job> _dependsOnJobs = null!;
|
||||
private ICollection<Job>? _dependsOnJobs;
|
||||
[JsonIgnore] public ICollection<Job> DependsOnJobs
|
||||
{
|
||||
get => LazyLoader.Load(this, ref _dependsOnJobs);
|
||||
get => LazyLoader.Load(this, ref _dependsOnJobs) ?? throw new InvalidOperationException();
|
||||
init => _dependsOnJobs = value;
|
||||
}
|
||||
|
||||
@ -37,14 +33,14 @@ public abstract class Job
|
||||
[JsonIgnore] [NotMapped] internal bool IsCompleted => state is >= (JobState)128 and < (JobState)192;
|
||||
|
||||
[NotMapped] [JsonIgnore] protected ILog Log { get; init; }
|
||||
[NotMapped] [JsonIgnore] protected ILazyLoader LazyLoader { get; init; }
|
||||
[NotMapped] [JsonIgnore] protected ILazyLoader LazyLoader { get; init; } = null!;
|
||||
|
||||
protected Job(string jobId, JobType jobType, ulong recurrenceMs, Job? parentJob = null, ICollection<Job>? dependsOnJobs = null)
|
||||
protected Job(string key, JobType jobType, ulong recurrenceMs, Job? parentJob = null, ICollection<Job>? dependsOnJobs = null)
|
||||
: base(key)
|
||||
{
|
||||
this.JobId = jobId;
|
||||
this.JobType = jobType;
|
||||
this.RecurrenceMs = recurrenceMs;
|
||||
this.ParentJobId = parentJob?.JobId;
|
||||
this.ParentJobId = parentJob?.Key;
|
||||
this.ParentJob = parentJob;
|
||||
this.DependsOnJobs = dependsOnJobs ?? [];
|
||||
|
||||
@ -54,10 +50,10 @@ public abstract class Job
|
||||
/// <summary>
|
||||
/// EF ONLY!!!
|
||||
/// </summary>
|
||||
protected internal Job(ILazyLoader lazyLoader, string jobId, JobType jobType, ulong recurrenceMs, string? parentJobId)
|
||||
protected internal Job(ILazyLoader lazyLoader, string key, JobType jobType, ulong recurrenceMs, string? parentJobId)
|
||||
: base(key)
|
||||
{
|
||||
this.LazyLoader = lazyLoader;
|
||||
this.JobId = jobId;
|
||||
this.JobType = jobType;
|
||||
this.RecurrenceMs = recurrenceMs;
|
||||
this.ParentJobId = parentJobId;
|
||||
@ -68,7 +64,7 @@ public abstract class Job
|
||||
|
||||
public IEnumerable<Job> Run(PgsqlContext context, ref bool running)
|
||||
{
|
||||
Log.Info($"Running job {JobId}");
|
||||
Log.Info($"Running job {this}");
|
||||
DateTime jobStart = DateTime.UtcNow;
|
||||
Job[]? ret = null;
|
||||
|
||||
@ -78,7 +74,7 @@ public abstract class Job
|
||||
context.SaveChanges();
|
||||
running = true;
|
||||
ret = RunInternal(context).ToArray();
|
||||
Log.Info($"Job {JobId} completed. Generated {ret.Length} new jobs.");
|
||||
Log.Info($"Job {this} completed. Generated {ret.Length} new jobs.");
|
||||
this.state = this.RecurrenceMs > 0 ? JobState.CompletedWaiting : JobState.Completed;
|
||||
this.LastExecution = DateTime.UtcNow;
|
||||
context.SaveChanges();
|
||||
@ -87,7 +83,7 @@ public abstract class Job
|
||||
{
|
||||
if (e is not DbUpdateException)
|
||||
{
|
||||
Log.Error($"Failed to run job {JobId}", e);
|
||||
Log.Error($"Failed to run job {this}", e);
|
||||
this.state = JobState.Failed;
|
||||
this.Enabled = false;
|
||||
this.LastExecution = DateTime.UtcNow;
|
||||
@ -95,7 +91,7 @@ public abstract class Job
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error($"Failed to update Database {JobId}", e);
|
||||
Log.Error($"Failed to update Database {this}", e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -109,28 +105,45 @@ public abstract class Job
|
||||
}
|
||||
catch (DbUpdateException e)
|
||||
{
|
||||
Log.Error($"Failed to update Database {JobId}", e);
|
||||
Log.Error($"Failed to update Database {this}", e);
|
||||
}
|
||||
|
||||
Log.Info($"Finished Job {JobId}! (took {DateTime.UtcNow.Subtract(jobStart).TotalMilliseconds}ms)");
|
||||
Log.Info($"Finished Job {this}! (took {DateTime.UtcNow.Subtract(jobStart).TotalMilliseconds}ms)");
|
||||
return ret ?? [];
|
||||
}
|
||||
|
||||
protected abstract IEnumerable<Job> RunInternal(PgsqlContext context);
|
||||
|
||||
public List<Job> GetDependenciesAndSelf()
|
||||
{
|
||||
List<Job> ret = GetDependencies();
|
||||
ret.Add(this);
|
||||
return ret;
|
||||
}
|
||||
|
||||
public List<Job> GetDependencies()
|
||||
{
|
||||
List<Job> ret = new ();
|
||||
foreach (Job job in DependsOnJobs)
|
||||
{
|
||||
ret.AddRange(job.GetDependenciesAndSelf());
|
||||
}
|
||||
ret.Add(this);
|
||||
return ret;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
public int CompareTo(Job? other)
|
||||
{
|
||||
return $"{JobId}";
|
||||
if (other is null)
|
||||
return -1;
|
||||
// Sort by missing dependencies
|
||||
if (this.GetDependencies().Count(job => !job.IsCompleted) <
|
||||
other.GetDependencies().Count(job => !job.IsCompleted))
|
||||
return -1;
|
||||
// Sort by NextExecution-time
|
||||
if (this.NextExecution < other.NextExecution)
|
||||
return -1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
public override string ToString() => base.ToString();
|
||||
}
|
18
API/Schema/Jobs/JobWithDownloading.cs
Normal file
18
API/Schema/Jobs/JobWithDownloading.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
|
||||
namespace API.Schema.Jobs;
|
||||
|
||||
public abstract class JobWithDownloading : Job
|
||||
{
|
||||
|
||||
public JobWithDownloading(string key, JobType jobType, ulong recurrenceMs, Job? parentJob = null, ICollection<Job>? dependsOnJobs = null)
|
||||
: base(key, jobType, recurrenceMs, parentJob, dependsOnJobs)
|
||||
{
|
||||
|
||||
}
|
||||
public JobWithDownloading(ILazyLoader lazyLoader, string key, JobType jobType, ulong recurrenceMs, string? parentJobId)
|
||||
: base(lazyLoader, key, jobType, recurrenceMs, parentJobId)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
@ -23,8 +23,8 @@ public class MoveFileOrFolderJob : Job
|
||||
/// <summary>
|
||||
/// EF ONLY!!!
|
||||
/// </summary>
|
||||
internal MoveFileOrFolderJob(ILazyLoader lazyLoader, string jobId, ulong recurrenceMs, string fromLocation, string toLocation, string? parentJobId)
|
||||
: base(lazyLoader, jobId, JobType.MoveFileOrFolderJob, recurrenceMs, parentJobId)
|
||||
internal MoveFileOrFolderJob(ILazyLoader lazyLoader, string key, ulong recurrenceMs, string fromLocation, string toLocation, string? parentJobId)
|
||||
: base(lazyLoader, key, JobType.MoveFileOrFolderJob, recurrenceMs, parentJobId)
|
||||
{
|
||||
this.FromLocation = fromLocation;
|
||||
this.ToLocation = toLocation;
|
||||
|
@ -8,33 +8,45 @@ namespace API.Schema.Jobs;
|
||||
|
||||
public class MoveMangaLibraryJob : Job
|
||||
{
|
||||
[StringLength(64)] [Required] public string MangaId { get; init; }
|
||||
[StringLength(64)] [Required] public string MangaId { get; init; } = null!;
|
||||
private Manga? _manga;
|
||||
|
||||
private Manga _manga = null!;
|
||||
|
||||
[JsonIgnore]
|
||||
public Manga Manga
|
||||
public Manga Manga
|
||||
{
|
||||
get => LazyLoader.Load(this, ref _manga);
|
||||
init => _manga = value;
|
||||
get => LazyLoader.Load(this, ref _manga) ?? throw new InvalidOperationException();
|
||||
init
|
||||
{
|
||||
MangaId = value.Key;
|
||||
_manga = value;
|
||||
}
|
||||
}
|
||||
[StringLength(64)] [Required] public string ToLibraryId { get; init; }
|
||||
public LocalLibrary ToLibrary { get; init; } = null!;
|
||||
|
||||
public MoveMangaLibraryJob(Manga manga, LocalLibrary toLibrary, Job? parentJob = null, ICollection<Job>? dependsOnJobs = null)
|
||||
|
||||
[StringLength(64)] [Required] public string ToLibraryId { get; private set; } = null!;
|
||||
private FileLibrary? _toFileLibrary;
|
||||
[JsonIgnore]
|
||||
public FileLibrary ToFileLibrary
|
||||
{
|
||||
get => LazyLoader.Load(this, ref _toFileLibrary) ?? throw new InvalidOperationException();
|
||||
init
|
||||
{
|
||||
ToLibraryId = value.Key;
|
||||
_toFileLibrary = value;
|
||||
}
|
||||
}
|
||||
|
||||
public MoveMangaLibraryJob(Manga manga, FileLibrary toFileLibrary, Job? parentJob = null, ICollection<Job>? dependsOnJobs = null)
|
||||
: base(TokenGen.CreateToken(typeof(MoveMangaLibraryJob)), JobType.MoveMangaLibraryJob, 0, parentJob, dependsOnJobs)
|
||||
{
|
||||
this.MangaId = manga.MangaId;
|
||||
this.Manga = manga;
|
||||
this.ToLibraryId = toLibrary.LocalLibraryId;
|
||||
this.ToLibrary = toLibrary;
|
||||
this.ToFileLibrary = toFileLibrary;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EF ONLY!!!
|
||||
/// </summary>
|
||||
internal MoveMangaLibraryJob(ILazyLoader lazyLoader, string jobId, ulong recurrenceMs, string mangaId, string toLibraryId, string? parentJobId)
|
||||
: base(lazyLoader, jobId, JobType.MoveMangaLibraryJob, recurrenceMs, parentJobId)
|
||||
internal MoveMangaLibraryJob(ILazyLoader lazyLoader, string key, ulong recurrenceMs, string mangaId, string toLibraryId, string? parentJobId)
|
||||
: base(lazyLoader, key, JobType.MoveMangaLibraryJob, recurrenceMs, parentJobId)
|
||||
{
|
||||
this.MangaId = mangaId;
|
||||
this.ToLibraryId = toLibraryId;
|
||||
@ -42,9 +54,9 @@ public class MoveMangaLibraryJob : Job
|
||||
|
||||
protected override IEnumerable<Job> RunInternal(PgsqlContext context)
|
||||
{
|
||||
context.Entry(Manga).Reference<LocalLibrary>(m => m.Library).Load();
|
||||
context.Entry(Manga).Reference<FileLibrary>(m => m.Library).Load();
|
||||
Dictionary<Chapter, string> oldPath = Manga.Chapters.ToDictionary(c => c, c => c.FullArchiveFilePath);
|
||||
Manga.Library = ToLibrary;
|
||||
Manga.Library = ToFileLibrary;
|
||||
try
|
||||
{
|
||||
context.SaveChanges();
|
||||
|
@ -6,24 +6,27 @@ using Newtonsoft.Json;
|
||||
|
||||
namespace API.Schema.Jobs;
|
||||
|
||||
public class RetrieveChaptersJob : Job
|
||||
public class RetrieveChaptersJob : JobWithDownloading
|
||||
{
|
||||
[StringLength(64)] [Required] public string MangaId { get; init; }
|
||||
[StringLength(64)] [Required] public string MangaId { get; init; } = null!;
|
||||
private Manga? _manga;
|
||||
|
||||
private Manga _manga = null!;
|
||||
|
||||
[JsonIgnore]
|
||||
public Manga Manga
|
||||
public Manga Manga
|
||||
{
|
||||
get => LazyLoader.Load(this, ref _manga);
|
||||
init => _manga = value;
|
||||
get => LazyLoader.Load(this, ref _manga) ?? throw new InvalidOperationException();
|
||||
init
|
||||
{
|
||||
MangaId = value.Key;
|
||||
_manga = value;
|
||||
}
|
||||
}
|
||||
|
||||
[StringLength(8)] [Required] public string Language { get; private set; }
|
||||
|
||||
public RetrieveChaptersJob(Manga manga, string language, ulong recurrenceMs, Job? parentJob = null, ICollection<Job>? dependsOnJobs = null)
|
||||
: base(TokenGen.CreateToken(typeof(RetrieveChaptersJob)), JobType.RetrieveChaptersJob, recurrenceMs, parentJob, dependsOnJobs)
|
||||
{
|
||||
this.MangaId = manga.MangaId;
|
||||
this.Manga = manga;
|
||||
this.Language = language;
|
||||
}
|
||||
@ -31,8 +34,8 @@ public class RetrieveChaptersJob : Job
|
||||
/// <summary>
|
||||
/// EF ONLY!!!
|
||||
/// </summary>
|
||||
internal RetrieveChaptersJob(ILazyLoader lazyLoader, string jobId, ulong recurrenceMs, string mangaId, string language, string? parentJobId)
|
||||
: base(lazyLoader, jobId, JobType.RetrieveChaptersJob, recurrenceMs, parentJobId)
|
||||
internal RetrieveChaptersJob(ILazyLoader lazyLoader, string key, string mangaId, ulong recurrenceMs, string language, string? parentJobId)
|
||||
: base(lazyLoader, key, JobType.RetrieveChaptersJob, recurrenceMs, parentJobId)
|
||||
{
|
||||
this.MangaId = mangaId;
|
||||
this.Language = language;
|
||||
@ -40,15 +43,21 @@ public class RetrieveChaptersJob : Job
|
||||
|
||||
protected override IEnumerable<Job> RunInternal(PgsqlContext context)
|
||||
{
|
||||
//TODO MangaConnector Selection
|
||||
MangaConnectorId<Manga> mcId = Manga.MangaConnectorIds.First();
|
||||
|
||||
// This gets all chapters that are not downloaded
|
||||
Chapter[] allChapters = Manga.MangaConnector.GetChapters(Manga, Language).DistinctBy(c => c.ChapterId).ToArray();
|
||||
Chapter[] newChapters = allChapters.Where(chapter => Manga.Chapters.Select(c => c.ChapterId).Contains(chapter.ChapterId) == false).ToArray();
|
||||
(Chapter, MangaConnectorId<Chapter>)[] allChapters = mcId.MangaConnector.GetChapters(mcId, 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.");
|
||||
|
||||
try
|
||||
{
|
||||
foreach (Chapter newChapter in newChapters)
|
||||
Manga.Chapters.Add(newChapter);
|
||||
foreach ((Chapter chapter, MangaConnectorId<Chapter> mcId) newChapter in newChapters)
|
||||
{
|
||||
Manga.Chapters.Add(newChapter.chapter);
|
||||
context.MangaConnectorToChapter.Add(newChapter.mcId);
|
||||
}
|
||||
context.SaveChanges();
|
||||
}
|
||||
catch (DbUpdateException e)
|
||||
|
@ -8,36 +8,38 @@ namespace API.Schema.Jobs;
|
||||
|
||||
public class UpdateChaptersDownloadedJob : Job
|
||||
{
|
||||
[StringLength(64)] [Required] public string MangaId { get; init; }
|
||||
[StringLength(64)] [Required] public string MangaId { get; init; } = null!;
|
||||
private Manga? _manga;
|
||||
|
||||
private Manga _manga = null!;
|
||||
|
||||
[JsonIgnore]
|
||||
public Manga Manga
|
||||
public Manga Manga
|
||||
{
|
||||
get => LazyLoader.Load(this, ref _manga);
|
||||
init => _manga = value;
|
||||
get => LazyLoader.Load(this, ref _manga) ?? throw new InvalidOperationException();
|
||||
init
|
||||
{
|
||||
MangaId = value.Key;
|
||||
_manga = value;
|
||||
}
|
||||
}
|
||||
|
||||
public UpdateChaptersDownloadedJob(Manga manga, ulong recurrenceMs, Job? parentJob = null, ICollection<Job>? dependsOnJobs = null)
|
||||
: base(TokenGen.CreateToken(typeof(UpdateChaptersDownloadedJob)), JobType.UpdateChaptersDownloadedJob, recurrenceMs, parentJob, dependsOnJobs)
|
||||
{
|
||||
this.MangaId = manga.MangaId;
|
||||
this.Manga = manga;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EF ONLY!!!
|
||||
/// </summary>
|
||||
internal UpdateChaptersDownloadedJob(ILazyLoader lazyLoader, string jobId, ulong recurrenceMs, string mangaId, string? parentJobId)
|
||||
: base(lazyLoader, jobId, JobType.UpdateChaptersDownloadedJob, recurrenceMs, parentJobId)
|
||||
internal UpdateChaptersDownloadedJob(ILazyLoader lazyLoader, string key, ulong recurrenceMs, string mangaId, string? parentJobId)
|
||||
: base(lazyLoader, key, JobType.UpdateChaptersDownloadedJob, recurrenceMs, parentJobId)
|
||||
{
|
||||
this.MangaId = mangaId;
|
||||
}
|
||||
|
||||
protected override IEnumerable<Job> RunInternal(PgsqlContext context)
|
||||
{
|
||||
context.Entry(Manga).Reference<LocalLibrary>(m => m.Library).Load();
|
||||
context.Entry(Manga).Reference<FileLibrary>(m => m.Library).Load();
|
||||
foreach (Chapter mangaChapter in Manga.Chapters)
|
||||
{
|
||||
mangaChapter.Downloaded = mangaChapter.CheckDownloaded();
|
||||
|
@ -8,30 +8,32 @@ namespace API.Schema.Jobs;
|
||||
|
||||
public class UpdateCoverJob : Job
|
||||
{
|
||||
[StringLength(64)] [Required] public string MangaId { get; init; }
|
||||
[StringLength(64)] [Required] public string MangaId { get; init; } = null!;
|
||||
private Manga? _manga;
|
||||
|
||||
private Manga _manga = null!;
|
||||
|
||||
[JsonIgnore]
|
||||
public Manga Manga
|
||||
public Manga Manga
|
||||
{
|
||||
get => LazyLoader.Load(this, ref _manga);
|
||||
init => _manga = value;
|
||||
get => LazyLoader.Load(this, ref _manga) ?? throw new InvalidOperationException();
|
||||
init
|
||||
{
|
||||
MangaId = value.Key;
|
||||
_manga = value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public UpdateCoverJob(Manga manga, ulong recurrenceMs, Job? parentJob = null, ICollection<Job>? dependsOnJobs = null)
|
||||
: base(TokenGen.CreateToken(typeof(UpdateCoverJob)), JobType.UpdateCoverJob, recurrenceMs, parentJob, dependsOnJobs)
|
||||
{
|
||||
this.MangaId = manga.MangaId;
|
||||
this.Manga = manga;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EF ONLY!!!
|
||||
/// </summary>
|
||||
internal UpdateCoverJob(ILazyLoader lazyLoader, string jobId, ulong recurrenceMs, string mangaId, string? parentJobId)
|
||||
: base(lazyLoader, jobId, JobType.UpdateCoverJob, recurrenceMs, parentJobId)
|
||||
internal UpdateCoverJob(ILazyLoader lazyLoader, string key, string mangaId, ulong recurrenceMs, string? parentJobId)
|
||||
: base(lazyLoader, key, JobType.UpdateCoverJob, recurrenceMs, parentJobId)
|
||||
{
|
||||
this.MangaId = mangaId;
|
||||
}
|
||||
|
@ -44,8 +44,9 @@ public class Kavita : LibraryConnector
|
||||
{
|
||||
}
|
||||
}
|
||||
catch (HttpRequestException e)
|
||||
catch (HttpRequestException)
|
||||
{
|
||||
|
||||
}
|
||||
return "";
|
||||
}
|
||||
@ -53,13 +54,13 @@ public class Kavita : LibraryConnector
|
||||
protected override void UpdateLibraryInternal()
|
||||
{
|
||||
foreach (KavitaLibrary lib in GetLibraries())
|
||||
NetClient.MakePost($"{BaseUrl}/api/ToLibrary/scan?libraryId={lib.id}", "Bearer", Auth);
|
||||
NetClient.MakePost($"{BaseUrl}/api/ToFileLibrary/scan?libraryId={lib.id}", "Bearer", Auth);
|
||||
}
|
||||
|
||||
internal override bool Test()
|
||||
{
|
||||
foreach (KavitaLibrary lib in GetLibraries())
|
||||
if (NetClient.MakePost($"{BaseUrl}/api/ToLibrary/scan?libraryId={lib.id}", "Bearer", Auth))
|
||||
if (NetClient.MakePost($"{BaseUrl}/api/ToFileLibrary/scan?libraryId={lib.id}", "Bearer", Auth))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
@ -70,7 +71,7 @@ public class Kavita : LibraryConnector
|
||||
/// <returns>Array of KavitaLibrary</returns>
|
||||
private IEnumerable<KavitaLibrary> GetLibraries()
|
||||
{
|
||||
Stream data = NetClient.MakeRequest($"{BaseUrl}/api/ToLibrary/libraries", "Bearer", Auth);
|
||||
Stream data = NetClient.MakeRequest($"{BaseUrl}/api/ToFileLibrary/libraries", "Bearer", Auth);
|
||||
if (data == Stream.Null)
|
||||
{
|
||||
Log.Info("No libraries found");
|
||||
@ -90,7 +91,7 @@ public class Kavita : LibraryConnector
|
||||
JsonObject? jObject = (JsonObject?)jsonNode;
|
||||
if(jObject is null)
|
||||
continue;
|
||||
int libraryId = jObject!["id"]!.GetValue<int>();
|
||||
int libraryId = jObject["id"]!.GetValue<int>();
|
||||
string libraryName = jObject["name"]!.GetValue<string>();
|
||||
ret.Add(new KavitaLibrary(libraryId, libraryName));
|
||||
}
|
||||
|
@ -3,12 +3,9 @@ using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace API.Schema;
|
||||
|
||||
[PrimaryKey("LinkId")]
|
||||
public class Link(string linkProvider, string linkUrl)
|
||||
[PrimaryKey("Key")]
|
||||
public class Link(string linkProvider, string linkUrl) : Identifiable(TokenGen.CreateToken(typeof(Link), linkProvider, linkUrl))
|
||||
{
|
||||
[StringLength(64)]
|
||||
[Required]
|
||||
public string LinkId { get; init; } = TokenGen.CreateToken(typeof(Link), linkProvider, linkUrl);
|
||||
[StringLength(64)]
|
||||
[Required]
|
||||
public string LinkProvider { get; init; } = linkProvider;
|
||||
@ -17,8 +14,5 @@ public class Link(string linkProvider, string linkUrl)
|
||||
[Url]
|
||||
public string LinkUrl { get; init; } = linkUrl;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{LinkId} {LinkProvider} {LinkUrl}";
|
||||
}
|
||||
public override string ToString() => $"{base.ToString()} {LinkProvider} {LinkUrl}";
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace API.Schema;
|
||||
|
||||
public class LocalLibrary(string basePath, string libraryName)
|
||||
{
|
||||
[StringLength(64)]
|
||||
[Required]
|
||||
public string LocalLibraryId { get; init; } = TokenGen.CreateToken(typeof(LocalLibrary), basePath);
|
||||
[StringLength(256)]
|
||||
[Required]
|
||||
public string BasePath { get; internal set; } = basePath;
|
||||
|
||||
[StringLength(512)]
|
||||
[Required]
|
||||
public string LibraryName { get; internal set; } = libraryName;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{LocalLibraryId} {LibraryName} - {BasePath}";
|
||||
}
|
||||
}
|
@ -2,7 +2,8 @@ using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using API.Schema.MangaConnectors;
|
||||
using API.Schema.Contexts;
|
||||
using API.Schema.Jobs;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Newtonsoft.Json;
|
||||
@ -10,36 +11,33 @@ using static System.IO.UnixFileMode;
|
||||
|
||||
namespace API.Schema;
|
||||
|
||||
[PrimaryKey("MangaId")]
|
||||
public class Manga
|
||||
[PrimaryKey("Key")]
|
||||
public class Manga : Identifiable
|
||||
{
|
||||
[StringLength(64)]
|
||||
[Required]
|
||||
public string MangaId { get; init; }
|
||||
[StringLength(256)] [Required] public string IdOnConnectorSite { get; init; }
|
||||
[StringLength(512)] [Required] public string Name { get; internal set; }
|
||||
[Required] public string Description { get; internal set; }
|
||||
[Url] [StringLength(512)] [Required] public string WebsiteUrl { get; internal init; }
|
||||
[JsonIgnore] [Url] [StringLength(512)] public string CoverUrl { get; internal set; }
|
||||
[Required] public MangaReleaseStatus ReleaseStatus { get; internal set; }
|
||||
|
||||
[StringLength(64)]
|
||||
public string? LibraryId { get; init; }
|
||||
[JsonIgnore] public LocalLibrary? Library { get; internal set; }
|
||||
|
||||
[StringLength(32)]
|
||||
[Required]
|
||||
public string MangaConnectorName { get; init; }
|
||||
[JsonIgnore] public MangaConnector MangaConnector { get; init; } = null!;
|
||||
[StringLength(64)] public string? LibraryId { get; private set; }
|
||||
private FileLibrary? _library;
|
||||
[JsonIgnore]
|
||||
public FileLibrary? Library
|
||||
{
|
||||
get => _lazyLoader.Load(this, ref _library);
|
||||
set
|
||||
{
|
||||
LibraryId = value?.Key;
|
||||
_library = value;
|
||||
}
|
||||
}
|
||||
|
||||
public ICollection<Author> Authors { get; internal set; }= null!;
|
||||
public ICollection<MangaTag> MangaTags { get; internal set; }= null!;
|
||||
public ICollection<Link> Links { get; internal set; }= null!;
|
||||
public ICollection<MangaAltTitle> AltTitles { get; internal set; } = null!;
|
||||
public ICollection<AltTitle> AltTitles { get; internal set; } = null!;
|
||||
[Required] public float IgnoreChaptersBefore { get; internal set; }
|
||||
[StringLength(1024)] [Required] public string DirectoryName { get; private set; }
|
||||
|
||||
[JsonIgnore] [StringLength(512)] public string? CoverFileNameInCache { get; internal set; } = null;
|
||||
[JsonIgnore] [StringLength(512)] public string? CoverFileNameInCache { get; internal set; }
|
||||
public uint? Year { get; internal init; }
|
||||
[StringLength(8)] public string? OriginalLanguage { get; internal init; }
|
||||
|
||||
@ -47,31 +45,37 @@ public class Manga
|
||||
[NotMapped]
|
||||
public string? FullDirectoryPath => Library is not null ? Path.Join(Library.BasePath, DirectoryName) : null;
|
||||
|
||||
[NotMapped] public ICollection<string> ChapterIds => Chapters.Select(c => c.ChapterId).ToList();
|
||||
private readonly ILazyLoader _lazyLoader = null!;
|
||||
private ICollection<Chapter> _chapters = null!;
|
||||
[JsonIgnore]
|
||||
public ICollection<Chapter> Chapters
|
||||
[NotMapped] public ICollection<string> ChapterIds => Chapters.Select(c => c.Key).ToList();
|
||||
private ICollection<Chapter>? _chapters;
|
||||
[JsonIgnore]
|
||||
public ICollection<Chapter> Chapters
|
||||
{
|
||||
get => _lazyLoader.Load(this, ref _chapters);
|
||||
get => _lazyLoader.Load(this, ref _chapters) ?? throw new InvalidOperationException();
|
||||
init => _chapters = value;
|
||||
}
|
||||
|
||||
public Manga(string idOnConnector, string name, string description, string websiteUrl, string coverUrl, MangaReleaseStatus releaseStatus,
|
||||
MangaConnector mangaConnector, ICollection<Author> authors, ICollection<MangaTag> mangaTags, ICollection<Link> links, ICollection<MangaAltTitle> altTitles,
|
||||
LocalLibrary? library = null, float ignoreChaptersBefore = 0f, uint? year = null, string? originalLanguage = null)
|
||||
[NotMapped] public Dictionary<string, string> IdsOnMangaConnectors =>
|
||||
MangaConnectorIds.ToDictionary(id => id.MangaConnectorName, id => id.IdOnConnectorSite);
|
||||
private ICollection<MangaConnectorId<Manga>>? _mangaConnectorIds;
|
||||
[JsonIgnore]
|
||||
public ICollection<MangaConnectorId<Manga>> MangaConnectorIds
|
||||
{
|
||||
get => _lazyLoader.Load(this, ref _mangaConnectorIds) ?? throw new InvalidOperationException();
|
||||
private set => _mangaConnectorIds = value;
|
||||
}
|
||||
|
||||
private readonly ILazyLoader _lazyLoader = null!;
|
||||
|
||||
public Manga(string name, string description, string coverUrl, MangaReleaseStatus releaseStatus,
|
||||
ICollection<Author> authors, ICollection<MangaTag> mangaTags, ICollection<Link> links, ICollection<AltTitle> altTitles,
|
||||
FileLibrary? library = null, float ignoreChaptersBefore = 0f, uint? year = null, string? originalLanguage = null)
|
||||
:base(TokenGen.CreateToken(typeof(Manga), name))
|
||||
{
|
||||
this.MangaId = TokenGen.CreateToken(typeof(Manga), mangaConnector.Name, idOnConnector);
|
||||
this.IdOnConnectorSite = idOnConnector;
|
||||
this.Name = name;
|
||||
this.Description = description;
|
||||
this.WebsiteUrl = websiteUrl;
|
||||
this.CoverUrl = coverUrl;
|
||||
this.ReleaseStatus = releaseStatus;
|
||||
this.LibraryId = library?.LocalLibraryId;
|
||||
this.Library = library;
|
||||
this.MangaConnectorName = mangaConnector.Name;
|
||||
this.MangaConnector = mangaConnector;
|
||||
this.Authors = authors;
|
||||
this.MangaTags = mangaTags;
|
||||
this.Links = links;
|
||||
@ -86,18 +90,16 @@ public class Manga
|
||||
/// <summary>
|
||||
/// EF ONLY!!!
|
||||
/// </summary>
|
||||
public Manga(ILazyLoader lazyLoader, string mangaId, string idOnConnectorSite, string name, string description, string websiteUrl, string coverUrl, MangaReleaseStatus releaseStatus,
|
||||
string mangaConnectorName, string directoryName, float ignoreChaptersBefore, string? libraryId, uint? year, string? originalLanguage)
|
||||
public Manga(ILazyLoader lazyLoader, string key, string name, string description, string coverUrl,
|
||||
MangaReleaseStatus releaseStatus,
|
||||
string directoryName, float ignoreChaptersBefore, string? libraryId, uint? year, string? originalLanguage)
|
||||
: base(key)
|
||||
{
|
||||
this._lazyLoader = lazyLoader;
|
||||
this.MangaId = mangaId;
|
||||
this.IdOnConnectorSite = idOnConnectorSite;
|
||||
this.Name = name;
|
||||
this.Description = description;
|
||||
this.WebsiteUrl = websiteUrl;
|
||||
this.CoverUrl = coverUrl;
|
||||
this.ReleaseStatus = releaseStatus;
|
||||
this.MangaConnectorName = mangaConnectorName;
|
||||
this.DirectoryName = directoryName;
|
||||
this.LibraryId = libraryId;
|
||||
this.IgnoreChaptersBefore = ignoreChaptersBefore;
|
||||
@ -154,8 +156,53 @@ public class Manga
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="other"></param>
|
||||
/// <param name="context"></param>
|
||||
/// <exception cref="DbUpdateException"></exception>
|
||||
public void MergeFrom(Manga other, PgsqlContext context)
|
||||
{
|
||||
return $"{MangaId} {Name}";
|
||||
try
|
||||
{
|
||||
context.Mangas.Remove(other);
|
||||
List<Job> newJobs = new();
|
||||
|
||||
this.MangaConnectorIds = this.MangaConnectorIds
|
||||
.UnionBy(other.MangaConnectorIds, id => id.MangaConnectorName)
|
||||
.ToList();
|
||||
|
||||
foreach (Chapter otherChapter in other.Chapters)
|
||||
{
|
||||
string oldPath = otherChapter.FullArchiveFilePath;
|
||||
Chapter newChapter = new(this, otherChapter.ChapterNumber, otherChapter.VolumeNumber,
|
||||
otherChapter.Title);
|
||||
this.Chapters.Add(newChapter);
|
||||
string newPath = newChapter.FullArchiveFilePath;
|
||||
newJobs.Add(new MoveFileOrFolderJob(oldPath, newPath));
|
||||
}
|
||||
|
||||
if (other.Chapters.Count > 0)
|
||||
newJobs.Add(new UpdateChaptersDownloadedJob(this, 0, null, newJobs));
|
||||
|
||||
context.Jobs.AddRange(newJobs);
|
||||
context.SaveChanges();
|
||||
}
|
||||
catch (DbUpdateException e)
|
||||
{
|
||||
throw new DbUpdateException(e.Message, e.InnerException, e.Entries);
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString() => $"{base.ToString()} {Name}";
|
||||
}
|
||||
|
||||
public enum MangaReleaseStatus : byte
|
||||
{
|
||||
Continuing = 0,
|
||||
Completed = 1,
|
||||
OnHiatus = 2,
|
||||
Cancelled = 3,
|
||||
Unreleased = 4
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace API.Schema;
|
||||
|
||||
[PrimaryKey("AltTitleId")]
|
||||
public class MangaAltTitle(string language, string title)
|
||||
{
|
||||
[StringLength(64)]
|
||||
[Required]
|
||||
public string AltTitleId { get; init; } = TokenGen.CreateToken("AltTitle");
|
||||
[StringLength(8)]
|
||||
[Required]
|
||||
public string Language { get; init; } = language;
|
||||
[StringLength(256)]
|
||||
[Required]
|
||||
public string Title { get; set; } = title;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{AltTitleId} {Language} {Title}";
|
||||
}
|
||||
}
|
67
API/Schema/MangaConnectorId.cs
Normal file
67
API/Schema/MangaConnectorId.cs
Normal file
@ -0,0 +1,67 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using API.Schema.MangaConnectors;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace API.Schema;
|
||||
|
||||
[PrimaryKey("Key")]
|
||||
public class MangaConnectorId<T> : Identifiable where T : Identifiable
|
||||
{
|
||||
[StringLength(64)] [Required] public string ObjId { get; private set; } = null!;
|
||||
[JsonIgnore] private T? _obj;
|
||||
|
||||
[JsonIgnore]
|
||||
public T Obj
|
||||
{
|
||||
get => _lazyLoader.Load(this, ref _obj) ?? throw new InvalidOperationException();
|
||||
internal set
|
||||
{
|
||||
ObjId = value.Key;
|
||||
_obj = value;
|
||||
}
|
||||
}
|
||||
|
||||
[StringLength(32)] [Required] public string MangaConnectorName { get; private set; } = null!;
|
||||
[JsonIgnore] private MangaConnector? _mangaConnector;
|
||||
[JsonIgnore]
|
||||
public MangaConnector MangaConnector
|
||||
{
|
||||
get => _lazyLoader.Load(this, ref _mangaConnector) ?? throw new InvalidOperationException();
|
||||
init
|
||||
{
|
||||
MangaConnectorName = value.Name;
|
||||
_mangaConnector = value;
|
||||
}
|
||||
}
|
||||
|
||||
[StringLength(256)] [Required] public string IdOnConnectorSite { get; init; }
|
||||
[Url] [StringLength(512)] public string? WebsiteUrl { get; internal init; }
|
||||
|
||||
private readonly ILazyLoader _lazyLoader = null!;
|
||||
|
||||
public MangaConnectorId(T obj, MangaConnector mangaConnector, string idOnConnectorSite, string? websiteUrl)
|
||||
: base(TokenGen.CreateToken(typeof(MangaConnectorId<T>), mangaConnector.Name, idOnConnectorSite))
|
||||
{
|
||||
this.Obj = obj;
|
||||
this.MangaConnector = mangaConnector;
|
||||
this.IdOnConnectorSite = idOnConnectorSite;
|
||||
this.WebsiteUrl = websiteUrl;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EF CORE ONLY!!!
|
||||
/// </summary>
|
||||
public MangaConnectorId(ILazyLoader lazyLoader, string key, string objId, string mangaConnectorName, string idOnConnectorSite, string? websiteUrl)
|
||||
: base(key)
|
||||
{
|
||||
this._lazyLoader = lazyLoader;
|
||||
this.ObjId = objId;
|
||||
this.MangaConnectorName = mangaConnectorName;
|
||||
this.IdOnConnectorSite = idOnConnectorSite;
|
||||
this.WebsiteUrl = websiteUrl;
|
||||
}
|
||||
|
||||
public override string ToString() => $"{base.ToString()} {_obj}";
|
||||
}
|
@ -17,9 +17,9 @@ public class ComickIo : MangaConnector
|
||||
this.downloadClient = new HttpDownloadClient();
|
||||
}
|
||||
|
||||
public override Manga[] SearchManga(string mangaSearchName)
|
||||
public override (Manga, MangaConnectorId<Manga>)[] SearchManga(string mangaSearchName)
|
||||
{
|
||||
Log.Info($"Searching Manga: {mangaSearchName}");
|
||||
Log.Info($"Searching Obj: {mangaSearchName}");
|
||||
|
||||
List<string> slugs = new();
|
||||
int page = 1;
|
||||
@ -46,20 +46,26 @@ public class ComickIo : MangaConnector
|
||||
}
|
||||
Log.Debug($"Search {mangaSearchName} yielded {slugs.Count} slugs. Requesting mangas now...");
|
||||
|
||||
List<Manga> mangas = slugs.Select(GetMangaFromId).ToList()!;
|
||||
|
||||
List<(Manga, MangaConnectorId<Manga>)> mangas = new ();
|
||||
foreach (string slug in slugs)
|
||||
{
|
||||
if(GetMangaFromId(slug) is { } entry)
|
||||
mangas.Add(entry);
|
||||
}
|
||||
|
||||
Log.Info($"Search {mangaSearchName} yielded {mangas.Count} results.");
|
||||
return mangas.ToArray();
|
||||
}
|
||||
|
||||
private readonly Regex _getSlugFromTitleRex = new(@"https?:\/\/comick\.io\/comic\/(.+)(?:\/.*)*");
|
||||
public override Manga? GetMangaFromUrl(string url)
|
||||
public override (Manga, MangaConnectorId<Manga>)? GetMangaFromUrl(string url)
|
||||
{
|
||||
Match m = _getSlugFromTitleRex.Match(url);
|
||||
return m.Groups[1].Success ? GetMangaFromId(m.Groups[1].Value) : null;
|
||||
}
|
||||
|
||||
public override Manga? GetMangaFromId(string mangaIdOnSite)
|
||||
public override (Manga, MangaConnectorId<Manga>)? GetMangaFromId(string mangaIdOnSite)
|
||||
{
|
||||
string requestUrl = $"https://api.comick.fun/comic/{mangaIdOnSite}";
|
||||
|
||||
@ -75,14 +81,15 @@ public class ComickIo : MangaConnector
|
||||
return ParseMangaFromJToken(data);
|
||||
}
|
||||
|
||||
public override Chapter[] GetChapters(Manga manga, string? language = null)
|
||||
public override (Chapter, MangaConnectorId<Chapter>)[] GetChapters(MangaConnectorId<Manga> mangaConnectorId,
|
||||
string? language = null)
|
||||
{
|
||||
Log.Info($"Getting Chapters: {manga.IdOnConnectorSite}");
|
||||
List<Chapter> chapters = new();
|
||||
Log.Info($"Getting Chapters: {mangaConnectorId.IdOnConnectorSite}");
|
||||
List<(Chapter, MangaConnectorId<Chapter>)> chapters = new();
|
||||
int page = 1;
|
||||
while(page < 50)
|
||||
{
|
||||
string requestUrl = $"https://api.comick.fun/comic/{manga.IdOnConnectorSite}/chapters?limit=100&page={page}&lang={language}";
|
||||
string requestUrl = $"https://api.comick.fun/comic/{mangaConnectorId.IdOnConnectorSite}/chapters?limit=100&page={page}&lang={language}";
|
||||
|
||||
RequestResult result = downloadClient.MakeRequest(requestUrl, RequestType.MangaInfo);
|
||||
if ((int)result.statusCode < 200 || (int)result.statusCode >= 300)
|
||||
@ -98,7 +105,7 @@ public class ComickIo : MangaConnector
|
||||
if (chaptersArray is null || chaptersArray.Count < 1)
|
||||
break;
|
||||
|
||||
chapters.AddRange(ParseChapters(manga, chaptersArray));
|
||||
chapters.AddRange(ParseChapters(mangaConnectorId, chaptersArray));
|
||||
|
||||
page++;
|
||||
}
|
||||
@ -107,11 +114,22 @@ public class ComickIo : MangaConnector
|
||||
}
|
||||
|
||||
private readonly Regex _hidFromUrl = new(@"https?:\/\/comick\.io\/comic\/.+\/([^-]+).*");
|
||||
internal override string[] GetChapterImageUrls(Chapter chapter)
|
||||
internal override string[] GetChapterImageUrls(MangaConnectorId<Chapter> chapterId)
|
||||
{
|
||||
Match m = _hidFromUrl.Match(chapter.Url);
|
||||
if (!m.Groups[1].Success)
|
||||
|
||||
Log.Info($"Getting Chapter Image-Urls: {chapterId.Obj}");
|
||||
if (chapterId.WebsiteUrl is null || !UrlMatchesConnector(chapterId.WebsiteUrl))
|
||||
{
|
||||
Log.Debug($"Url is not for Connector. {chapterId.WebsiteUrl}");
|
||||
return [];
|
||||
}
|
||||
|
||||
Match m = _hidFromUrl.Match(chapterId.WebsiteUrl);
|
||||
if (!m.Groups[1].Success)
|
||||
{
|
||||
Log.Debug($"Could not parse hid from url. {chapterId.WebsiteUrl}");
|
||||
return [];
|
||||
}
|
||||
|
||||
string hid = m.Groups[1].Value;
|
||||
|
||||
@ -133,7 +151,7 @@ public class ComickIo : MangaConnector
|
||||
}).ToArray();
|
||||
}
|
||||
|
||||
private Manga ParseMangaFromJToken(JToken json)
|
||||
private (Manga manga, MangaConnectorId<Manga> id) ParseMangaFromJToken(JToken json)
|
||||
{
|
||||
string? hid = json["comic"]?.Value<string>("hid");
|
||||
string? slug = json["comic"]?.Value<string>("slug");
|
||||
@ -156,15 +174,15 @@ public class ComickIo : MangaConnector
|
||||
JArray? altTitlesArray = json["comic"]?["md_titles"] as JArray;
|
||||
//Cant let language be null, so fill with whatever.
|
||||
byte whatever = 0;
|
||||
List<MangaAltTitle> altTitles = altTitlesArray?
|
||||
.Select(token => new MangaAltTitle(token.Value<string>("lang")??whatever++.ToString(), token.Value<string>("title")!))
|
||||
List<AltTitle> altTitles = altTitlesArray?
|
||||
.Select(token => new AltTitle(token.Value<string>("lang")??whatever++.ToString(), token.Value<string>("title")!))
|
||||
.ToList()!;
|
||||
|
||||
JArray? authorsArray = json["authors"] as JArray;
|
||||
JArray? artistsArray = json["artists"] as JArray;
|
||||
List<Author> authors = authorsArray?.Concat(artistsArray!)
|
||||
.Select(token => new Author(token.Value<string>("name")!))
|
||||
.DistinctBy(a => a.AuthorId)
|
||||
.DistinctBy(a => a.Key)
|
||||
.ToList()!;
|
||||
|
||||
JArray? genreArray = json["comic"]?["md_comic_md_genres"] as JArray;
|
||||
@ -192,7 +210,7 @@ public class ComickIo : MangaConnector
|
||||
"al" => "AniList",
|
||||
"ap" => "Anime Planet",
|
||||
"bw" => "BookWalker",
|
||||
"mu" => "Manga Updates",
|
||||
"mu" => "Obj Updates",
|
||||
"nu" => "Novel Updates",
|
||||
"kt" => "Kitsu.io",
|
||||
"amz" => "Amazon",
|
||||
@ -211,14 +229,14 @@ public class ComickIo : MangaConnector
|
||||
if(name is null)
|
||||
throw new Exception("name is null");
|
||||
|
||||
return new Manga(hid, name, description??"", url, coverUrl, status, this,
|
||||
authors, tags, links, altTitles,
|
||||
Manga manga = new (name, description??"", coverUrl, status, authors, tags, links, altTitles,
|
||||
year: year, originalLanguage: originalLanguage);
|
||||
return (manga, new MangaConnectorId<Manga>(manga, this, hid, url));
|
||||
}
|
||||
|
||||
private List<Chapter> ParseChapters(Manga parentManga, JArray chaptersArray)
|
||||
private List<(Chapter, MangaConnectorId<Chapter>)> ParseChapters(MangaConnectorId<Manga> mcIdManga, JArray chaptersArray)
|
||||
{
|
||||
List<Chapter> chapters = new ();
|
||||
List<(Chapter, MangaConnectorId<Chapter>)> chapters = new ();
|
||||
foreach (JToken chapter in chaptersArray)
|
||||
{
|
||||
string? chapterNum = chapter.Value<string>("chap");
|
||||
@ -226,12 +244,14 @@ public class ComickIo : MangaConnector
|
||||
int? volumeNum = volumeNumStr is null ? null : int.Parse(volumeNumStr);
|
||||
string? title = chapter.Value<string>("title");
|
||||
string? hid = chapter.Value<string>("hid");
|
||||
string url = $"https://comick.io/comic/{parentManga.IdOnConnectorSite}/{hid}";
|
||||
string url = $"https://comick.io/comic/{mcIdManga.IdOnConnectorSite}/{hid}";
|
||||
|
||||
if(chapterNum is null || hid is null)
|
||||
continue;
|
||||
|
||||
Chapter ch = new (mcIdManga.Obj, chapterNum, volumeNum, title);
|
||||
|
||||
chapters.Add(new (parentManga, url, chapterNum, volumeNum, hid, title));
|
||||
chapters.Add((ch, new (ch, this, hid, url)));
|
||||
}
|
||||
return chapters;
|
||||
}
|
||||
|
@ -10,14 +10,14 @@ public class Global : MangaConnector
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public override Manga[] SearchManga(string mangaSearchName)
|
||||
public override (Manga, MangaConnectorId<Manga>)[] SearchManga(string mangaSearchName)
|
||||
{
|
||||
//Get all enabled Connectors
|
||||
MangaConnector[] enabledConnectors = context.MangaConnectors.Where(c => c.Enabled && c.Name != "Global").ToArray();
|
||||
|
||||
//Create Task for each MangaConnector to search simulatneously
|
||||
Task<Manga[]>[] tasks =
|
||||
enabledConnectors.Select(c => new Task<Manga[]>(() => c.SearchManga(mangaSearchName))).ToArray();
|
||||
//Create Task for each MangaConnector to search simultaneously
|
||||
Task<(Manga, MangaConnectorId<Manga>)[]>[] tasks =
|
||||
enabledConnectors.Select(c => new Task<(Manga, MangaConnectorId<Manga>)[]>(() => c.SearchManga(mangaSearchName))).ToArray();
|
||||
foreach (var task in tasks)
|
||||
task.Start();
|
||||
|
||||
@ -28,28 +28,29 @@ public class Global : MangaConnector
|
||||
}while(tasks.Any(t => t.Status < TaskStatus.RanToCompletion));
|
||||
|
||||
//Concatenate all results into one
|
||||
Manga[] ret = tasks.Select(t => t.IsCompletedSuccessfully ? t.Result : []).ToArray().SelectMany(i => i).ToArray();
|
||||
(Manga, MangaConnectorId<Manga>)[] ret = tasks.Select(t => t.IsCompletedSuccessfully ? t.Result : []).ToArray().SelectMany(i => i).ToArray();
|
||||
return ret;
|
||||
}
|
||||
|
||||
public override Manga? GetMangaFromUrl(string url)
|
||||
public override (Manga, MangaConnectorId<Manga>)? GetMangaFromUrl(string url)
|
||||
{
|
||||
MangaConnector? mc = context.MangaConnectors.ToArray().FirstOrDefault(c => c.UrlMatchesConnector(url));
|
||||
return mc?.GetMangaFromUrl(url) ?? null;
|
||||
}
|
||||
|
||||
public override Manga? GetMangaFromId(string mangaIdOnSite)
|
||||
public override (Manga, MangaConnectorId<Manga>)? GetMangaFromId(string mangaIdOnSite)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public override Chapter[] GetChapters(Manga manga, string? language = null)
|
||||
public override (Chapter, MangaConnectorId<Chapter>)[] GetChapters(MangaConnectorId<Manga> manga,
|
||||
string? language = null)
|
||||
{
|
||||
return manga.MangaConnector.GetChapters(manga, language);
|
||||
}
|
||||
|
||||
internal override string[] GetChapterImageUrls(Chapter chapter)
|
||||
internal override string[] GetChapterImageUrls(MangaConnectorId<Chapter> chapterId)
|
||||
{
|
||||
return chapter.ParentManga.MangaConnector.GetChapterImageUrls(chapter);
|
||||
return chapterId.MangaConnector.GetChapterImageUrls(chapterId);
|
||||
}
|
||||
}
|
@ -34,35 +34,36 @@ public abstract class MangaConnector(string name, string[] supportedLanguages, s
|
||||
[Required]
|
||||
public bool Enabled { get; internal set; } = true;
|
||||
|
||||
public abstract Manga[] SearchManga(string mangaSearchName);
|
||||
public abstract (Manga, MangaConnectorId<Manga>)[] SearchManga(string mangaSearchName);
|
||||
|
||||
public abstract Manga? GetMangaFromUrl(string url);
|
||||
public abstract (Manga, MangaConnectorId<Manga>)? GetMangaFromUrl(string url);
|
||||
|
||||
public abstract Manga? GetMangaFromId(string mangaIdOnSite);
|
||||
public abstract (Manga, MangaConnectorId<Manga>)? GetMangaFromId(string mangaIdOnSite);
|
||||
|
||||
public abstract Chapter[] GetChapters(Manga manga, string? language = null);
|
||||
public abstract (Chapter, MangaConnectorId<Chapter>)[] GetChapters(MangaConnectorId<Manga> mangaId,
|
||||
string? language = null);
|
||||
|
||||
internal abstract string[] GetChapterImageUrls(Chapter chapter);
|
||||
internal abstract string[] GetChapterImageUrls(MangaConnectorId<Chapter> chapterId);
|
||||
|
||||
public bool UrlMatchesConnector(string url) => BaseUris.Any(baseUri => Regex.IsMatch(url, "https?://" + baseUri + "/.*"));
|
||||
|
||||
internal string? SaveCoverImageToCache(Manga manga, int retries = 3)
|
||||
internal string? SaveCoverImageToCache(MangaConnectorId<Manga> mangaId, int retries = 3)
|
||||
{
|
||||
if(retries < 0)
|
||||
return null;
|
||||
|
||||
Regex urlRex = new (@"https?:\/\/((?:[a-zA-Z0-9-]+\.)+[a-zA-Z0-9]+)\/(?:.+\/)*(.+\.([a-zA-Z]+))");
|
||||
//https?:\/\/[a-zA-Z0-9-]+\.([a-zA-Z0-9-]+\.[a-zA-Z0-9]+)\/(?:.+\/)*(.+\.([a-zA-Z]+)) for only second level domains
|
||||
Match match = urlRex.Match(manga.CoverUrl);
|
||||
string filename = $"{match.Groups[1].Value}-{manga.MangaId}.{match.Groups[3].Value}";
|
||||
Match match = urlRex.Match(mangaId.Obj.CoverUrl);
|
||||
string filename = $"{match.Groups[1].Value}-{mangaId.Key}.{match.Groups[3].Value}";
|
||||
string saveImagePath = Path.Join(TrangaSettings.coverImageCache, filename);
|
||||
|
||||
if (File.Exists(saveImagePath))
|
||||
return saveImagePath;
|
||||
|
||||
RequestResult coverResult = downloadClient.MakeRequest(manga.CoverUrl, RequestType.MangaCover, $"https://{match.Groups[1].Value}");
|
||||
RequestResult coverResult = downloadClient.MakeRequest(mangaId.Obj.CoverUrl, RequestType.MangaCover, $"https://{match.Groups[1].Value}");
|
||||
if ((int)coverResult.statusCode < 200 || (int)coverResult.statusCode >= 300)
|
||||
return SaveCoverImageToCache(manga, --retries);
|
||||
return SaveCoverImageToCache(mangaId, --retries);
|
||||
|
||||
using MemoryStream ms = new();
|
||||
coverResult.result.CopyTo(ms);
|
||||
|
@ -18,10 +18,10 @@ public class MangaDex : MangaConnector
|
||||
}
|
||||
|
||||
private const int Limit = 100;
|
||||
public override Manga[] SearchManga(string mangaSearchName)
|
||||
public override (Manga, MangaConnectorId<Manga>)[] SearchManga(string mangaSearchName)
|
||||
{
|
||||
Log.Info($"Searching Manga: {mangaSearchName}");
|
||||
List<Manga> mangas = new ();
|
||||
Log.Info($"Searching Obj: {mangaSearchName}");
|
||||
List<(Manga, MangaConnectorId<Manga>)> mangas = new ();
|
||||
|
||||
int offset = 0;
|
||||
int total = int.MaxValue;
|
||||
@ -67,9 +67,9 @@ public class MangaDex : MangaConnector
|
||||
}
|
||||
|
||||
private static readonly Regex GetMangaIdFromUrl = new(@"https?:\/\/mangadex\.org\/title\/([a-z0-9-]+)\/?.*");
|
||||
public override Manga? GetMangaFromUrl(string url)
|
||||
public override (Manga, MangaConnectorId<Manga>)? GetMangaFromUrl(string url)
|
||||
{
|
||||
Log.Info($"Getting Manga: {url}");
|
||||
Log.Info($"Getting Obj: {url}");
|
||||
if (!UrlMatchesConnector(url))
|
||||
{
|
||||
Log.Debug($"Url is not for Connector. {url}");
|
||||
@ -87,9 +87,9 @@ public class MangaDex : MangaConnector
|
||||
return GetMangaFromId(id);
|
||||
}
|
||||
|
||||
public override Manga? GetMangaFromId(string mangaIdOnSite)
|
||||
public override (Manga, MangaConnectorId<Manga>)? GetMangaFromId(string mangaIdOnSite)
|
||||
{
|
||||
Log.Info($"Getting Manga: {mangaIdOnSite}");
|
||||
Log.Info($"Getting Obj: {mangaIdOnSite}");
|
||||
string requestUrl =
|
||||
$"https://api.mangadex.org/manga/{mangaIdOnSite}" +
|
||||
$"?includes%5B%5D=manga&includes%5B%5D=cover_art&includes%5B%5D=author&includes%5B%5D=artist&includes%5B%5D=tag'";
|
||||
@ -118,14 +118,13 @@ public class MangaDex : MangaConnector
|
||||
return null;
|
||||
}
|
||||
|
||||
Manga manga = ParseMangaFromJToken(data);
|
||||
return manga;
|
||||
return ParseMangaFromJToken(data);
|
||||
}
|
||||
|
||||
public override Chapter[] GetChapters(Manga manga, string? language = null)
|
||||
public override (Chapter, MangaConnectorId<Chapter>)[] GetChapters(MangaConnectorId<Manga> manga, string? language = null)
|
||||
{
|
||||
Log.Info($"Getting Chapters: {manga.IdOnConnectorSite}");
|
||||
List<Chapter> chapters = new ();
|
||||
List<(Chapter, MangaConnectorId<Chapter>)> chapters = new ();
|
||||
|
||||
int offset = 0;
|
||||
int total = int.MaxValue;
|
||||
@ -166,24 +165,24 @@ public class MangaDex : MangaConnector
|
||||
chapters.AddRange(data.Select(d => ParseChapterFromJToken(manga, d)));
|
||||
}
|
||||
|
||||
Log.Info($"Request for chapters for {manga.Name} yielded {chapters.Count} results.");
|
||||
Log.Info($"Request for chapters for {manga.Obj.Name} yielded {chapters.Count} results.");
|
||||
return chapters.ToArray();
|
||||
}
|
||||
|
||||
private static readonly Regex GetChapterIdFromUrl = new(@"https?:\/\/mangadex\.org\/chapter\/([a-z0-9-]+)\/?.*");
|
||||
internal override string[] GetChapterImageUrls(Chapter chapter)
|
||||
internal override string[] GetChapterImageUrls(MangaConnectorId<Chapter> chapterId)
|
||||
{
|
||||
Log.Info($"Getting Chapter Image-Urls: {chapter.Url}");
|
||||
if (!UrlMatchesConnector(chapter.Url))
|
||||
Log.Info($"Getting Chapter Image-Urls: {chapterId.Obj}");
|
||||
if (chapterId.WebsiteUrl is null || !UrlMatchesConnector(chapterId.WebsiteUrl))
|
||||
{
|
||||
Log.Debug($"Url is not for Connector. {chapter.Url}");
|
||||
Log.Debug($"Url is not for Connector. {chapterId.WebsiteUrl}");
|
||||
return [];
|
||||
}
|
||||
|
||||
Match match = GetChapterIdFromUrl.Match(chapter.Url);
|
||||
Match match = GetChapterIdFromUrl.Match(chapterId.WebsiteUrl);
|
||||
if (!match.Success || !match.Groups[1].Success)
|
||||
{
|
||||
Log.Debug($"Url is not for Connector (Could not retrieve id). {chapter.Url}");
|
||||
Log.Debug($"Url is not for Connector (Could not retrieve id). {chapterId.WebsiteUrl}");
|
||||
return [];
|
||||
}
|
||||
|
||||
@ -223,7 +222,7 @@ public class MangaDex : MangaConnector
|
||||
return urls.ToArray();
|
||||
}
|
||||
|
||||
private Manga ParseMangaFromJToken(JToken jToken)
|
||||
private (Manga manga, MangaConnectorId<Manga> id) ParseMangaFromJToken(JToken jToken)
|
||||
{
|
||||
string? id = jToken.Value<string>("id");
|
||||
if(id is null)
|
||||
@ -267,7 +266,7 @@ public class MangaDex : MangaConnector
|
||||
"al" => "AniList",
|
||||
"ap" => "Anime Planet",
|
||||
"bw" => "BookWalker",
|
||||
"mu" => "Manga Updates",
|
||||
"mu" => "Obj Updates",
|
||||
"nu" => "Novel Updates",
|
||||
"kt" => "Kitsu.io",
|
||||
"amz" => "Amazon",
|
||||
@ -279,14 +278,14 @@ public class MangaDex : MangaConnector
|
||||
return new Link(key, url);
|
||||
}).ToList()!;
|
||||
|
||||
List<MangaAltTitle> altTitles = (altTitlesJArray??[])
|
||||
List<AltTitle> altTitles = (altTitlesJArray??[])
|
||||
.Select(t =>
|
||||
{
|
||||
JObject? j = t as JObject;
|
||||
JProperty? p = j?.Properties().First();
|
||||
if (p is null)
|
||||
return null;
|
||||
return new MangaAltTitle(p.Name, p.Value.ToString());
|
||||
return new AltTitle(p.Name, p.Value.ToString());
|
||||
}).Where(x => x is not null).ToList()!;
|
||||
|
||||
List<MangaTag> tags = (tagsJArray??[])
|
||||
@ -313,26 +312,27 @@ public class MangaDex : MangaConnector
|
||||
string websiteUrl = $"https://mangadex.org/title/{id}";
|
||||
string coverUrl = $"https://uploads.mangadex.org/covers/{id}/{coverFileName}";
|
||||
|
||||
return new Manga(id, name, description, websiteUrl, coverUrl, releaseStatus, this,
|
||||
authors, tags, links,altTitles,
|
||||
Manga manga = new Manga(name, description, coverUrl, releaseStatus, authors, tags, links,altTitles,
|
||||
null, 0f, year, originalLanguage);
|
||||
return (manga, new MangaConnectorId<Manga>(manga, this, id, websiteUrl));
|
||||
}
|
||||
|
||||
private Chapter ParseChapterFromJToken(Manga parentManga, JToken jToken)
|
||||
private (Chapter chapter, MangaConnectorId<Chapter> id) ParseChapterFromJToken(MangaConnectorId<Manga> mcIdManga, JToken jToken)
|
||||
{
|
||||
string? id = jToken.Value<string>("id");
|
||||
JToken? attributes = jToken["attributes"];
|
||||
string? chapter = attributes?.Value<string>("chapter");
|
||||
string? chapterStr = attributes?.Value<string>("chapter");
|
||||
string? volumeStr = attributes?.Value<string>("volume");
|
||||
int? volume = null;
|
||||
int? volumeNumber = null;
|
||||
string? title = attributes?.Value<string>("title");
|
||||
|
||||
if(id is null || chapter is null)
|
||||
if(id is null || chapterStr is null)
|
||||
throw new Exception("jToken was not in expected format");
|
||||
if(volumeStr is not null)
|
||||
volume = int.Parse(volumeStr);
|
||||
volumeNumber = int.Parse(volumeStr);
|
||||
|
||||
string url = $"https://mangadex.org/chapter/{id}";
|
||||
return new Chapter(parentManga, url, chapter, volume, id, title);
|
||||
string websiteUrl = $"https://mangadex.org/chapter/{id}";
|
||||
Chapter chapter = new (mcIdManga.Obj, chapterStr, volumeNumber, title);
|
||||
return (chapter, new MangaConnectorId<Chapter>(chapter, this, id, websiteUrl));
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
namespace API.Schema;
|
||||
|
||||
public enum MangaReleaseStatus : byte
|
||||
{
|
||||
Continuing = 0,
|
||||
Completed = 1,
|
||||
OnHiatus = 2,
|
||||
Cancelled = 3,
|
||||
Unreleased = 4
|
||||
}
|
35
API/Schema/MetadataFetchers/MetadataEntry.cs
Normal file
35
API/Schema/MetadataFetchers/MetadataEntry.cs
Normal file
@ -0,0 +1,35 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace API.Schema.MetadataFetchers;
|
||||
|
||||
[PrimaryKey("MetadataFetcherName", "Identifier")]
|
||||
public class MetadataEntry
|
||||
{
|
||||
[JsonIgnore]
|
||||
public Manga Manga { get; init; } = null!;
|
||||
public string MangaId { get; init; }
|
||||
[JsonIgnore]
|
||||
public MetadataFetcher MetadataFetcher { get; init; } = null!;
|
||||
public string MetadataFetcherName { get; init; }
|
||||
public string Identifier { get; init; }
|
||||
|
||||
public MetadataEntry(MetadataFetcher fetcher, Manga manga, string identifier)
|
||||
{
|
||||
this.Manga = manga;
|
||||
this.MangaId = manga.Key;
|
||||
this.MetadataFetcher = fetcher;
|
||||
this.MetadataFetcherName = fetcher.MetadataFetcherName;
|
||||
this.Identifier = identifier;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EFCORE only!!!!
|
||||
/// </summary>
|
||||
internal MetadataEntry(string mangaId, string identifier, string metadataFetcherName)
|
||||
{
|
||||
this.MangaId = mangaId;
|
||||
this.Identifier = identifier;
|
||||
this.MetadataFetcherName = metadataFetcherName;
|
||||
}
|
||||
}
|
37
API/Schema/MetadataFetchers/MetadataFetcher.cs
Normal file
37
API/Schema/MetadataFetchers/MetadataFetcher.cs
Normal file
@ -0,0 +1,37 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using API.Schema.Contexts;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace API.Schema.MetadataFetchers;
|
||||
|
||||
[PrimaryKey("MetadataFetcherName")]
|
||||
public abstract class MetadataFetcher
|
||||
{
|
||||
// ReSharper disable once EntityFramework.ModelValidation.UnlimitedStringLength
|
||||
public string MetadataFetcherName { get; init; }
|
||||
|
||||
protected MetadataFetcher()
|
||||
{
|
||||
this.MetadataFetcherName = this.GetType().Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EFCORE ONLY!!!
|
||||
/// </summary>
|
||||
internal MetadataFetcher(string metadataFetcherName)
|
||||
{
|
||||
this.MetadataFetcherName = metadataFetcherName;
|
||||
}
|
||||
|
||||
internal MetadataEntry CreateMetadataEntry(Manga manga, string identifier) =>
|
||||
new (this, manga, identifier);
|
||||
|
||||
public abstract MetadataSearchResult[] SearchMetadataEntry(Manga manga);
|
||||
|
||||
public abstract MetadataSearchResult[] SearchMetadataEntry(string searchTerm);
|
||||
|
||||
/// <summary>
|
||||
/// Updates the Manga linked in the MetadataEntry
|
||||
/// </summary>
|
||||
public abstract void UpdateMetadata(MetadataEntry metadataEntry, PgsqlContext dbContext);
|
||||
}
|
3
API/Schema/MetadataFetchers/MetadataSearchResult.cs
Normal file
3
API/Schema/MetadataFetchers/MetadataSearchResult.cs
Normal file
@ -0,0 +1,3 @@
|
||||
namespace API.Schema.MetadataFetchers;
|
||||
|
||||
public record MetadataSearchResult(string Identifier, string Name, string Url, string? Description = null, string? CoverUrl = null);
|
79
API/Schema/MetadataFetchers/MyAnimeList.cs
Normal file
79
API/Schema/MetadataFetchers/MyAnimeList.cs
Normal file
@ -0,0 +1,79 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using API.Schema.Contexts;
|
||||
using JikanDotNet;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace API.Schema.MetadataFetchers;
|
||||
|
||||
public class MyAnimeList : MetadataFetcher
|
||||
{
|
||||
private static readonly Jikan Jikan = new ();
|
||||
private static readonly Regex GetIdFromUrl = new(@"https?:\/\/myanimelist\.net\/manga\/([0-9]+)\/?.*");
|
||||
|
||||
public override MetadataSearchResult[] SearchMetadataEntry(Manga manga)
|
||||
{
|
||||
if (manga.Links.Any(link => link.LinkProvider.Equals("MyAnimeList", StringComparison.InvariantCultureIgnoreCase)))
|
||||
{
|
||||
string url = manga.Links.First(link => link.LinkProvider.Equals("MyAnimeList", StringComparison.InvariantCultureIgnoreCase)).LinkUrl;
|
||||
Match m = GetIdFromUrl.Match(url);
|
||||
if (m.Success && m.Groups[1].Success)
|
||||
{
|
||||
long id = long.Parse(m.Groups[1].Value);
|
||||
JikanDotNet.Manga data = Jikan.GetMangaAsync(id).Result.Data;
|
||||
return [new MetadataSearchResult(id.ToString(), data.Titles.First().Title, data.Url, data.Synopsis)];
|
||||
}
|
||||
}
|
||||
|
||||
return SearchMetadataEntry(manga.Name);
|
||||
}
|
||||
|
||||
public override MetadataSearchResult[] SearchMetadataEntry(string searchTerm)
|
||||
{
|
||||
|
||||
ICollection<JikanDotNet.Manga> resultData = Jikan.SearchMangaAsync(searchTerm).Result.Data;
|
||||
if (resultData.Count < 1)
|
||||
return [];
|
||||
return resultData.Select(data =>
|
||||
new MetadataSearchResult(data.MalId.ToString(), data.Titles.First().Title, data.Url, data.Synopsis))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the Manga linked in the MetadataEntry
|
||||
/// </summary>
|
||||
/// <param name="metadataEntry"></param>
|
||||
/// <param name="dbContext"></param>
|
||||
/// <exception cref="FormatException"></exception>
|
||||
/// <exception cref="DbUpdateException"></exception>
|
||||
public override void UpdateMetadata(MetadataEntry metadataEntry, PgsqlContext dbContext)
|
||||
{
|
||||
Manga dbManga = dbContext.Mangas.Find(metadataEntry.MangaId)!;
|
||||
MangaFull resultData;
|
||||
try
|
||||
{
|
||||
long id = long.Parse(metadataEntry.Identifier);
|
||||
resultData = Jikan.GetMangaFullDataAsync(id).Result.Data;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw new FormatException("ID was not in correct format");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
dbManga.Name = resultData.Titles.First().Title;
|
||||
dbManga.Description = resultData.Synopsis;
|
||||
dbManga.AltTitles.Clear();
|
||||
dbManga.AltTitles = resultData.Titles.Select(t => new AltTitle(t.Type, t.Title)).ToList();
|
||||
dbManga.Authors.Clear();
|
||||
dbManga.Authors = resultData.Authors.Select(a => new Author(a.Name)).ToList();
|
||||
|
||||
dbContext.SaveChanges();
|
||||
}
|
||||
catch (DbUpdateException e)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
using API.Schema.Contexts;
|
||||
using API.Schema.Jobs;
|
||||
using API.Schema.MangaConnectors;
|
||||
using API.Schema.MetadataFetchers;
|
||||
using API.Schema.NotificationConnectors;
|
||||
using log4net;
|
||||
using log4net.Config;
|
||||
@ -24,6 +25,7 @@ public static class Tranga
|
||||
public static Thread NotificationSenderThread { get; } = new (NotificationSender);
|
||||
public static Thread JobStarterThread { get; } = new (JobStarter);
|
||||
private static readonly ILog Log = LogManager.GetLogger(typeof(Tranga));
|
||||
internal static MetadataFetcher[] MetadataFetchers = [new MyAnimeList()];
|
||||
|
||||
internal static void StartLogger()
|
||||
{
|
||||
@ -137,18 +139,7 @@ public static class Tranga
|
||||
List<Job> dueJobs = waitingJobs.FilterDueJobs();
|
||||
List<Job> jobsWithoutDependencies = dueJobs.FilterJobDependencies();
|
||||
|
||||
List<Job> jobsWithoutDownloading = jobsWithoutDependencies.FilterJobsWithoutDownloading();
|
||||
|
||||
//Match running and waiting jobs per Connector
|
||||
Dictionary<string, Dictionary<JobType, List<Job>>> runningJobsPerConnector =
|
||||
runningJobs.GetJobsPerJobTypeAndConnector();
|
||||
Dictionary<string, Dictionary<JobType, List<Job>>> waitingJobsPerConnector =
|
||||
jobsWithoutDependencies.GetJobsPerJobTypeAndConnector();
|
||||
List<Job> jobsNotHeldBackByConnector =
|
||||
MatchJobsRunningAndWaiting(runningJobsPerConnector, waitingJobsPerConnector);
|
||||
|
||||
|
||||
List<Job> startJobs = jobsWithoutDownloading.Concat(jobsNotHeldBackByConnector).ToList();
|
||||
List<Job> startJobs = dueJobs;
|
||||
Log.Debug($"Jobs Filtered! (took {DateTime.UtcNow.Subtract(filterStart).TotalMilliseconds}ms)");
|
||||
|
||||
|
||||
@ -160,7 +151,7 @@ public static class Tranga
|
||||
{
|
||||
using IServiceScope jobScope = serviceProvider.CreateScope();
|
||||
PgsqlContext jobContext = jobScope.ServiceProvider.GetRequiredService<PgsqlContext>();
|
||||
if (jobContext.Jobs.Find(job.JobId) is not { } inContext)
|
||||
if (jobContext.Jobs.Find(job.Key) is not { } inContext)
|
||||
return;
|
||||
inContext.Run(jobContext, ref running); //FIND the job IN THE NEW CONTEXT!!!!!!! SO WE DON'T GET TRACKING PROBLEMS AND AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
});
|
||||
@ -174,14 +165,12 @@ public static class Tranga
|
||||
$"Waiting: {waitingJobs.Count} Due: {dueJobs.Count}\n" +
|
||||
$"{string.Join("\n", dueJobs.Select(s => "\t- " + s))}\n" +
|
||||
$"of which {jobsWithoutDependencies.Count} without missing dependencies, of which\n" +
|
||||
$"\t{jobsWithoutDownloading.Count} without downloading\n" +
|
||||
$"\t{jobsNotHeldBackByConnector.Count} not held back by Connector\n" +
|
||||
$"{startJobs.Count} were started:\n" +
|
||||
$"{string.Join("\n", startJobs.Select(s => "\t- " + s))}");
|
||||
|
||||
if (Log.IsDebugEnabled && dueJobs.Count < 1)
|
||||
if(waitingJobs.MinBy(j => j.NextExecution) is { } nextJob)
|
||||
Log.Debug($"Next job in {nextJob.NextExecution.Subtract(DateTime.UtcNow)} (at {nextJob.NextExecution}): {nextJob.JobId}");
|
||||
Log.Debug($"Next job in {nextJob.NextExecution.Subtract(DateTime.UtcNow)} (at {nextJob.NextExecution}): {nextJob.Key}");
|
||||
|
||||
(Thread, Job)[] removeFromThreadsList = RunningJobs.Where(t => !t.Key.IsAlive)
|
||||
.Select(t => (t.Key, t.Value)).ToArray();
|
||||
@ -252,26 +241,6 @@ public static class Tranga
|
||||
Log.Debug($"Filtering Jobs without Download took {end.Subtract(start).TotalMilliseconds}ms");
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
private static Dictionary<string, Dictionary<JobType, List<Job>>> GetJobsPerJobTypeAndConnector(this List<Job> jobs)
|
||||
{
|
||||
DateTime start = DateTime.UtcNow;
|
||||
Dictionary<string, Dictionary<JobType, List<Job>>> ret = new();
|
||||
foreach (Job job in jobs)
|
||||
{
|
||||
if(GetJobConnectorName(job) is not { } connector)
|
||||
continue;
|
||||
if (!ret.ContainsKey(connector))
|
||||
ret.Add(connector, new());
|
||||
if (!ret[connector].ContainsKey(job.JobType))
|
||||
ret[connector].Add(job.JobType, new());
|
||||
ret[connector][job.JobType].Add(job);
|
||||
}
|
||||
DateTime end = DateTime.UtcNow;
|
||||
Log.Debug($"Fetching connector per Job for jobs took {end.Subtract(start).TotalMilliseconds}ms");
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static List<Job> MatchJobsRunningAndWaiting(Dictionary<string, Dictionary<JobType, List<Job>>> running,
|
||||
Dictionary<string, Dictionary<JobType, List<Job>>> waiting)
|
||||
@ -321,18 +290,4 @@ public static class Tranga
|
||||
Log.Debug($"Getting eligible jobs (not held back by Connector) took {end.Subtract(start).TotalMilliseconds}ms");
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
private static string? GetJobConnectorName(Job job)
|
||||
{
|
||||
if (job is DownloadAvailableChaptersJob dacj)
|
||||
return dacj.Manga.MangaConnectorName;
|
||||
if (job is DownloadMangaCoverJob dmcj)
|
||||
return dmcj.Manga.MangaConnectorName;
|
||||
if (job is DownloadSingleChapterJob dscj)
|
||||
return dscj.Chapter.ParentManga.MangaConnectorName;
|
||||
if (job is RetrieveChaptersJob rcj)
|
||||
return rcj.Manga.MangaConnectorName;
|
||||
return null;
|
||||
}
|
||||
}
|
@ -8,7 +8,7 @@ namespace API;
|
||||
|
||||
public static class TrangaSettings
|
||||
{
|
||||
public static string downloadLocation { get; private set; } = (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "/Manga" : Path.Join(Directory.GetCurrentDirectory(), "Downloads"));
|
||||
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]
|
||||
internal static readonly string DefaultUserAgent = $"Tranga/2.0 ({Enum.GetName(Environment.OSVersion.Platform)}; {(Environment.Is64BitOperatingSystem ? "x64" : "")})";
|
||||
@ -18,14 +18,14 @@ public static class TrangaSettings
|
||||
public static string flareSolverrUrl { get; private set; } = string.Empty;
|
||||
/// <summary>
|
||||
/// Placeholders:
|
||||
/// %M Manga Name
|
||||
/// %M Obj Name
|
||||
/// %V Volume
|
||||
/// %C Chapter
|
||||
/// %T Title
|
||||
/// %A Author (first in list)
|
||||
/// %I Chapter Internal ID
|
||||
/// %i Manga Internal ID
|
||||
/// %Y Year (Manga)
|
||||
/// %i Obj Internal ID
|
||||
/// %Y Year (Obj)
|
||||
///
|
||||
/// ?_(...) replace _ with a value from above:
|
||||
/// Everything inside the braces will only be added if the value of %_ is not null
|
||||
|
BIN
DB-Layout.png
Normal file
BIN
DB-Layout.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 37 KiB |
460
DB-Layout.uxf
Normal file
460
DB-Layout.uxf
Normal file
@ -0,0 +1,460 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<diagram program="umlet" version="15.1">
|
||||
<zoom_level>10</zoom_level>
|
||||
<element>
|
||||
<id>UMLClass</id>
|
||||
<coordinates>
|
||||
<x>1160</x>
|
||||
<y>680</y>
|
||||
<w>100</w>
|
||||
<h>40</h>
|
||||
</coordinates>
|
||||
<panel_attributes>Manga</panel_attributes>
|
||||
<additional_attributes/>
|
||||
</element>
|
||||
<element>
|
||||
<id>UMLClass</id>
|
||||
<coordinates>
|
||||
<x>900</x>
|
||||
<y>680</y>
|
||||
<w>140</w>
|
||||
<h>40</h>
|
||||
</coordinates>
|
||||
<panel_attributes>MangaConnector</panel_attributes>
|
||||
<additional_attributes/>
|
||||
</element>
|
||||
<element>
|
||||
<id>UMLClass</id>
|
||||
<coordinates>
|
||||
<x>500</x>
|
||||
<y>800</y>
|
||||
<w>80</w>
|
||||
<h>40</h>
|
||||
</coordinates>
|
||||
<panel_attributes>/Job/</panel_attributes>
|
||||
<additional_attributes/>
|
||||
</element>
|
||||
<element>
|
||||
<id>UMLClass</id>
|
||||
<coordinates>
|
||||
<x>680</x>
|
||||
<y>800</y>
|
||||
<w>160</w>
|
||||
<h>40</h>
|
||||
</coordinates>
|
||||
<panel_attributes>/JobWithDownload/</panel_attributes>
|
||||
<additional_attributes/>
|
||||
</element>
|
||||
<element>
|
||||
<id>UMLClass</id>
|
||||
<coordinates>
|
||||
<x>680</x>
|
||||
<y>680</y>
|
||||
<w>160</w>
|
||||
<h>40</h>
|
||||
</coordinates>
|
||||
<panel_attributes>RetrieveChaptersJob
|
||||
</panel_attributes>
|
||||
<additional_attributes/>
|
||||
</element>
|
||||
<element>
|
||||
<id>Relation</id>
|
||||
<coordinates>
|
||||
<x>570</x>
|
||||
<y>810</y>
|
||||
<w>130</w>
|
||||
<h>30</h>
|
||||
</coordinates>
|
||||
<panel_attributes>lt=<<-</panel_attributes>
|
||||
<additional_attributes>10.0;10.0;110.0;10.0</additional_attributes>
|
||||
</element>
|
||||
<element>
|
||||
<id>Relation</id>
|
||||
<coordinates>
|
||||
<x>750</x>
|
||||
<y>710</y>
|
||||
<w>30</w>
|
||||
<h>110</h>
|
||||
</coordinates>
|
||||
<panel_attributes>lt=<<-</panel_attributes>
|
||||
<additional_attributes>10.0;90.0;10.0;10.0</additional_attributes>
|
||||
</element>
|
||||
<element>
|
||||
<id>Relation</id>
|
||||
<coordinates>
|
||||
<x>1170</x>
|
||||
<y>710</y>
|
||||
<w>30</w>
|
||||
<h>230</h>
|
||||
</coordinates>
|
||||
<panel_attributes>lt=-</panel_attributes>
|
||||
<additional_attributes>10.0;210.0;10.0;10.0</additional_attributes>
|
||||
</element>
|
||||
<element>
|
||||
<id>Relation</id>
|
||||
<coordinates>
|
||||
<x>1030</x>
|
||||
<y>820</y>
|
||||
<w>150</w>
|
||||
<h>140</h>
|
||||
</coordinates>
|
||||
<panel_attributes>lt=-</panel_attributes>
|
||||
<additional_attributes>130.0;120.0;70.0;120.0;70.0;10.0;10.0;10.0</additional_attributes>
|
||||
</element>
|
||||
<element>
|
||||
<id>Relation</id>
|
||||
<coordinates>
|
||||
<x>960</x>
|
||||
<y>710</y>
|
||||
<w>30</w>
|
||||
<h>110</h>
|
||||
</coordinates>
|
||||
<panel_attributes>lt=-</panel_attributes>
|
||||
<additional_attributes>10.0;90.0;10.0;10.0</additional_attributes>
|
||||
</element>
|
||||
<element>
|
||||
<id>Relation</id>
|
||||
<coordinates>
|
||||
<x>830</x>
|
||||
<y>810</y>
|
||||
<w>90</w>
|
||||
<h>30</h>
|
||||
</coordinates>
|
||||
<panel_attributes>lt=-</panel_attributes>
|
||||
<additional_attributes>70.0;10.0;10.0;10.0</additional_attributes>
|
||||
</element>
|
||||
<element>
|
||||
<id>UMLClass</id>
|
||||
<coordinates>
|
||||
<x>1410</x>
|
||||
<y>680</y>
|
||||
<w>100</w>
|
||||
<h>40</h>
|
||||
</coordinates>
|
||||
<panel_attributes>FileLibrary</panel_attributes>
|
||||
<additional_attributes/>
|
||||
</element>
|
||||
<element>
|
||||
<id>Relation</id>
|
||||
<coordinates>
|
||||
<x>1250</x>
|
||||
<y>690</y>
|
||||
<w>180</w>
|
||||
<h>30</h>
|
||||
</coordinates>
|
||||
<panel_attributes>lt=-</panel_attributes>
|
||||
<additional_attributes>160.0;10.0;10.0;10.0</additional_attributes>
|
||||
</element>
|
||||
<element>
|
||||
<id>UMLClass</id>
|
||||
<coordinates>
|
||||
<x>1410</x>
|
||||
<y>620</y>
|
||||
<w>100</w>
|
||||
<h>40</h>
|
||||
</coordinates>
|
||||
<panel_attributes>Link</panel_attributes>
|
||||
<additional_attributes/>
|
||||
</element>
|
||||
<element>
|
||||
<id>UMLClass</id>
|
||||
<coordinates>
|
||||
<x>1410</x>
|
||||
<y>560</y>
|
||||
<w>100</w>
|
||||
<h>40</h>
|
||||
</coordinates>
|
||||
<panel_attributes>Author</panel_attributes>
|
||||
<additional_attributes/>
|
||||
</element>
|
||||
<element>
|
||||
<id>UMLClass</id>
|
||||
<coordinates>
|
||||
<x>1410</x>
|
||||
<y>500</y>
|
||||
<w>100</w>
|
||||
<h>40</h>
|
||||
</coordinates>
|
||||
<panel_attributes>MangaTag</panel_attributes>
|
||||
<additional_attributes/>
|
||||
</element>
|
||||
<element>
|
||||
<id>Relation</id>
|
||||
<coordinates>
|
||||
<x>1200</x>
|
||||
<y>510</y>
|
||||
<w>230</w>
|
||||
<h>190</h>
|
||||
</coordinates>
|
||||
<panel_attributes>lt=-</panel_attributes>
|
||||
<additional_attributes>210.0;10.0;10.0;10.0;10.0;170.0</additional_attributes>
|
||||
</element>
|
||||
<element>
|
||||
<id>Relation</id>
|
||||
<coordinates>
|
||||
<x>1230</x>
|
||||
<y>570</y>
|
||||
<w>200</w>
|
||||
<h>130</h>
|
||||
</coordinates>
|
||||
<panel_attributes>lt=-</panel_attributes>
|
||||
<additional_attributes>180.0;10.0;10.0;10.0;10.0;110.0</additional_attributes>
|
||||
</element>
|
||||
<element>
|
||||
<id>Relation</id>
|
||||
<coordinates>
|
||||
<x>1250</x>
|
||||
<y>630</y>
|
||||
<w>180</w>
|
||||
<h>70</h>
|
||||
</coordinates>
|
||||
<panel_attributes>lt=-</panel_attributes>
|
||||
<additional_attributes>160.0;10.0;90.0;10.0;90.0;50.0;10.0;50.0</additional_attributes>
|
||||
</element>
|
||||
<element>
|
||||
<id>UMLClass</id>
|
||||
<coordinates>
|
||||
<x>1410</x>
|
||||
<y>440</y>
|
||||
<w>100</w>
|
||||
<h>40</h>
|
||||
</coordinates>
|
||||
<panel_attributes>AltTitle</panel_attributes>
|
||||
<additional_attributes/>
|
||||
</element>
|
||||
<element>
|
||||
<id>Relation</id>
|
||||
<coordinates>
|
||||
<x>1170</x>
|
||||
<y>450</y>
|
||||
<w>260</w>
|
||||
<h>250</h>
|
||||
</coordinates>
|
||||
<panel_attributes>lt=-</panel_attributes>
|
||||
<additional_attributes>240.0;10.0;10.0;10.0;10.0;230.0</additional_attributes>
|
||||
</element>
|
||||
<element>
|
||||
<id>UMLClass</id>
|
||||
<coordinates>
|
||||
<x>1380</x>
|
||||
<y>800</y>
|
||||
<w>160</w>
|
||||
<h>40</h>
|
||||
</coordinates>
|
||||
<panel_attributes>MangaMetadataEntry</panel_attributes>
|
||||
<additional_attributes/>
|
||||
</element>
|
||||
<element>
|
||||
<id>Relation</id>
|
||||
<coordinates>
|
||||
<x>1230</x>
|
||||
<y>710</y>
|
||||
<w>170</w>
|
||||
<h>130</h>
|
||||
</coordinates>
|
||||
<panel_attributes>lt=-</panel_attributes>
|
||||
<additional_attributes>150.0;110.0;10.0;110.0;10.0;10.0</additional_attributes>
|
||||
</element>
|
||||
<element>
|
||||
<id>UMLClass</id>
|
||||
<coordinates>
|
||||
<x>1650</x>
|
||||
<y>800</y>
|
||||
<w>140</w>
|
||||
<h>40</h>
|
||||
</coordinates>
|
||||
<panel_attributes>MetadataFetcher</panel_attributes>
|
||||
<additional_attributes/>
|
||||
</element>
|
||||
<element>
|
||||
<id>Relation</id>
|
||||
<coordinates>
|
||||
<x>1530</x>
|
||||
<y>810</y>
|
||||
<w>140</w>
|
||||
<h>30</h>
|
||||
</coordinates>
|
||||
<panel_attributes>lt=-</panel_attributes>
|
||||
<additional_attributes>120.0;10.0;10.0;10.0</additional_attributes>
|
||||
</element>
|
||||
<element>
|
||||
<id>UMLUseCase</id>
|
||||
<coordinates>
|
||||
<x>1660</x>
|
||||
<y>680</y>
|
||||
<w>120</w>
|
||||
<h>40</h>
|
||||
</coordinates>
|
||||
<panel_attributes>Path</panel_attributes>
|
||||
<additional_attributes/>
|
||||
</element>
|
||||
<element>
|
||||
<id>Relation</id>
|
||||
<coordinates>
|
||||
<x>1500</x>
|
||||
<y>690</y>
|
||||
<w>180</w>
|
||||
<h>30</h>
|
||||
</coordinates>
|
||||
<panel_attributes>lt=-</panel_attributes>
|
||||
<additional_attributes>160.0;10.0;10.0;10.0</additional_attributes>
|
||||
</element>
|
||||
<element>
|
||||
<id>UMLClass</id>
|
||||
<coordinates>
|
||||
<x>900</x>
|
||||
<y>800</y>
|
||||
<w>140</w>
|
||||
<h>40</h>
|
||||
</coordinates>
|
||||
<panel_attributes>MangaConnectorID</panel_attributes>
|
||||
<additional_attributes/>
|
||||
</element>
|
||||
<element>
|
||||
<id>Relation</id>
|
||||
<coordinates>
|
||||
<x>1030</x>
|
||||
<y>690</y>
|
||||
<w>150</w>
|
||||
<h>140</h>
|
||||
</coordinates>
|
||||
<panel_attributes>lt=-</panel_attributes>
|
||||
<additional_attributes>130.0;10.0;70.0;10.0;70.0;120.0;10.0;120.0</additional_attributes>
|
||||
</element>
|
||||
<element>
|
||||
<id>UMLClass</id>
|
||||
<coordinates>
|
||||
<x>1160</x>
|
||||
<y>920</y>
|
||||
<w>100</w>
|
||||
<h>40</h>
|
||||
</coordinates>
|
||||
<panel_attributes>Chapter
|
||||
</panel_attributes>
|
||||
<additional_attributes/>
|
||||
</element>
|
||||
<element>
|
||||
<id>UMLClass</id>
|
||||
<coordinates>
|
||||
<x>460</x>
|
||||
<y>680</y>
|
||||
<w>160</w>
|
||||
<h>40</h>
|
||||
</coordinates>
|
||||
<panel_attributes>UpdateChapters
|
||||
DownloadedJob</panel_attributes>
|
||||
<additional_attributes/>
|
||||
</element>
|
||||
<element>
|
||||
<id>Relation</id>
|
||||
<coordinates>
|
||||
<x>530</x>
|
||||
<y>710</y>
|
||||
<w>30</w>
|
||||
<h>110</h>
|
||||
</coordinates>
|
||||
<panel_attributes>lt=<<-</panel_attributes>
|
||||
<additional_attributes>10.0;90.0;10.0;10.0</additional_attributes>
|
||||
</element>
|
||||
<element>
|
||||
<id>UMLClass</id>
|
||||
<coordinates>
|
||||
<x>1970</x>
|
||||
<y>640</y>
|
||||
<w>110</w>
|
||||
<h>40</h>
|
||||
</coordinates>
|
||||
<panel_attributes>lw=2
|
||||
Komga</panel_attributes>
|
||||
<additional_attributes/>
|
||||
</element>
|
||||
<element>
|
||||
<id>UMLClass</id>
|
||||
<coordinates>
|
||||
<x>1970</x>
|
||||
<y>710</y>
|
||||
<w>110</w>
|
||||
<h>40</h>
|
||||
</coordinates>
|
||||
<panel_attributes>lw=2
|
||||
Kavita</panel_attributes>
|
||||
<additional_attributes/>
|
||||
</element>
|
||||
<element>
|
||||
<id>UMLPackage</id>
|
||||
<coordinates>
|
||||
<x>1930</x>
|
||||
<y>600</y>
|
||||
<w>190</w>
|
||||
<h>170</h>
|
||||
</coordinates>
|
||||
<panel_attributes>Library</panel_attributes>
|
||||
<additional_attributes/>
|
||||
</element>
|
||||
<element>
|
||||
<id>Relation</id>
|
||||
<coordinates>
|
||||
<x>1770</x>
|
||||
<y>690</y>
|
||||
<w>180</w>
|
||||
<h>30</h>
|
||||
</coordinates>
|
||||
<panel_attributes>lt=-</panel_attributes>
|
||||
<additional_attributes>160.0;10.0;10.0;10.0</additional_attributes>
|
||||
</element>
|
||||
<element>
|
||||
<id>UMLClass</id>
|
||||
<coordinates>
|
||||
<x>710</x>
|
||||
<y>910</y>
|
||||
<w>100</w>
|
||||
<h>30</h>
|
||||
</coordinates>
|
||||
<panel_attributes>/Identifiable/</panel_attributes>
|
||||
<additional_attributes/>
|
||||
</element>
|
||||
<element>
|
||||
<id>Relation</id>
|
||||
<coordinates>
|
||||
<x>750</x>
|
||||
<y>830</y>
|
||||
<w>30</w>
|
||||
<h>100</h>
|
||||
</coordinates>
|
||||
<panel_attributes>lt=<<-</panel_attributes>
|
||||
<additional_attributes>10.0;80.0;10.0;10.0</additional_attributes>
|
||||
</element>
|
||||
<element>
|
||||
<id>Relation</id>
|
||||
<coordinates>
|
||||
<x>530</x>
|
||||
<y>830</y>
|
||||
<w>200</w>
|
||||
<h>120</h>
|
||||
</coordinates>
|
||||
<panel_attributes>lt=<<-</panel_attributes>
|
||||
<additional_attributes>180.0;100.0;10.0;100.0;10.0;10.0</additional_attributes>
|
||||
</element>
|
||||
<element>
|
||||
<id>Relation</id>
|
||||
<coordinates>
|
||||
<x>750</x>
|
||||
<y>930</y>
|
||||
<w>480</w>
|
||||
<h>110</h>
|
||||
</coordinates>
|
||||
<panel_attributes>lt=<<-</panel_attributes>
|
||||
<additional_attributes>10.0;10.0;10.0;90.0;460.0;90.0;460.0;30.0</additional_attributes>
|
||||
</element>
|
||||
<element>
|
||||
<id>Relation</id>
|
||||
<coordinates>
|
||||
<x>800</x>
|
||||
<y>670</y>
|
||||
<w>380</w>
|
||||
<h>280</h>
|
||||
</coordinates>
|
||||
<panel_attributes>lt=<<-</panel_attributes>
|
||||
<additional_attributes>10.0;260.0;260.0;260.0;260.0;10.0;360.0;10.0</additional_attributes>
|
||||
</element>
|
||||
</diagram>
|
@ -94,6 +94,8 @@ Endpoints are documented in Swagger. Just spin up an instance, and go to `http:/
|
||||
- [PuppeteerSharp](https://github.com/hardkoded/puppeteer-sharp/blob/master/LICENSE)
|
||||
- [Html Agility Pack (HAP)](https://github.com/zzzprojects/html-agility-pack/blob/master/LICENSE)
|
||||
- [Soenneker.Utils.String.NeedlemanWunsch](https://github.com/soenneker/soenneker.utils.string.needlemanwunsch/blob/main/LICENSE)
|
||||
- [Jikan](https://jikan.moe/)
|
||||
- [Jikan.Net](https://github.com/Ervie/jikan.net)
|
||||
- 💙 Blåhaj 🦈
|
||||
|
||||
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||
@ -154,6 +156,8 @@ Tranga is using a code-first Entity-Framework Core approach. If you modify the d
|
||||
|
||||
**A broad overview of where is what:**<br />
|
||||
|
||||

|
||||
|
||||
- `Program.cs` Configuration for ASP.NET, Swagger (also in `NamedSwaggerGenOptions.cs`)
|
||||
- `Tranga.cs` Worker-Logic
|
||||
- `Schema/` Entity-Framework
|
||||
|
@ -1,8 +1,11 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=altnames/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=authorsartists/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=comick/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Gotify/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=jikan/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=jjob/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=kitsu/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Komga/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=lunasea/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=mangakatana/@EntryIndexedValue">True</s:Boolean>
|
||||
@ -10,5 +13,6 @@
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Mangasee/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Mangaworld/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Ntfy/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=solverr/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Taskmanager/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Tranga/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
Reference in New Issue
Block a user