Compare commits

..

No commits in common. "3283dd72903f4e891bb0662407c0a13837664b64" and "a1a5028858053ee201812a278c702c640c9f0236" have entirely different histories.

30 changed files with 169 additions and 1177 deletions

View File

@ -19,7 +19,6 @@
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="9.0.5" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Npgsql" Version="9.0.3" /> <PackageReference Include="Npgsql" Version="9.0.3" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" /> <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />

View File

@ -134,10 +134,8 @@ public class JobController(PgsqlContext context, ILog Log) : Controller
} }
} }
Job retrieveChapters = new RetrieveChaptersJob(m, record.language, record.recurrenceTimeMs); Job retrieveChapters = new RetrieveChaptersJob(m, record.language, record.recurrenceTimeMs);
Job updateFilesDownloaded = Job downloadChapters = new DownloadAvailableChaptersJob(m, record.recurrenceTimeMs, dependsOnJobs: [retrieveChapters]);
new UpdateChaptersDownloadedJob(m, record.recurrenceTimeMs, dependsOnJobs: [retrieveChapters]); return AddJobs([retrieveChapters, downloadChapters]);
Job downloadChapters = new DownloadAvailableChaptersJob(m, record.recurrenceTimeMs, dependsOnJobs: [retrieveChapters, updateFilesDownloaded]);
return AddJobs([retrieveChapters, downloadChapters, updateFilesDownloaded]);
} }
/// <summary> /// <summary>
@ -160,7 +158,7 @@ public class JobController(PgsqlContext context, ILog Log) : Controller
} }
/// <summary> /// <summary>
/// Create a new UpdateChaptersDownloadedJob /// Create a new UpdateFilesDownloadedJob
/// </summary> /// </summary>
/// <param name="MangaId">ID of the Manga</param> /// <param name="MangaId">ID of the Manga</param>
/// <response code="201">Job-IDs</response> /// <response code="201">Job-IDs</response>
@ -174,7 +172,7 @@ public class JobController(PgsqlContext context, ILog Log) : Controller
{ {
if(context.Mangas.Find(MangaId) is not { } m) if(context.Mangas.Find(MangaId) is not { } m)
return NotFound(); return NotFound();
Job job = new UpdateChaptersDownloadedJob(m, 0); Job job = new UpdateFilesDownloadedJob(m, 0);
return AddJobs([job]); return AddJobs([job]);
} }
@ -188,7 +186,7 @@ public class JobController(PgsqlContext context, ILog Log) : Controller
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")] [ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
public IActionResult CreateUpdateAllFilesDownloadedJob() public IActionResult CreateUpdateAllFilesDownloadedJob()
{ {
List<UpdateChaptersDownloadedJob> jobs = context.Mangas.Select(m => new UpdateChaptersDownloadedJob(m, 0, null, null)).ToList(); List<UpdateFilesDownloadedJob> jobs = context.Mangas.Select(m => new UpdateFilesDownloadedJob(m, 0, null, null)).ToList();
try try
{ {
context.Jobs.AddRange(jobs); context.Jobs.AddRange(jobs);

View File

@ -343,7 +343,7 @@ public class MangaController(PgsqlContext context, ILog Log) : Controller
return NotFound(); return NotFound();
MoveMangaLibraryJob moveLibrary = new(manga, library); MoveMangaLibraryJob moveLibrary = new(manga, library);
UpdateChaptersDownloadedJob updateDownloadedFiles = new(manga, 0, dependsOnJobs: [moveLibrary]); UpdateFilesDownloadedJob updateDownloadedFiles = new(manga, 0, dependsOnJobs: [moveLibrary]);
try try
{ {

View File

@ -25,7 +25,7 @@ public class SearchController(PgsqlContext context, ILog Log) : Controller
/// <response code="404">MangaConnector with ID not found</response> /// <response code="404">MangaConnector with ID not found</response>
/// <response code="406">MangaConnector with ID is disabled</response> /// <response code="406">MangaConnector with ID is disabled</response>
/// <response code="500">Error during Database Operation</response> /// <response code="500">Error during Database Operation</response>
[HttpGet("{MangaConnectorName}/{Query}")] [HttpPost("{MangaConnectorName}/{Query}")]
[ProducesResponseType<Manga[]>(Status200OK, "application/json")] [ProducesResponseType<Manga[]>(Status200OK, "application/json")]
[ProducesResponseType(Status404NotFound)] [ProducesResponseType(Status404NotFound)]
[ProducesResponseType(Status406NotAcceptable)] [ProducesResponseType(Status406NotAcceptable)]
@ -67,25 +67,30 @@ public class SearchController(PgsqlContext context, ILog Log) : Controller
/// <response code="500">Error during Database Operation</response> /// <response code="500">Error during Database Operation</response>
[HttpPost("Url")] [HttpPost("Url")]
[ProducesResponseType<Manga>(Status200OK, "application/json")] [ProducesResponseType<Manga>(Status200OK, "application/json")]
[ProducesResponseType(Status300MultipleChoices)]
[ProducesResponseType(Status400BadRequest)] [ProducesResponseType(Status400BadRequest)]
[ProducesResponseType(Status404NotFound)]
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")] [ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
public IActionResult GetMangaFromUrl([FromBody]string url) public IActionResult GetMangaFromUrl([FromBody]string url)
{ {
if (context.MangaConnectors.Find("Global") is not { } connector) List<MangaConnector> connectors = context.MangaConnectors.AsEnumerable().Where(c => c.UrlMatchesConnector(url)).ToList();
return StatusCode(Status500InternalServerError, "Could not find Global Connector."); if (connectors.Count == 0)
return NotFound();
else if (connectors.Count > 1)
return StatusCode(Status300MultipleChoices);
if(connector.GetMangaFromUrl(url) is not { } manga) if(connectors.First().GetMangaFromUrl(url) is not { } manga)
return BadRequest(); return BadRequest();
try try
{ {
if(AddMangaToContext(manga) is { } add) if(AddMangaToContext(manga) is { } add)
return Ok(add); return Ok(add);
return StatusCode(Status500InternalServerError); return StatusCode(500);
} }
catch (DbUpdateException e) catch (DbUpdateException e)
{ {
Log.Error(e); Log.Error(e);
return StatusCode(Status500InternalServerError, e.Message); return StatusCode(500, e.Message);
} }
} }

View File

@ -416,7 +416,7 @@ namespace API.Migrations.pgsql
b.HasDiscriminator().HasValue((byte)5); b.HasDiscriminator().HasValue((byte)5);
}); });
modelBuilder.Entity("API.Schema.Jobs.UpdateChaptersDownloadedJob", b => modelBuilder.Entity("API.Schema.Jobs.UpdateFilesDownloadedJob", b =>
{ {
b.HasBaseType("API.Schema.Jobs.Job"); b.HasBaseType("API.Schema.Jobs.Job");
@ -661,7 +661,7 @@ namespace API.Migrations.pgsql
b.Navigation("Manga"); b.Navigation("Manga");
}); });
modelBuilder.Entity("API.Schema.Jobs.UpdateChaptersDownloadedJob", b => modelBuilder.Entity("API.Schema.Jobs.UpdateFilesDownloadedJob", b =>
{ {
b.HasOne("API.Schema.Manga", "Manga") b.HasOne("API.Schema.Manga", "Manga")
.WithMany() .WithMany()

View File

@ -416,7 +416,7 @@ namespace API.Migrations.pgsql
b.HasDiscriminator().HasValue((byte)5); b.HasDiscriminator().HasValue((byte)5);
}); });
modelBuilder.Entity("API.Schema.Jobs.UpdateChaptersDownloadedJob", b => modelBuilder.Entity("API.Schema.Jobs.UpdateFilesDownloadedJob", b =>
{ {
b.HasBaseType("API.Schema.Jobs.Job"); b.HasBaseType("API.Schema.Jobs.Job");
@ -667,7 +667,7 @@ namespace API.Migrations.pgsql
b.Navigation("Manga"); b.Navigation("Manga");
}); });
modelBuilder.Entity("API.Schema.Jobs.UpdateChaptersDownloadedJob", b => modelBuilder.Entity("API.Schema.Jobs.UpdateFilesDownloadedJob", b =>
{ {
b.HasOne("API.Schema.Manga", "Manga") b.HasOne("API.Schema.Manga", "Manga")
.WithMany() .WithMany()

View File

@ -416,7 +416,7 @@ namespace API.Migrations.pgsql
b.HasDiscriminator().HasValue((byte)5); b.HasDiscriminator().HasValue((byte)5);
}); });
modelBuilder.Entity("API.Schema.Jobs.UpdateChaptersDownloadedJob", b => modelBuilder.Entity("API.Schema.Jobs.UpdateFilesDownloadedJob", b =>
{ {
b.HasBaseType("API.Schema.Jobs.Job"); b.HasBaseType("API.Schema.Jobs.Job");
@ -667,7 +667,7 @@ namespace API.Migrations.pgsql
b.Navigation("Manga"); b.Navigation("Manga");
}); });
modelBuilder.Entity("API.Schema.Jobs.UpdateChaptersDownloadedJob", b => modelBuilder.Entity("API.Schema.Jobs.UpdateFilesDownloadedJob", b =>
{ {
b.HasOne("API.Schema.Manga", "Manga") b.HasOne("API.Schema.Manga", "Manga")
.WithMany() .WithMany()

View File

@ -416,7 +416,7 @@ namespace API.Migrations.pgsql
b.HasDiscriminator().HasValue((byte)5); b.HasDiscriminator().HasValue((byte)5);
}); });
modelBuilder.Entity("API.Schema.Jobs.UpdateChaptersDownloadedJob", b => modelBuilder.Entity("API.Schema.Jobs.UpdateFilesDownloadedJob", b =>
{ {
b.HasBaseType("API.Schema.Jobs.Job"); b.HasBaseType("API.Schema.Jobs.Job");
@ -668,7 +668,7 @@ namespace API.Migrations.pgsql
b.Navigation("Manga"); b.Navigation("Manga");
}); });
modelBuilder.Entity("API.Schema.Jobs.UpdateChaptersDownloadedJob", b => modelBuilder.Entity("API.Schema.Jobs.UpdateFilesDownloadedJob", b =>
{ {
b.HasOne("API.Schema.Manga", "Manga") b.HasOne("API.Schema.Manga", "Manga")
.WithMany() .WithMany()

View File

@ -1,720 +0,0 @@
// <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("20250516180953_Split-UpdateChaptersDownloadedJob-Into-UpdateSingleChapterDownloadedJob")]
partial class SplitUpdateChaptersDownloadedJobIntoUpdateSingleChapterDownloadedJob
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "9.0.3")
.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>("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("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.UpdateSingleChapterDownloadedJob", b =>
{
b.HasBaseType("API.Schema.Jobs.Job");
b.Property<string>("ChapterId")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.HasIndex("ChapterId");
b.ToTable("Jobs", t =>
{
t.Property("ChapterId")
.HasColumnName("UpdateSingleChapterDownloadedJob_ChapterId");
});
b.HasDiscriminator().HasValue((byte)8);
});
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.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("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.UpdateSingleChapterDownloadedJob", b =>
{
b.HasOne("API.Schema.Chapter", "Chapter")
.WithMany()
.HasForeignKey("ChapterId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Chapter");
});
modelBuilder.Entity("API.Schema.Manga", b =>
{
b.Navigation("Chapters");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -1,94 +0,0 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace API.Migrations.pgsql
{
/// <inheritdoc />
public partial class SplitUpdateChaptersDownloadedJobIntoUpdateSingleChapterDownloadedJob : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Jobs_Mangas_UpdateFilesDownloadedJob_MangaId",
table: "Jobs");
migrationBuilder.RenameColumn(
name: "UpdateFilesDownloadedJob_MangaId",
table: "Jobs",
newName: "UpdateChaptersDownloadedJob_MangaId");
migrationBuilder.RenameIndex(
name: "IX_Jobs_UpdateFilesDownloadedJob_MangaId",
table: "Jobs",
newName: "IX_Jobs_UpdateChaptersDownloadedJob_MangaId");
migrationBuilder.AddColumn<string>(
name: "UpdateSingleChapterDownloadedJob_ChapterId",
table: "Jobs",
type: "character varying(64)",
maxLength: 64,
nullable: true);
migrationBuilder.CreateIndex(
name: "IX_Jobs_UpdateSingleChapterDownloadedJob_ChapterId",
table: "Jobs",
column: "UpdateSingleChapterDownloadedJob_ChapterId");
migrationBuilder.AddForeignKey(
name: "FK_Jobs_Chapters_UpdateSingleChapterDownloadedJob_ChapterId",
table: "Jobs",
column: "UpdateSingleChapterDownloadedJob_ChapterId",
principalTable: "Chapters",
principalColumn: "ChapterId",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_Jobs_Mangas_UpdateChaptersDownloadedJob_MangaId",
table: "Jobs",
column: "UpdateChaptersDownloadedJob_MangaId",
principalTable: "Mangas",
principalColumn: "MangaId",
onDelete: ReferentialAction.Cascade);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Jobs_Chapters_UpdateSingleChapterDownloadedJob_ChapterId",
table: "Jobs");
migrationBuilder.DropForeignKey(
name: "FK_Jobs_Mangas_UpdateChaptersDownloadedJob_MangaId",
table: "Jobs");
migrationBuilder.DropIndex(
name: "IX_Jobs_UpdateSingleChapterDownloadedJob_ChapterId",
table: "Jobs");
migrationBuilder.DropColumn(
name: "UpdateSingleChapterDownloadedJob_ChapterId",
table: "Jobs");
migrationBuilder.RenameColumn(
name: "UpdateChaptersDownloadedJob_MangaId",
table: "Jobs",
newName: "UpdateFilesDownloadedJob_MangaId");
migrationBuilder.RenameIndex(
name: "IX_Jobs_UpdateChaptersDownloadedJob_MangaId",
table: "Jobs",
newName: "IX_Jobs_UpdateFilesDownloadedJob_MangaId");
migrationBuilder.AddForeignKey(
name: "FK_Jobs_Mangas_UpdateFilesDownloadedJob_MangaId",
table: "Jobs",
column: "UpdateFilesDownloadedJob_MangaId",
principalTable: "Mangas",
principalColumn: "MangaId",
onDelete: ReferentialAction.Cascade);
}
}
}

View File

@ -413,7 +413,7 @@ namespace API.Migrations.pgsql
b.HasDiscriminator().HasValue((byte)5); b.HasDiscriminator().HasValue((byte)5);
}); });
modelBuilder.Entity("API.Schema.Jobs.UpdateChaptersDownloadedJob", b => modelBuilder.Entity("API.Schema.Jobs.UpdateFilesDownloadedJob", b =>
{ {
b.HasBaseType("API.Schema.Jobs.Job"); b.HasBaseType("API.Schema.Jobs.Job");
@ -427,32 +427,12 @@ namespace API.Migrations.pgsql
b.ToTable("Jobs", t => b.ToTable("Jobs", t =>
{ {
t.Property("MangaId") t.Property("MangaId")
.HasColumnName("UpdateChaptersDownloadedJob_MangaId"); .HasColumnName("UpdateFilesDownloadedJob_MangaId");
}); });
b.HasDiscriminator().HasValue((byte)6); b.HasDiscriminator().HasValue((byte)6);
}); });
modelBuilder.Entity("API.Schema.Jobs.UpdateSingleChapterDownloadedJob", b =>
{
b.HasBaseType("API.Schema.Jobs.Job");
b.Property<string>("ChapterId")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.HasIndex("ChapterId");
b.ToTable("Jobs", t =>
{
t.Property("ChapterId")
.HasColumnName("UpdateSingleChapterDownloadedJob_ChapterId");
});
b.HasDiscriminator().HasValue((byte)8);
});
modelBuilder.Entity("API.Schema.MangaConnectors.ComickIo", b => modelBuilder.Entity("API.Schema.MangaConnectors.ComickIo", b =>
{ {
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector"); b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
@ -685,7 +665,7 @@ namespace API.Migrations.pgsql
b.Navigation("Manga"); b.Navigation("Manga");
}); });
modelBuilder.Entity("API.Schema.Jobs.UpdateChaptersDownloadedJob", b => modelBuilder.Entity("API.Schema.Jobs.UpdateFilesDownloadedJob", b =>
{ {
b.HasOne("API.Schema.Manga", "Manga") b.HasOne("API.Schema.Manga", "Manga")
.WithMany() .WithMany()
@ -696,17 +676,6 @@ namespace API.Migrations.pgsql
b.Navigation("Manga"); b.Navigation("Manga");
}); });
modelBuilder.Entity("API.Schema.Jobs.UpdateSingleChapterDownloadedJob", b =>
{
b.HasOne("API.Schema.Chapter", "Chapter")
.WithMany()
.HasForeignKey("ChapterId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Chapter");
});
modelBuilder.Entity("API.Schema.Manga", b => modelBuilder.Entity("API.Schema.Manga", b =>
{ {
b.Navigation("Chapters"); b.Navigation("Chapters");

View File

@ -122,9 +122,12 @@ using (IServiceScope scope = app.Services.CreateScope())
context.LocalLibraries.Add(new LocalLibrary(TrangaSettings.downloadLocation, "Default Library")); context.LocalLibraries.Add(new LocalLibrary(TrangaSettings.downloadLocation, "Default Library"));
context.Jobs.AddRange(context.Jobs.Where(j => j.JobType == JobType.DownloadAvailableChaptersJob) context.Jobs.AddRange(context.Jobs.Where(j => j.JobType == JobType.DownloadAvailableChaptersJob)
.Include(downloadAvailableChaptersJob => ((DownloadAvailableChaptersJob)downloadAvailableChaptersJob).Manga) .AsEnumerable()
.ToList() .Select(dacj =>
.Select(dacj => new UpdateChaptersDownloadedJob(((DownloadAvailableChaptersJob)dacj).Manga, 0))); {
DownloadAvailableChaptersJob? j = dacj as DownloadAvailableChaptersJob;
return new UpdateFilesDownloadedJob(j!.Manga, 0);
}));
context.Jobs.RemoveRange(context.Jobs.Where(j => j.state == JobState.Completed && j.RecurrenceMs < 1)); context.Jobs.RemoveRange(context.Jobs.Where(j => j.state == JobState.Completed && j.RecurrenceMs < 1));
foreach (Job job in context.Jobs.Where(j => j.state == JobState.Running)) foreach (Job job in context.Jobs.Where(j => j.state == JobState.Running))
{ {
@ -150,7 +153,7 @@ using (IServiceScope scope = app.Services.CreateScope())
TrangaSettings.Load(); TrangaSettings.Load();
Tranga.StartLogger(); Tranga.StartLogger();
Tranga.JobStarterThread.Start(app.Services); Tranga.JobStarterThread.Start(app.Services);
//Tranga.NotificationSenderThread.Start(app.Services); //TODO RE-ENABLE Tranga.NotificationSenderThread.Start(app.Services);
app.UseCors("AllowAll"); app.UseCors("AllowAll");

View File

@ -26,7 +26,7 @@ public class Chapter : IComparable<Chapter>
[StringLength(256)] [Required] public string FileName { get; private set; } [StringLength(256)] [Required] public string FileName { get; private set; }
[Required] public bool Downloaded { get; internal set; } [Required] public bool Downloaded { get; internal set; }
[NotMapped] public string FullArchiveFilePath => Path.Join(ParentManga.FullDirectoryPath, FileName); [JsonIgnore] [NotMapped] public string FullArchiveFilePath => Path.Join(ParentManga.FullDirectoryPath, FileName);
public Chapter(Manga parentManga, string url, string chapterNumber, int? volumeNumber = null, string? title = null) public Chapter(Manga parentManga, string url, string chapterNumber, int? volumeNumber = null, string? title = null)
{ {

View File

@ -1,7 +1,5 @@
using API.Schema.LibraryConnectors; using API.Schema.LibraryConnectors;
using log4net;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
namespace API.Schema.Contexts; namespace API.Schema.Contexts;
@ -9,18 +7,6 @@ public class LibraryContext(DbContextOptions<LibraryContext> options) : DbContex
{ {
public DbSet<LibraryConnector> LibraryConnectors { get; set; } public DbSet<LibraryConnector> LibraryConnectors { get; set; }
private ILog Log => LogManager.GetLogger(GetType());
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
optionsBuilder.EnableSensitiveDataLogging();
optionsBuilder.LogTo(s =>
{
Log.Debug(s);
}, [DbLoggerCategory.Query.Name], LogLevel.Trace, DbContextLoggerOptions.Level | DbContextLoggerOptions.Category);
}
protected override void OnModelCreating(ModelBuilder modelBuilder) protected override void OnModelCreating(ModelBuilder modelBuilder)
{ {
//LibraryConnector Types //LibraryConnector Types

View File

@ -1,7 +1,5 @@
using API.Schema.NotificationConnectors; using API.Schema.NotificationConnectors;
using log4net;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
namespace API.Schema.Contexts; namespace API.Schema.Contexts;
@ -9,15 +7,4 @@ public class NotificationsContext(DbContextOptions<NotificationsContext> options
{ {
public DbSet<NotificationConnector> NotificationConnectors { get; set; } public DbSet<NotificationConnector> NotificationConnectors { get; set; }
public DbSet<Notification> Notifications { get; set; } public DbSet<Notification> Notifications { get; set; }
private ILog Log => LogManager.GetLogger(GetType());
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
optionsBuilder.EnableSensitiveDataLogging();
optionsBuilder.LogTo(s =>
{
Log.Debug(s);
}, [DbLoggerCategory.Query.Name], LogLevel.Trace, DbContextLoggerOptions.Level | DbContextLoggerOptions.Category);
}
} }

View File

@ -1,8 +1,6 @@
using API.Schema.Jobs; using API.Schema.Jobs;
using API.Schema.MangaConnectors; using API.Schema.MangaConnectors;
using log4net;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
namespace API.Schema.Contexts; namespace API.Schema.Contexts;
@ -15,17 +13,6 @@ public class PgsqlContext(DbContextOptions<PgsqlContext> options) : DbContext(op
public DbSet<Chapter> Chapters { get; set; } public DbSet<Chapter> Chapters { get; set; }
public DbSet<Author> Authors { get; set; } public DbSet<Author> Authors { get; set; }
public DbSet<MangaTag> Tags { get; set; } public DbSet<MangaTag> Tags { get; set; }
private ILog Log => LogManager.GetLogger(GetType());
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
optionsBuilder.EnableSensitiveDataLogging();
optionsBuilder.LogTo(s =>
{
Log.Debug(s);
}, [DbLoggerCategory.Query.Name], LogLevel.Trace, DbContextLoggerOptions.Level | DbContextLoggerOptions.Category);
}
protected override void OnModelCreating(ModelBuilder modelBuilder) protected override void OnModelCreating(ModelBuilder modelBuilder)
{ {
@ -38,8 +25,7 @@ public class PgsqlContext(DbContextOptions<PgsqlContext> options) : DbContext(op
.HasValue<DownloadSingleChapterJob>(JobType.DownloadSingleChapterJob) .HasValue<DownloadSingleChapterJob>(JobType.DownloadSingleChapterJob)
.HasValue<DownloadMangaCoverJob>(JobType.DownloadMangaCoverJob) .HasValue<DownloadMangaCoverJob>(JobType.DownloadMangaCoverJob)
.HasValue<RetrieveChaptersJob>(JobType.RetrieveChaptersJob) .HasValue<RetrieveChaptersJob>(JobType.RetrieveChaptersJob)
.HasValue<UpdateChaptersDownloadedJob>(JobType.UpdateChaptersDownloadedJob) .HasValue<UpdateFilesDownloadedJob>(JobType.UpdateFilesDownloadedJob);
.HasValue<UpdateSingleChapterDownloadedJob>(JobType.UpdateSingleChapterDownloadedJob);
//Job specification //Job specification
modelBuilder.Entity<DownloadAvailableChaptersJob>() modelBuilder.Entity<DownloadAvailableChaptersJob>()
@ -50,7 +36,7 @@ public class PgsqlContext(DbContextOptions<PgsqlContext> options) : DbContext(op
.OnDelete(DeleteBehavior.Cascade); .OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<DownloadAvailableChaptersJob>() modelBuilder.Entity<DownloadAvailableChaptersJob>()
.Navigation(j => j.Manga) .Navigation(j => j.Manga)
.EnableLazyLoading(); .AutoInclude();
modelBuilder.Entity<DownloadMangaCoverJob>() modelBuilder.Entity<DownloadMangaCoverJob>()
.HasOne<Manga>(j => j.Manga) .HasOne<Manga>(j => j.Manga)
.WithMany() .WithMany()
@ -59,7 +45,7 @@ public class PgsqlContext(DbContextOptions<PgsqlContext> options) : DbContext(op
.OnDelete(DeleteBehavior.Cascade); .OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<DownloadMangaCoverJob>() modelBuilder.Entity<DownloadMangaCoverJob>()
.Navigation(j => j.Manga) .Navigation(j => j.Manga)
.EnableLazyLoading(); .AutoInclude();
modelBuilder.Entity<DownloadSingleChapterJob>() modelBuilder.Entity<DownloadSingleChapterJob>()
.HasOne<Chapter>(j => j.Chapter) .HasOne<Chapter>(j => j.Chapter)
.WithMany() .WithMany()
@ -68,7 +54,7 @@ public class PgsqlContext(DbContextOptions<PgsqlContext> options) : DbContext(op
.OnDelete(DeleteBehavior.Cascade); .OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<DownloadSingleChapterJob>() modelBuilder.Entity<DownloadSingleChapterJob>()
.Navigation(j => j.Chapter) .Navigation(j => j.Chapter)
.EnableLazyLoading(); .AutoInclude();
modelBuilder.Entity<MoveMangaLibraryJob>() modelBuilder.Entity<MoveMangaLibraryJob>()
.HasOne<Manga>(j => j.Manga) .HasOne<Manga>(j => j.Manga)
.WithMany() .WithMany()
@ -77,7 +63,7 @@ public class PgsqlContext(DbContextOptions<PgsqlContext> options) : DbContext(op
.OnDelete(DeleteBehavior.Cascade); .OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<MoveMangaLibraryJob>() modelBuilder.Entity<MoveMangaLibraryJob>()
.Navigation(j => j.Manga) .Navigation(j => j.Manga)
.EnableLazyLoading(); .AutoInclude();
modelBuilder.Entity<MoveMangaLibraryJob>() modelBuilder.Entity<MoveMangaLibraryJob>()
.HasOne<LocalLibrary>(j => j.ToLibrary) .HasOne<LocalLibrary>(j => j.ToLibrary)
.WithMany() .WithMany()
@ -86,7 +72,7 @@ public class PgsqlContext(DbContextOptions<PgsqlContext> options) : DbContext(op
.OnDelete(DeleteBehavior.Cascade); .OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<MoveMangaLibraryJob>() modelBuilder.Entity<MoveMangaLibraryJob>()
.Navigation(j => j.ToLibrary) .Navigation(j => j.ToLibrary)
.EnableLazyLoading(); .AutoInclude();
modelBuilder.Entity<RetrieveChaptersJob>() modelBuilder.Entity<RetrieveChaptersJob>()
.HasOne<Manga>(j => j.Manga) .HasOne<Manga>(j => j.Manga)
.WithMany() .WithMany()
@ -95,22 +81,23 @@ public class PgsqlContext(DbContextOptions<PgsqlContext> options) : DbContext(op
.OnDelete(DeleteBehavior.Cascade); .OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<RetrieveChaptersJob>() modelBuilder.Entity<RetrieveChaptersJob>()
.Navigation(j => j.Manga) .Navigation(j => j.Manga)
.EnableLazyLoading(); .AutoInclude();
modelBuilder.Entity<UpdateChaptersDownloadedJob>() modelBuilder.Entity<UpdateFilesDownloadedJob>()
.HasOne<Manga>(j => j.Manga) .HasOne<Manga>(j => j.Manga)
.WithMany() .WithMany()
.HasForeignKey(j => j.MangaId) .HasForeignKey(j => j.MangaId)
.IsRequired() .IsRequired()
.OnDelete(DeleteBehavior.Cascade); .OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<UpdateChaptersDownloadedJob>() modelBuilder.Entity<UpdateFilesDownloadedJob>()
.Navigation(j => j.Manga) .Navigation(j => j.Manga)
.EnableLazyLoading(); .AutoInclude();
//Job has possible ParentJob //Job has possible ParentJob
modelBuilder.Entity<Job>() modelBuilder.Entity<Job>()
.HasOne<Job>(childJob => childJob.ParentJob) .HasMany<Job>()
.WithMany() .WithOne(childJob => childJob.ParentJob)
.HasForeignKey(childjob => childjob.ParentJobId) .HasForeignKey(childjob => childjob.ParentJobId)
.IsRequired(false)
.OnDelete(DeleteBehavior.Cascade); .OnDelete(DeleteBehavior.Cascade);
//Job might be dependent on other Jobs //Job might be dependent on other Jobs
modelBuilder.Entity<Job>() modelBuilder.Entity<Job>()
@ -118,8 +105,7 @@ public class PgsqlContext(DbContextOptions<PgsqlContext> options) : DbContext(op
.WithMany(); .WithMany();
modelBuilder.Entity<Job>() modelBuilder.Entity<Job>()
.Navigation(j => j.DependsOnJobs) .Navigation(j => j.DependsOnJobs)
.AutoInclude(false) .AutoInclude(false);
.EnableLazyLoading();
//MangaConnector Types //MangaConnector Types
modelBuilder.Entity<MangaConnector>() modelBuilder.Entity<MangaConnector>()
@ -150,8 +136,7 @@ public class PgsqlContext(DbContextOptions<PgsqlContext> options) : DbContext(op
.AutoInclude(); .AutoInclude();
modelBuilder.Entity<Manga>() modelBuilder.Entity<Manga>()
.Navigation(m => m.Chapters) .Navigation(m => m.Chapters)
.AutoInclude(false) .AutoInclude(false);
.EnableLazyLoading();
//Manga owns MangaAltTitles //Manga owns MangaAltTitles
modelBuilder.Entity<Manga>() modelBuilder.Entity<Manga>()
.OwnsMany<MangaAltTitle>(m => m.AltTitles) .OwnsMany<MangaAltTitle>(m => m.AltTitles)

View File

@ -1,6 +1,5 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using API.Schema.Contexts; using API.Schema.Contexts;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace API.Schema.Jobs; namespace API.Schema.Jobs;
@ -8,15 +7,7 @@ namespace API.Schema.Jobs;
public class DownloadAvailableChaptersJob : Job public class DownloadAvailableChaptersJob : Job
{ {
[StringLength(64)] [Required] public string MangaId { get; init; } [StringLength(64)] [Required] public string MangaId { get; init; }
[JsonIgnore] public Manga Manga { get; init; } = null!;
private Manga _manga = null!;
[JsonIgnore]
public Manga Manga
{
get => LazyLoader.Load(this, ref _manga);
init => _manga = value;
}
public DownloadAvailableChaptersJob(Manga manga, ulong recurrenceMs, Job? parentJob = null, ICollection<Job>? dependsOnJobs = null) public DownloadAvailableChaptersJob(Manga manga, ulong recurrenceMs, Job? parentJob = null, ICollection<Job>? dependsOnJobs = null)
: base(TokenGen.CreateToken(typeof(DownloadAvailableChaptersJob)), JobType.DownloadAvailableChaptersJob, recurrenceMs, parentJob, dependsOnJobs) : base(TokenGen.CreateToken(typeof(DownloadAvailableChaptersJob)), JobType.DownloadAvailableChaptersJob, recurrenceMs, parentJob, dependsOnJobs)
@ -28,14 +19,15 @@ public class DownloadAvailableChaptersJob : Job
/// <summary> /// <summary>
/// EF ONLY!!! /// EF ONLY!!!
/// </summary> /// </summary>
internal DownloadAvailableChaptersJob(ILazyLoader lazyLoader, string mangaId, ulong recurrenceMs, string? parentJobId) internal DownloadAvailableChaptersJob(string mangaId, ulong recurrenceMs, string? parentJobId)
: base(lazyLoader, TokenGen.CreateToken(typeof(DownloadAvailableChaptersJob)), JobType.DownloadAvailableChaptersJob, recurrenceMs, parentJobId) : base(TokenGen.CreateToken(typeof(DownloadAvailableChaptersJob)), JobType.DownloadAvailableChaptersJob, recurrenceMs, parentJobId)
{ {
this.MangaId = mangaId; this.MangaId = mangaId;
} }
protected override IEnumerable<Job> RunInternal(PgsqlContext context) protected override IEnumerable<Job> RunInternal(PgsqlContext context)
{ {
return Manga.Chapters.Where(c => c.Downloaded == false).Select(chapter => new DownloadSingleChapterJob(chapter, this)); context.Attach(Manga);
return Manga.Chapters.Select(chapter => new DownloadSingleChapterJob(chapter, this));
} }
} }

View File

@ -1,7 +1,6 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using API.Schema.Contexts; using API.Schema.Contexts;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace API.Schema.Jobs; namespace API.Schema.Jobs;
@ -9,15 +8,7 @@ namespace API.Schema.Jobs;
public class DownloadMangaCoverJob : Job public class DownloadMangaCoverJob : Job
{ {
[StringLength(64)] [Required] public string MangaId { get; init; } [StringLength(64)] [Required] public string MangaId { get; init; }
[JsonIgnore] public Manga Manga { get; init; } = null!;
private Manga _manga = null!;
[JsonIgnore]
public Manga Manga
{
get => LazyLoader.Load(this, ref _manga);
init => _manga = value;
}
public DownloadMangaCoverJob(Manga manga, Job? parentJob = null, ICollection<Job>? dependsOnJobs = null) public DownloadMangaCoverJob(Manga manga, Job? parentJob = null, ICollection<Job>? dependsOnJobs = null)
: base(TokenGen.CreateToken(typeof(DownloadMangaCoverJob)), JobType.DownloadMangaCoverJob, 0, parentJob, dependsOnJobs) : base(TokenGen.CreateToken(typeof(DownloadMangaCoverJob)), JobType.DownloadMangaCoverJob, 0, parentJob, dependsOnJobs)
@ -29,14 +20,15 @@ public class DownloadMangaCoverJob : Job
/// <summary> /// <summary>
/// EF ONLY!!! /// EF ONLY!!!
/// </summary> /// </summary>
internal DownloadMangaCoverJob(ILazyLoader lazyLoader, string mangaId, string? parentJobId) internal DownloadMangaCoverJob(string mangaId, string? parentJobId)
: base(lazyLoader, TokenGen.CreateToken(typeof(DownloadMangaCoverJob)), JobType.DownloadMangaCoverJob, 0, parentJobId) : base(TokenGen.CreateToken(typeof(DownloadMangaCoverJob)), JobType.DownloadMangaCoverJob, 0, parentJobId)
{ {
this.MangaId = mangaId; this.MangaId = mangaId;
} }
protected override IEnumerable<Job> RunInternal(PgsqlContext context) protected override IEnumerable<Job> RunInternal(PgsqlContext context)
{ {
context.Attach(Manga);
try try
{ {
Manga.CoverFileNameInCache = Manga.MangaConnector.SaveCoverImageToCache(Manga); Manga.CoverFileNameInCache = Manga.MangaConnector.SaveCoverImageToCache(Manga);

View File

@ -3,8 +3,6 @@ using System.IO.Compression;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using API.MangaDownloadClients; using API.MangaDownloadClients;
using API.Schema.Contexts; using API.Schema.Contexts;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Newtonsoft.Json; using Newtonsoft.Json;
using SixLabors.ImageSharp; using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg;
@ -18,14 +16,7 @@ public class DownloadSingleChapterJob : Job
{ {
[StringLength(64)] [Required] public string ChapterId { get; init; } [StringLength(64)] [Required] public string ChapterId { get; init; }
private Chapter _chapter = null!; [JsonIgnore] public Chapter Chapter { get; init; } = null!;
[JsonIgnore]
public Chapter Chapter
{
get => LazyLoader.Load(this, ref _chapter);
init => _chapter = value;
}
public DownloadSingleChapterJob(Chapter chapter, Job? parentJob = null, ICollection<Job>? dependsOnJobs = null) public DownloadSingleChapterJob(Chapter chapter, Job? parentJob = null, ICollection<Job>? dependsOnJobs = null)
: base(TokenGen.CreateToken(typeof(DownloadSingleChapterJob)), JobType.DownloadSingleChapterJob, 0, parentJob, dependsOnJobs) : base(TokenGen.CreateToken(typeof(DownloadSingleChapterJob)), JobType.DownloadSingleChapterJob, 0, parentJob, dependsOnJobs)
@ -37,14 +28,15 @@ public class DownloadSingleChapterJob : Job
/// <summary> /// <summary>
/// EF ONLY!!! /// EF ONLY!!!
/// </summary> /// </summary>
internal DownloadSingleChapterJob(ILazyLoader lazyLoader, string chapterId, string? parentJobId) internal DownloadSingleChapterJob(string chapterId, string? parentJobId)
: base(lazyLoader, TokenGen.CreateToken(typeof(DownloadSingleChapterJob)), JobType.DownloadSingleChapterJob, 0, parentJobId) : base(TokenGen.CreateToken(typeof(DownloadSingleChapterJob)), JobType.DownloadSingleChapterJob, 0, parentJobId)
{ {
this.ChapterId = chapterId; this.ChapterId = chapterId;
} }
protected override IEnumerable<Job> RunInternal(PgsqlContext context) protected override IEnumerable<Job> RunInternal(PgsqlContext context)
{ {
context.Attach(Chapter);
string[] imageUrls = Chapter.ParentManga.MangaConnector.GetChapterImageUrls(Chapter); string[] imageUrls = Chapter.ParentManga.MangaConnector.GetChapterImageUrls(Chapter);
if (imageUrls.Length < 1) if (imageUrls.Length < 1)
{ {
@ -105,16 +97,16 @@ public class DownloadSingleChapterJob : Job
Chapter.Downloaded = true; Chapter.Downloaded = true;
context.SaveChanges(); context.SaveChanges();
if (context.Jobs.ToList().Any(j => if (context.Jobs.AsEnumerable().Any(j =>
{ {
if (j.JobType != JobType.UpdateChaptersDownloadedJob) if (j.JobType != JobType.UpdateFilesDownloadedJob)
return false; return false;
UpdateChaptersDownloadedJob job = (UpdateChaptersDownloadedJob)j; UpdateFilesDownloadedJob job = (UpdateFilesDownloadedJob)j;
return job.MangaId == this.Chapter.ParentMangaId; return job.MangaId == this.Chapter.ParentMangaId;
})) }))
return []; return [];
return [new UpdateChaptersDownloadedJob(Chapter.ParentManga, 0, this.ParentJob)]; return [new UpdateFilesDownloadedJob(Chapter.ParentManga, 0, this.ParentJob)];
} }
private void ProcessImage(string imagePath) private void ProcessImage(string imagePath)

View File

@ -3,7 +3,6 @@ using System.ComponentModel.DataAnnotations.Schema;
using API.Schema.Contexts; using API.Schema.Contexts;
using log4net; using log4net;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace API.Schema.Jobs; namespace API.Schema.Jobs;
@ -17,12 +16,7 @@ public abstract class Job
[StringLength(64)] public string? ParentJobId { get; init; } [StringLength(64)] public string? ParentJobId { get; init; }
[JsonIgnore] public Job? ParentJob { get; init; } [JsonIgnore] public Job? ParentJob { get; init; }
private ICollection<Job> _dependsOnJobs = null!; [JsonIgnore] public ICollection<Job> DependsOnJobs { get; init; }
[JsonIgnore] public ICollection<Job> DependsOnJobs
{
get => LazyLoader.Load(this, ref _dependsOnJobs);
init => _dependsOnJobs = value;
}
[Required] public JobType JobType { get; init; } [Required] public JobType JobType { get; init; }
@ -38,7 +32,6 @@ public abstract class Job
[JsonIgnore] [NotMapped] internal bool DependenciesFulfilled => DependsOnJobs.All(j => j.IsCompleted); [JsonIgnore] [NotMapped] internal bool DependenciesFulfilled => DependsOnJobs.All(j => j.IsCompleted);
[NotMapped] [JsonIgnore] protected ILog Log { get; init; } [NotMapped] [JsonIgnore] protected ILog Log { get; init; }
[NotMapped] [JsonIgnore] protected ILazyLoader LazyLoader { get; init; }
protected Job(string jobId, JobType jobType, ulong recurrenceMs, Job? parentJob = null, ICollection<Job>? dependsOnJobs = null) protected Job(string jobId, JobType jobType, ulong recurrenceMs, Job? parentJob = null, ICollection<Job>? dependsOnJobs = null)
{ {
@ -55,9 +48,8 @@ public abstract class Job
/// <summary> /// <summary>
/// EF ONLY!!! /// EF ONLY!!!
/// </summary> /// </summary>
protected internal Job(ILazyLoader lazyLoader, string jobId, JobType jobType, ulong recurrenceMs, string? parentJobId) protected internal Job(string jobId, JobType jobType, ulong recurrenceMs, string? parentJobId)
{ {
this.LazyLoader = lazyLoader;
this.JobId = jobId; this.JobId = jobId;
this.JobType = jobType; this.JobType = jobType;
this.RecurrenceMs = recurrenceMs; this.RecurrenceMs = recurrenceMs;
@ -69,39 +61,29 @@ public abstract class Job
public IEnumerable<Job> Run(IServiceProvider serviceProvider) public IEnumerable<Job> Run(IServiceProvider serviceProvider)
{ {
Log.Info($"Running job {JobId}"); Log.Debug($"Running job {JobId}");
DateTime jobStart = DateTime.UtcNow;
Job[]? ret = null;
using IServiceScope scope = serviceProvider.CreateScope(); using IServiceScope scope = serviceProvider.CreateScope();
PgsqlContext context = scope.ServiceProvider.GetRequiredService<PgsqlContext>();
try try
{ {
PgsqlContext context = scope.ServiceProvider.GetRequiredService<PgsqlContext>();
context.Attach(this); context.Attach(this);
this.state = JobState.Running; this.state = JobState.Running;
context.SaveChanges(); context.SaveChanges();
ret = RunInternal(context).ToArray(); Job[] newJobs = RunInternal(context).ToArray();
this.state = JobState.Completed; this.state = JobState.Completed;
context.Jobs.AddRange(ret);
Log.Info($"Job {JobId} completed. Generated {ret.Length} new jobs.");
context.SaveChanges(); context.SaveChanges();
context.Jobs.AddRange(newJobs);
context.SaveChanges();
Log.Info($"Job {JobId} completed. Generated {newJobs.Length} new jobs.");
return newJobs;
} }
catch (Exception e) catch (DbUpdateException e)
{
if (e is not DbUpdateException)
{ {
this.state = JobState.Failed; this.state = JobState.Failed;
Log.Error($"Failed to run job {JobId}", e); Log.Error($"Failed to run job {JobId}", e);
context.SaveChanges(); return [];
} }
else
{
Log.Error($"Failed to update Database {JobId}", e);
}
}
Log.Info($"Finished Job {JobId}! (took {DateTime.UtcNow.Subtract(jobStart).TotalMilliseconds}ms)");
return ret ?? [];
} }
protected abstract IEnumerable<Job> RunInternal(PgsqlContext context); protected abstract IEnumerable<Job> RunInternal(PgsqlContext context);

View File

@ -9,7 +9,6 @@ public enum JobType : byte
MoveFileOrFolderJob = 3, MoveFileOrFolderJob = 3,
DownloadMangaCoverJob = 4, DownloadMangaCoverJob = 4,
RetrieveChaptersJob = 5, RetrieveChaptersJob = 5,
UpdateChaptersDownloadedJob = 6, UpdateFilesDownloadedJob = 6,
MoveMangaLibraryJob = 7, MoveMangaLibraryJob = 7
UpdateSingleChapterDownloadedJob = 8,
} }

View File

@ -1,6 +1,5 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using API.Schema.Contexts; using API.Schema.Contexts;
using Microsoft.EntityFrameworkCore.Infrastructure;
namespace API.Schema.Jobs; namespace API.Schema.Jobs;
@ -23,8 +22,8 @@ public class MoveFileOrFolderJob : Job
/// <summary> /// <summary>
/// EF ONLY!!! /// EF ONLY!!!
/// </summary> /// </summary>
internal MoveFileOrFolderJob(ILazyLoader lazyLoader, string jobId, string fromLocation, string toLocation, string? parentJobId) internal MoveFileOrFolderJob(string jobId, string fromLocation, string toLocation, string? parentJobId)
: base(lazyLoader, jobId, JobType.MoveFileOrFolderJob, 0, parentJobId) : base(jobId, JobType.MoveFileOrFolderJob, 0, parentJobId)
{ {
this.FromLocation = fromLocation; this.FromLocation = fromLocation;
this.ToLocation = toLocation; this.ToLocation = toLocation;

View File

@ -1,7 +1,6 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using API.Schema.Contexts; using API.Schema.Contexts;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace API.Schema.Jobs; namespace API.Schema.Jobs;
@ -9,15 +8,7 @@ namespace API.Schema.Jobs;
public class MoveMangaLibraryJob : Job public class MoveMangaLibraryJob : Job
{ {
[StringLength(64)] [Required] public string MangaId { get; init; } [StringLength(64)] [Required] public string MangaId { get; init; }
[JsonIgnore] public Manga Manga { get; init; } = null!;
private Manga _manga = null!;
[JsonIgnore]
public Manga Manga
{
get => LazyLoader.Load(this, ref _manga);
init => _manga = value;
}
[StringLength(64)] [Required] public string ToLibraryId { get; init; } [StringLength(64)] [Required] public string ToLibraryId { get; init; }
public LocalLibrary ToLibrary { get; init; } = null!; public LocalLibrary ToLibrary { get; init; } = null!;
@ -33,8 +24,8 @@ public class MoveMangaLibraryJob : Job
/// <summary> /// <summary>
/// EF ONLY!!! /// EF ONLY!!!
/// </summary> /// </summary>
internal MoveMangaLibraryJob(ILazyLoader lazyLoader, string mangaId, string toLibraryId, string? parentJobId) internal MoveMangaLibraryJob(string mangaId, string toLibraryId, string? parentJobId)
: base(lazyLoader, TokenGen.CreateToken(typeof(MoveMangaLibraryJob)), JobType.MoveMangaLibraryJob, 0, parentJobId) : base(TokenGen.CreateToken(typeof(MoveMangaLibraryJob)), JobType.MoveMangaLibraryJob, 0, parentJobId)
{ {
this.MangaId = mangaId; this.MangaId = mangaId;
this.ToLibraryId = toLibraryId; this.ToLibraryId = toLibraryId;
@ -42,6 +33,8 @@ public class MoveMangaLibraryJob : Job
protected override IEnumerable<Job> RunInternal(PgsqlContext context) protected override IEnumerable<Job> RunInternal(PgsqlContext context)
{ {
context.Attach(Manga);
context.Entry(Manga).Collection<Chapter>(m => m.Chapters).Load();
Dictionary<Chapter, string> oldPath = Manga.Chapters.ToDictionary(c => c, c => c.FullArchiveFilePath); Dictionary<Chapter, string> oldPath = Manga.Chapters.ToDictionary(c => c, c => c.FullArchiveFilePath);
Manga.Library = ToLibrary; Manga.Library = ToLibrary;
try try

View File

@ -1,7 +1,6 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using API.Schema.Contexts; using API.Schema.Contexts;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace API.Schema.Jobs; namespace API.Schema.Jobs;
@ -9,15 +8,7 @@ namespace API.Schema.Jobs;
public class RetrieveChaptersJob : Job public class RetrieveChaptersJob : Job
{ {
[StringLength(64)] [Required] public string MangaId { get; init; } [StringLength(64)] [Required] public string MangaId { get; init; }
[JsonIgnore] public Manga Manga { get; init; } = null!;
private Manga _manga = null!;
[JsonIgnore]
public Manga Manga
{
get => LazyLoader.Load(this, ref _manga);
init => _manga = value;
}
[StringLength(8)] [Required] public string Language { get; private set; } [StringLength(8)] [Required] public string Language { get; private set; }
public RetrieveChaptersJob(Manga manga, string language, ulong recurrenceMs, Job? parentJob = null, ICollection<Job>? dependsOnJobs = null) public RetrieveChaptersJob(Manga manga, string language, ulong recurrenceMs, Job? parentJob = null, ICollection<Job>? dependsOnJobs = null)
@ -31,8 +22,8 @@ public class RetrieveChaptersJob : Job
/// <summary> /// <summary>
/// EF ONLY!!! /// EF ONLY!!!
/// </summary> /// </summary>
internal RetrieveChaptersJob(ILazyLoader lazyLoader, string mangaId, string language, ulong recurrenceMs, string? parentJobId) internal RetrieveChaptersJob(string mangaId, string language, ulong recurrenceMs, string? parentJobId)
: base(lazyLoader, TokenGen.CreateToken(typeof(RetrieveChaptersJob)), JobType.RetrieveChaptersJob, recurrenceMs, parentJobId) : base(TokenGen.CreateToken(typeof(RetrieveChaptersJob)), JobType.RetrieveChaptersJob, recurrenceMs, parentJobId)
{ {
this.MangaId = mangaId; this.MangaId = mangaId;
this.Language = language; this.Language = language;
@ -40,9 +31,10 @@ public class RetrieveChaptersJob : Job
protected override IEnumerable<Job> RunInternal(PgsqlContext context) protected override IEnumerable<Job> RunInternal(PgsqlContext context)
{ {
context.Attach(Manga);
// This gets all chapters that are not downloaded // This gets all chapters that are not downloaded
Chapter[] allChapters = Manga.MangaConnector.GetChapters(Manga, Language); Chapter[] allChapters = Manga.MangaConnector.GetChapters(Manga, Language);
Chapter[] newChapters = allChapters.Where(chapter => Manga.Chapters.Contains(chapter) == false).ToArray(); Chapter[] newChapters = allChapters.Where(chapter => context.Chapters.Contains(chapter) == false).ToArray();
Log.Info($"{newChapters.Length} new chapters."); Log.Info($"{newChapters.Length} new chapters.");
try try

View File

@ -1,41 +0,0 @@
using System.ComponentModel.DataAnnotations;
using API.Schema.Contexts;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Newtonsoft.Json;
namespace API.Schema.Jobs;
public class UpdateChaptersDownloadedJob : Job
{
[StringLength(64)] [Required] public string MangaId { get; init; }
private Manga _manga = null!;
[JsonIgnore]
public Manga Manga
{
get => LazyLoader.Load(this, ref _manga);
init => _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 mangaId, ulong recurrenceMs, string? parentJobId)
: base(lazyLoader, TokenGen.CreateToken(typeof(UpdateChaptersDownloadedJob)), JobType.UpdateChaptersDownloadedJob, recurrenceMs, parentJobId)
{
this.MangaId = mangaId;
}
protected override IEnumerable<Job> RunInternal(PgsqlContext context)
{
return Manga.Chapters.Select(c => new UpdateSingleChapterDownloadedJob(c, this));
}
}

View File

@ -0,0 +1,46 @@
using System.ComponentModel.DataAnnotations;
using API.Schema.Contexts;
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;
namespace API.Schema.Jobs;
public class UpdateFilesDownloadedJob : Job
{
[StringLength(64)] [Required] public string MangaId { get; init; }
[JsonIgnore] public Manga Manga { get; init; } = null!;
public UpdateFilesDownloadedJob(Manga manga, ulong recurrenceMs, Job? parentJob = null, ICollection<Job>? dependsOnJobs = null)
: base(TokenGen.CreateToken(typeof(UpdateFilesDownloadedJob)), JobType.UpdateFilesDownloadedJob, recurrenceMs, parentJob, dependsOnJobs)
{
this.MangaId = manga.MangaId;
this.Manga = manga;
}
/// <summary>
/// EF ONLY!!!
/// </summary>
internal UpdateFilesDownloadedJob(string mangaId, ulong recurrenceMs, string? parentJobId)
: base(TokenGen.CreateToken(typeof(UpdateFilesDownloadedJob)), JobType.UpdateFilesDownloadedJob, recurrenceMs, parentJobId)
{
this.MangaId = mangaId;
}
protected override IEnumerable<Job> RunInternal(PgsqlContext context)
{
context.Attach(Manga);
context.Entry(Manga).Collection<Chapter>(m => m.Chapters).Load();
foreach (Chapter chapter in Manga.Chapters)
chapter.Downloaded = chapter.CheckDownloaded();
try
{
context.SaveChanges();
}
catch (DbUpdateException e)
{
Log.Error(e);
}
return [];
}
}

View File

@ -1,52 +0,0 @@
using System.ComponentModel.DataAnnotations;
using API.Schema.Contexts;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Newtonsoft.Json;
namespace API.Schema.Jobs;
public class UpdateSingleChapterDownloadedJob : Job
{
[StringLength(64)] [Required] public string ChapterId { get; init; }
private Chapter _chapter = null!;
[JsonIgnore]
public Chapter Chapter
{
get => LazyLoader.Load(this, ref _chapter);
init => _chapter = value;
}
public UpdateSingleChapterDownloadedJob(Chapter chapter, Job? parentJob = null, ICollection<Job>? dependsOnJobs = null)
: base(TokenGen.CreateToken(typeof(UpdateSingleChapterDownloadedJob)), JobType.UpdateSingleChapterDownloadedJob, 0, parentJob, dependsOnJobs)
{
this.ChapterId = chapter.ChapterId;
this.Chapter = chapter;
}
/// <summary>
/// EF ONLY!!!
/// </summary>
internal UpdateSingleChapterDownloadedJob(ILazyLoader lazyLoader, string chapterId, string? parentJobId)
: base(lazyLoader, TokenGen.CreateToken(typeof(UpdateSingleChapterDownloadedJob)), JobType.UpdateSingleChapterDownloadedJob, 0, parentJobId)
{
this.ChapterId = chapterId;
}
protected override IEnumerable<Job> RunInternal(PgsqlContext context)
{
Chapter.Downloaded = Chapter.CheckDownloaded();
try
{
context.SaveChanges();
}
catch (DbUpdateException e)
{
Log.Error(e);
}
return [];
}
}

View File

@ -4,7 +4,6 @@ using System.Runtime.InteropServices;
using System.Text; using System.Text;
using API.Schema.MangaConnectors; using API.Schema.MangaConnectors;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Newtonsoft.Json; using Newtonsoft.Json;
using static System.IO.UnixFileMode; using static System.IO.UnixFileMode;
@ -47,15 +46,7 @@ public class Manga
[NotMapped] [NotMapped]
public string? FullDirectoryPath => Library is not null ? Path.Join(Library.BasePath, DirectoryName) : null; public string? FullDirectoryPath => Library is not null ? Path.Join(Library.BasePath, DirectoryName) : null;
[NotMapped] public ICollection<string> ChapterIds => Chapters.Select(c => c.ChapterId).ToList(); [JsonIgnore] public ICollection<Chapter> Chapters { get; internal set; } = [];
private readonly ILazyLoader _lazyLoader = null!;
private ICollection<Chapter> _chapters = null!;
[JsonIgnore]
public ICollection<Chapter> Chapters
{
get => _lazyLoader.Load(this, ref _chapters);
init => _chapters = value;
}
public Manga(string idOnConnector, string name, string description, string websiteUrl, string coverUrl, MangaReleaseStatus releaseStatus, 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, MangaConnector mangaConnector, ICollection<Author> authors, ICollection<MangaTag> mangaTags, ICollection<Link> links, ICollection<MangaAltTitle> altTitles,
@ -80,16 +71,14 @@ public class Manga
this.DirectoryName = CleanDirectoryName(name); this.DirectoryName = CleanDirectoryName(name);
this.Year = year; this.Year = year;
this.OriginalLanguage = originalLanguage; this.OriginalLanguage = originalLanguage;
this.Chapters = [];
} }
/// <summary> /// <summary>
/// EF ONLY!!! /// EF ONLY!!!
/// </summary> /// </summary>
public Manga(ILazyLoader lazyLoader, string mangaId, string idOnConnectorSite, string name, string description, string websiteUrl, string coverUrl, MangaReleaseStatus releaseStatus, public Manga(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) string mangaConnectorName, string directoryName, float ignoreChaptersBefore, string? libraryId, uint? year, string? originalLanguage)
{ {
this._lazyLoader = lazyLoader;
this.MangaId = mangaId; this.MangaId = mangaId;
this.IdOnConnectorSite = idOnConnectorSite; this.IdOnConnectorSite = idOnConnectorSite;
this.Name = name; this.Name = name;
@ -108,9 +97,7 @@ public class Manga
public string CreatePublicationFolder() public string CreatePublicationFolder()
{ {
string? publicationFolder = FullDirectoryPath; string publicationFolder = FullDirectoryPath;
if (publicationFolder is null)
throw new DirectoryNotFoundException("Publication folder not found");
if(!Directory.Exists(publicationFolder)) if(!Directory.Exists(publicationFolder))
Directory.CreateDirectory(publicationFolder); Directory.CreateDirectory(publicationFolder);
if(RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) if(RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
@ -144,7 +131,7 @@ public class Manga
StringBuilder sb = new (); StringBuilder sb = new ();
foreach (char c in name) foreach (char c in name)
{ {
if (c >= 32 && c < 127 && ForbiddenCharsBelow127.Contains(c) == false) if (c > 32 && c < 127 && ForbiddenCharsBelow127.Contains(c) == false)
sb.Append(c); sb.Append(c);
else if (c > 127 && c < 152 && IncludeCharsAbove127.Contains(c)) else if (c > 127 && c < 152 && IncludeCharsAbove127.Contains(c))
sb.Append(c); sb.Append(c);

View File

@ -5,7 +5,7 @@ namespace API;
public static class TokenGen public static class TokenGen
{ {
private const int MinimumLength = 16; private const int MinimumLength = 32;
private const int MaximumLength = 64; private const int MaximumLength = 64;
private const string Chars = "abcdefghijklmnopqrstuvwxyz0123456789"; private const string Chars = "abcdefghijklmnopqrstuvwxyz0123456789";

View File

@ -12,16 +12,6 @@ namespace API;
public static class Tranga public static class Tranga
{ {
// ReSharper disable once InconsistentNaming
private const string TRANGA =
"\n\n" +
" _______ v2\n" +
"|_ _|.----..---.-..-----..-----..---.-.\n" +
" | | | _|| _ || || _ || _ |\n" +
" |___| |__| |___._||__|__||___ ||___._|\n" +
" |_____| \n\n";
public static Thread NotificationSenderThread { get; } = new (NotificationSender); public static Thread NotificationSenderThread { get; } = new (NotificationSender);
public static Thread JobStarterThread { get; } = new (JobStarter); public static Thread JobStarterThread { get; } = new (JobStarter);
private static readonly ILog Log = LogManager.GetLogger(typeof(Tranga)); private static readonly ILog Log = LogManager.GetLogger(typeof(Tranga));
@ -30,7 +20,6 @@ public static class Tranga
{ {
BasicConfigurator.Configure(); BasicConfigurator.Configure();
Log.Info("Logger Configured."); Log.Info("Logger Configured.");
Log.Info(TRANGA);
} }
private static void NotificationSender(object? serviceProviderObj) private static void NotificationSender(object? serviceProviderObj)
@ -93,10 +82,17 @@ public static class Tranga
Log.Error("Error sending notifications.", e); Log.Error("Error sending notifications.", e);
} }
} }
private const string TRANGA =
"\n\n" +
" _______ \n" +
"|_ _|.----..---.-..-----..-----..---.-.\n" +
" | | | _|| _ || || _ || _ |\n" +
" |___| |__| |___._||__|__||___ ||___._|\n" +
" |_____| \n\n";
private static readonly Dictionary<Thread, Job> RunningJobs = new(); private static readonly Dictionary<Thread, Job> RunningJobs = new();
private static void JobStarter(object? serviceProviderObj) private static void JobStarter(object? serviceProviderObj)
{ {
Log.Info("JobStarter Thread running.");
if (serviceProviderObj is null) if (serviceProviderObj is null)
{ {
Log.Error("serviceProviderObj is null"); Log.Error("serviceProviderObj is null");
@ -104,20 +100,23 @@ public static class Tranga
} }
IServiceProvider serviceProvider = (IServiceProvider)serviceProviderObj; IServiceProvider serviceProvider = (IServiceProvider)serviceProviderObj;
using IServiceScope scope = serviceProvider.CreateScope(); using IServiceScope scope = serviceProvider.CreateScope();
PgsqlContext context = scope.ServiceProvider.GetRequiredService<PgsqlContext>(); PgsqlContext? context = scope.ServiceProvider.GetService<PgsqlContext>();
if (context is null)
{
Log.Error("PgsqlContext is null");
return;
}
Log.Info(TRANGA);
Log.Info("Loading Jobs");
context.Jobs.Load();
Log.Info("JobStarter Thread running.");
while (true) while (true)
{ {
Log.Debug("Starting Job-Cycle...");
DateTime cycleStart = DateTime.UtcNow;
Log.Debug("Loading Jobs...");
DateTime loadStart = DateTime.UtcNow;
context.Jobs.Load();
Log.Debug("Updating Entries...");
foreach (EntityEntry entityEntry in context.ChangeTracker.Entries().ToArray()) foreach (EntityEntry entityEntry in context.ChangeTracker.Entries().ToArray())
entityEntry.Reload(); entityEntry.Reload();
Log.Debug($"Jobs Loaded! (took {DateTime.UtcNow.Subtract(loadStart).TotalMilliseconds}ms)");
//Update finished Jobs to new states //Update finished Jobs to new states
context.Jobs.Load();
List<Job> completedJobs = context.Jobs.Local.Where(j => j.state == JobState.Completed).ToList(); List<Job> completedJobs = context.Jobs.Local.Where(j => j.state == JobState.Completed).ToList();
foreach (Job completedJob in completedJobs) foreach (Job completedJob in completedJobs)
if (completedJob.RecurrenceMs <= 0) if (completedJob.RecurrenceMs <= 0)
@ -137,8 +136,6 @@ public static class Tranga
//Retrieve waiting and due Jobs //Retrieve waiting and due Jobs
List<Job> runningJobs = context.Jobs.Local.Where(j => j.state == JobState.Running).ToList(); List<Job> runningJobs = context.Jobs.Local.Where(j => j.state == JobState.Running).ToList();
DateTime filterStart = DateTime.UtcNow;
Log.Debug("Filtering Jobs...");
List<MangaConnector> busyConnectors = GetBusyConnectors(runningJobs); List<MangaConnector> busyConnectors = GetBusyConnectors(runningJobs);
List<Job> waitingJobs = GetWaitingJobs(context.Jobs.Local.ToList()); List<Job> waitingJobs = GetWaitingJobs(context.Jobs.Local.ToList());
@ -149,7 +146,6 @@ public static class Tranga
List<Job> jobsWithoutDownloading = List<Job> jobsWithoutDownloading =
jobsWithoutMissingDependencies jobsWithoutMissingDependencies
.Where(j => j.JobType != JobType.DownloadSingleChapterJob) .Where(j => j.JobType != JobType.DownloadSingleChapterJob)
.DistinctBy(j => j.JobType)
.ToList(); .ToList();
List<Job> firstChapterPerConnector = List<Job> firstChapterPerConnector =
jobsWithoutMissingDependencies jobsWithoutMissingDependencies
@ -167,8 +163,6 @@ public static class Tranga
.ToList(); .ToList();
List<Job> startJobs = jobsWithoutDownloading.Concat(firstChapterPerConnector).ToList(); List<Job> startJobs = jobsWithoutDownloading.Concat(firstChapterPerConnector).ToList();
Log.Debug($"Jobs Filtered! (took {DateTime.UtcNow.Subtract(filterStart).TotalMilliseconds}ms)");
//Start Jobs that are allowed to run (preconditions match) //Start Jobs that are allowed to run (preconditions match)
foreach (Job job in startJobs) foreach (Job job in startJobs)
@ -201,7 +195,6 @@ public static class Tranga
{ {
Log.Error("Failed saving Job changes.", e); Log.Error("Failed saving Job changes.", e);
} }
Log.Debug($"Job-Cycle over! (took {DateTime.UtcNow.Subtract(cycleStart).TotalMilliseconds}ms)");
Thread.Sleep(TrangaSettings.startNewJobTimeoutMs); Thread.Sleep(TrangaSettings.startNewJobTimeoutMs);
} }
} }