diff --git a/API/Controllers/JobController.cs b/API/Controllers/JobController.cs
index a4011ea..5424239 100644
--- a/API/Controllers/JobController.cs
+++ b/API/Controllers/JobController.cs
@@ -158,7 +158,7 @@ public class JobController(PgsqlContext context, ILog Log) : Controller
}
///
- /// Create a new UpdateFilesDownloadedJob
+ /// Create a new UpdateChaptersDownloadedJob
///
/// ID of the Manga
/// Job-IDs
@@ -172,7 +172,7 @@ public class JobController(PgsqlContext context, ILog Log) : Controller
{
if(context.Mangas.Find(MangaId) is not { } m)
return NotFound();
- Job job = new UpdateFilesDownloadedJob(m, 0);
+ Job job = new UpdateChaptersDownloadedJob(m, 0);
return AddJobs([job]);
}
@@ -186,7 +186,7 @@ public class JobController(PgsqlContext context, ILog Log) : Controller
[ProducesResponseType(Status500InternalServerError, "text/plain")]
public IActionResult CreateUpdateAllFilesDownloadedJob()
{
- List jobs = context.Mangas.Select(m => new UpdateFilesDownloadedJob(m, 0, null, null)).ToList();
+ List jobs = context.Mangas.Select(m => new UpdateChaptersDownloadedJob(m, 0, null, null)).ToList();
try
{
context.Jobs.AddRange(jobs);
diff --git a/API/Controllers/MangaController.cs b/API/Controllers/MangaController.cs
index 6098d3a..3ec580c 100644
--- a/API/Controllers/MangaController.cs
+++ b/API/Controllers/MangaController.cs
@@ -343,7 +343,7 @@ public class MangaController(PgsqlContext context, ILog Log) : Controller
return NotFound();
MoveMangaLibraryJob moveLibrary = new(manga, library);
- UpdateFilesDownloadedJob updateDownloadedFiles = new(manga, 0, dependsOnJobs: [moveLibrary]);
+ UpdateChaptersDownloadedJob updateDownloadedFiles = new(manga, 0, dependsOnJobs: [moveLibrary]);
try
{
diff --git a/API/Migrations/pgsql/20250515120724_Initial-1.Designer.cs b/API/Migrations/pgsql/20250515120724_Initial-1.Designer.cs
index 9b0185b..4b1b2cb 100644
--- a/API/Migrations/pgsql/20250515120724_Initial-1.Designer.cs
+++ b/API/Migrations/pgsql/20250515120724_Initial-1.Designer.cs
@@ -416,7 +416,7 @@ namespace API.Migrations.pgsql
b.HasDiscriminator().HasValue((byte)5);
});
- modelBuilder.Entity("API.Schema.Jobs.UpdateFilesDownloadedJob", b =>
+ modelBuilder.Entity("API.Schema.Jobs.UpdateChaptersDownloadedJob", b =>
{
b.HasBaseType("API.Schema.Jobs.Job");
@@ -661,7 +661,7 @@ namespace API.Migrations.pgsql
b.Navigation("Manga");
});
- modelBuilder.Entity("API.Schema.Jobs.UpdateFilesDownloadedJob", b =>
+ modelBuilder.Entity("API.Schema.Jobs.UpdateChaptersDownloadedJob", b =>
{
b.HasOne("API.Schema.Manga", "Manga")
.WithMany()
diff --git a/API/Migrations/pgsql/20250516121442_AltTitle-Owned.Designer.cs b/API/Migrations/pgsql/20250516121442_AltTitle-Owned.Designer.cs
index 29f52b0..c97d81d 100644
--- a/API/Migrations/pgsql/20250516121442_AltTitle-Owned.Designer.cs
+++ b/API/Migrations/pgsql/20250516121442_AltTitle-Owned.Designer.cs
@@ -416,7 +416,7 @@ namespace API.Migrations.pgsql
b.HasDiscriminator().HasValue((byte)5);
});
- modelBuilder.Entity("API.Schema.Jobs.UpdateFilesDownloadedJob", b =>
+ modelBuilder.Entity("API.Schema.Jobs.UpdateChaptersDownloadedJob", b =>
{
b.HasBaseType("API.Schema.Jobs.Job");
@@ -667,7 +667,7 @@ namespace API.Migrations.pgsql
b.Navigation("Manga");
});
- modelBuilder.Entity("API.Schema.Jobs.UpdateFilesDownloadedJob", b =>
+ modelBuilder.Entity("API.Schema.Jobs.UpdateChaptersDownloadedJob", b =>
{
b.HasOne("API.Schema.Manga", "Manga")
.WithMany()
diff --git a/API/Migrations/pgsql/20250516121725_Manga-Year-Nullable.Designer.cs b/API/Migrations/pgsql/20250516121725_Manga-Year-Nullable.Designer.cs
index afe97c3..2ebe310 100644
--- a/API/Migrations/pgsql/20250516121725_Manga-Year-Nullable.Designer.cs
+++ b/API/Migrations/pgsql/20250516121725_Manga-Year-Nullable.Designer.cs
@@ -416,7 +416,7 @@ namespace API.Migrations.pgsql
b.HasDiscriminator().HasValue((byte)5);
});
- modelBuilder.Entity("API.Schema.Jobs.UpdateFilesDownloadedJob", b =>
+ modelBuilder.Entity("API.Schema.Jobs.UpdateChaptersDownloadedJob", b =>
{
b.HasBaseType("API.Schema.Jobs.Job");
@@ -667,7 +667,7 @@ namespace API.Migrations.pgsql
b.Navigation("Manga");
});
- modelBuilder.Entity("API.Schema.Jobs.UpdateFilesDownloadedJob", b =>
+ modelBuilder.Entity("API.Schema.Jobs.UpdateChaptersDownloadedJob", b =>
{
b.HasOne("API.Schema.Manga", "Manga")
.WithMany()
diff --git a/API/Migrations/pgsql/20250516122242_AltTitle-Owned-WithId.Designer.cs b/API/Migrations/pgsql/20250516122242_AltTitle-Owned-WithId.Designer.cs
index 30b595c..7b86671 100644
--- a/API/Migrations/pgsql/20250516122242_AltTitle-Owned-WithId.Designer.cs
+++ b/API/Migrations/pgsql/20250516122242_AltTitle-Owned-WithId.Designer.cs
@@ -416,7 +416,7 @@ namespace API.Migrations.pgsql
b.HasDiscriminator().HasValue((byte)5);
});
- modelBuilder.Entity("API.Schema.Jobs.UpdateFilesDownloadedJob", b =>
+ modelBuilder.Entity("API.Schema.Jobs.UpdateChaptersDownloadedJob", b =>
{
b.HasBaseType("API.Schema.Jobs.Job");
@@ -668,7 +668,7 @@ namespace API.Migrations.pgsql
b.Navigation("Manga");
});
- modelBuilder.Entity("API.Schema.Jobs.UpdateFilesDownloadedJob", b =>
+ modelBuilder.Entity("API.Schema.Jobs.UpdateChaptersDownloadedJob", b =>
{
b.HasOne("API.Schema.Manga", "Manga")
.WithMany()
diff --git a/API/Migrations/pgsql/20250516180953_Split-UpdateChaptersDownloadedJob-Into-UpdateSingleChapterDownloadedJob.Designer.cs b/API/Migrations/pgsql/20250516180953_Split-UpdateChaptersDownloadedJob-Into-UpdateSingleChapterDownloadedJob.Designer.cs
new file mode 100644
index 0000000..effec49
--- /dev/null
+++ b/API/Migrations/pgsql/20250516180953_Split-UpdateChaptersDownloadedJob-Into-UpdateSingleChapterDownloadedJob.Designer.cs
@@ -0,0 +1,720 @@
+//
+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
+ {
+ ///
+ 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("AuthorId")
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)");
+
+ b.Property("AuthorName")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)");
+
+ b.HasKey("AuthorId");
+
+ b.ToTable("Authors");
+ });
+
+ modelBuilder.Entity("API.Schema.Chapter", b =>
+ {
+ b.Property("ChapterId")
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)");
+
+ b.Property("ChapterNumber")
+ .IsRequired()
+ .HasMaxLength(10)
+ .HasColumnType("character varying(10)");
+
+ b.Property("Downloaded")
+ .HasColumnType("boolean");
+
+ b.Property("FileName")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.Property("ParentMangaId")
+ .IsRequired()
+ .HasColumnType("character varying(64)");
+
+ b.Property("Title")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.Property("Url")
+ .IsRequired()
+ .HasMaxLength(2048)
+ .HasColumnType("character varying(2048)");
+
+ b.Property("VolumeNumber")
+ .HasColumnType("integer");
+
+ b.HasKey("ChapterId");
+
+ b.HasIndex("ParentMangaId");
+
+ b.ToTable("Chapters");
+ });
+
+ modelBuilder.Entity("API.Schema.Jobs.Job", b =>
+ {
+ b.Property("JobId")
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)");
+
+ b.Property("Enabled")
+ .HasColumnType("boolean");
+
+ b.Property("JobType")
+ .HasColumnType("smallint");
+
+ b.Property("LastExecution")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("ParentJobId")
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)");
+
+ b.Property("RecurrenceMs")
+ .HasColumnType("numeric(20,0)");
+
+ b.Property("state")
+ .HasColumnType("smallint");
+
+ b.HasKey("JobId");
+
+ b.HasIndex("ParentJobId");
+
+ b.ToTable("Jobs");
+
+ b.HasDiscriminator("JobType");
+
+ b.UseTphMappingStrategy();
+ });
+
+ modelBuilder.Entity("API.Schema.LocalLibrary", b =>
+ {
+ b.Property("LocalLibraryId")
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)");
+
+ b.Property("BasePath")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.Property("LibraryName")
+ .IsRequired()
+ .HasMaxLength(512)
+ .HasColumnType("character varying(512)");
+
+ b.HasKey("LocalLibraryId");
+
+ b.ToTable("LocalLibraries");
+ });
+
+ modelBuilder.Entity("API.Schema.Manga", b =>
+ {
+ b.Property("MangaId")
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)");
+
+ b.Property("CoverFileNameInCache")
+ .HasMaxLength(512)
+ .HasColumnType("character varying(512)");
+
+ b.Property("CoverUrl")
+ .IsRequired()
+ .HasMaxLength(512)
+ .HasColumnType("character varying(512)");
+
+ b.Property("Description")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("DirectoryName")
+ .IsRequired()
+ .HasMaxLength(1024)
+ .HasColumnType("character varying(1024)");
+
+ b.Property("IdOnConnectorSite")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.Property("IgnoreChaptersBefore")
+ .HasColumnType("real");
+
+ b.Property("LibraryId")
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)");
+
+ b.Property("MangaConnectorName")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(512)
+ .HasColumnType("character varying(512)");
+
+ b.Property("OriginalLanguage")
+ .HasMaxLength(8)
+ .HasColumnType("character varying(8)");
+
+ b.Property("ReleaseStatus")
+ .HasColumnType("smallint");
+
+ b.Property("WebsiteUrl")
+ .IsRequired()
+ .HasMaxLength(512)
+ .HasColumnType("character varying(512)");
+
+ b.Property("Year")
+ .HasColumnType("bigint");
+
+ b.HasKey("MangaId");
+
+ b.HasIndex("LibraryId");
+
+ b.HasIndex("MangaConnectorName");
+
+ b.ToTable("Mangas");
+ });
+
+ modelBuilder.Entity("API.Schema.MangaConnectors.MangaConnector", b =>
+ {
+ b.Property("Name")
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)");
+
+ b.PrimitiveCollection("BaseUris")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("text[]");
+
+ b.Property("Enabled")
+ .HasColumnType("boolean");
+
+ b.Property("IconUrl")
+ .IsRequired()
+ .HasMaxLength(2048)
+ .HasColumnType("character varying(2048)");
+
+ b.PrimitiveCollection("SupportedLanguages")
+ .IsRequired()
+ .HasMaxLength(8)
+ .HasColumnType("text[]");
+
+ b.HasKey("Name");
+
+ b.ToTable("MangaConnectors");
+
+ b.HasDiscriminator("Name").HasValue("MangaConnector");
+
+ b.UseTphMappingStrategy();
+ });
+
+ modelBuilder.Entity("API.Schema.MangaTag", b =>
+ {
+ b.Property("Tag")
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)");
+
+ b.HasKey("Tag");
+
+ b.ToTable("Tags");
+ });
+
+ modelBuilder.Entity("AuthorToManga", b =>
+ {
+ b.Property("AuthorIds")
+ .HasColumnType("character varying(64)");
+
+ b.Property("MangaIds")
+ .HasColumnType("character varying(64)");
+
+ b.HasKey("AuthorIds", "MangaIds");
+
+ b.HasIndex("MangaIds");
+
+ b.ToTable("AuthorToManga");
+ });
+
+ modelBuilder.Entity("JobJob", b =>
+ {
+ b.Property("DependsOnJobsJobId")
+ .HasColumnType("character varying(64)");
+
+ b.Property("JobId")
+ .HasColumnType("character varying(64)");
+
+ b.HasKey("DependsOnJobsJobId", "JobId");
+
+ b.HasIndex("JobId");
+
+ b.ToTable("JobJob");
+ });
+
+ modelBuilder.Entity("MangaTagToManga", b =>
+ {
+ b.Property("MangaTagIds")
+ .HasColumnType("character varying(64)");
+
+ b.Property("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("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("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("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("FromLocation")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.Property("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("MangaId")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)");
+
+ b.Property("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("Language")
+ .IsRequired()
+ .HasMaxLength(8)
+ .HasColumnType("character varying(8)");
+
+ b.Property("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("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("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("LinkId")
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)");
+
+ b1.Property("LinkProvider")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)");
+
+ b1.Property("LinkUrl")
+ .IsRequired()
+ .HasMaxLength(2048)
+ .HasColumnType("character varying(2048)");
+
+ b1.Property("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("AltTitleId")
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)");
+
+ b1.Property("Language")
+ .IsRequired()
+ .HasMaxLength(8)
+ .HasColumnType("character varying(8)");
+
+ b1.Property("MangaId")
+ .IsRequired()
+ .HasColumnType("character varying(64)");
+
+ b1.Property("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
+ }
+ }
+}
diff --git a/API/Migrations/pgsql/20250516180953_Split-UpdateChaptersDownloadedJob-Into-UpdateSingleChapterDownloadedJob.cs b/API/Migrations/pgsql/20250516180953_Split-UpdateChaptersDownloadedJob-Into-UpdateSingleChapterDownloadedJob.cs
new file mode 100644
index 0000000..eff3b29
--- /dev/null
+++ b/API/Migrations/pgsql/20250516180953_Split-UpdateChaptersDownloadedJob-Into-UpdateSingleChapterDownloadedJob.cs
@@ -0,0 +1,94 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace API.Migrations.pgsql
+{
+ ///
+ public partial class SplitUpdateChaptersDownloadedJobIntoUpdateSingleChapterDownloadedJob : Migration
+ {
+ ///
+ 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(
+ 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);
+ }
+
+ ///
+ 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);
+ }
+ }
+}
diff --git a/API/Migrations/pgsql/PgsqlContextModelSnapshot.cs b/API/Migrations/pgsql/PgsqlContextModelSnapshot.cs
index 623bec6..9153829 100644
--- a/API/Migrations/pgsql/PgsqlContextModelSnapshot.cs
+++ b/API/Migrations/pgsql/PgsqlContextModelSnapshot.cs
@@ -413,7 +413,7 @@ namespace API.Migrations.pgsql
b.HasDiscriminator().HasValue((byte)5);
});
- modelBuilder.Entity("API.Schema.Jobs.UpdateFilesDownloadedJob", b =>
+ modelBuilder.Entity("API.Schema.Jobs.UpdateChaptersDownloadedJob", b =>
{
b.HasBaseType("API.Schema.Jobs.Job");
@@ -427,12 +427,32 @@ namespace API.Migrations.pgsql
b.ToTable("Jobs", t =>
{
t.Property("MangaId")
- .HasColumnName("UpdateFilesDownloadedJob_MangaId");
+ .HasColumnName("UpdateChaptersDownloadedJob_MangaId");
});
b.HasDiscriminator().HasValue((byte)6);
});
+ modelBuilder.Entity("API.Schema.Jobs.UpdateSingleChapterDownloadedJob", b =>
+ {
+ b.HasBaseType("API.Schema.Jobs.Job");
+
+ b.Property("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");
@@ -665,7 +685,7 @@ namespace API.Migrations.pgsql
b.Navigation("Manga");
});
- modelBuilder.Entity("API.Schema.Jobs.UpdateFilesDownloadedJob", b =>
+ modelBuilder.Entity("API.Schema.Jobs.UpdateChaptersDownloadedJob", b =>
{
b.HasOne("API.Schema.Manga", "Manga")
.WithMany()
@@ -676,6 +696,17 @@ namespace API.Migrations.pgsql
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");
diff --git a/API/Program.cs b/API/Program.cs
index 1953c47..f4b4b24 100644
--- a/API/Program.cs
+++ b/API/Program.cs
@@ -126,7 +126,7 @@ using (IServiceScope scope = app.Services.CreateScope())
.Select(dacj =>
{
DownloadAvailableChaptersJob? j = dacj as DownloadAvailableChaptersJob;
- return new UpdateFilesDownloadedJob(j!.Manga, 0);
+ return new UpdateChaptersDownloadedJob(j!.Manga, 0);
}));
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))
diff --git a/API/Schema/Contexts/PgsqlContext.cs b/API/Schema/Contexts/PgsqlContext.cs
index aa1b25b..1ca0fed 100644
--- a/API/Schema/Contexts/PgsqlContext.cs
+++ b/API/Schema/Contexts/PgsqlContext.cs
@@ -38,7 +38,8 @@ public class PgsqlContext(DbContextOptions options) : DbContext(op
.HasValue(JobType.DownloadSingleChapterJob)
.HasValue(JobType.DownloadMangaCoverJob)
.HasValue(JobType.RetrieveChaptersJob)
- .HasValue(JobType.UpdateFilesDownloadedJob);
+ .HasValue(JobType.UpdateChaptersDownloadedJob)
+ .HasValue(JobType.UpdateSingleChapterDownloadedJob);
//Job specification
modelBuilder.Entity()
@@ -95,13 +96,13 @@ public class PgsqlContext(DbContextOptions options) : DbContext(op
modelBuilder.Entity()
.Navigation(j => j.Manga)
.AutoInclude();
- modelBuilder.Entity()
+ modelBuilder.Entity()
.HasOne(j => j.Manga)
.WithMany()
.HasForeignKey(j => j.MangaId)
.IsRequired()
.OnDelete(DeleteBehavior.Cascade);
- modelBuilder.Entity()
+ modelBuilder.Entity()
.Navigation(j => j.Manga)
.AutoInclude();
diff --git a/API/Schema/Jobs/DownloadSingleChapterJob.cs b/API/Schema/Jobs/DownloadSingleChapterJob.cs
index f336ac2..5a84e7e 100644
--- a/API/Schema/Jobs/DownloadSingleChapterJob.cs
+++ b/API/Schema/Jobs/DownloadSingleChapterJob.cs
@@ -102,14 +102,14 @@ public class DownloadSingleChapterJob : Job
context.Jobs.Load();
if (context.Jobs.AsEnumerable().Any(j =>
{
- if (j.JobType != JobType.UpdateFilesDownloadedJob)
+ if (j.JobType != JobType.UpdateChaptersDownloadedJob)
return false;
- UpdateFilesDownloadedJob job = (UpdateFilesDownloadedJob)j;
+ UpdateChaptersDownloadedJob job = (UpdateChaptersDownloadedJob)j;
return job.MangaId == this.Chapter.ParentMangaId;
}))
return [];
- return [new UpdateFilesDownloadedJob(Chapter.ParentManga, 0, this.ParentJob)];
+ return [new UpdateChaptersDownloadedJob(Chapter.ParentManga, 0, this.ParentJob)];
}
private void ProcessImage(string imagePath)
diff --git a/API/Schema/Jobs/JobType.cs b/API/Schema/Jobs/JobType.cs
index 6919313..4ce607e 100644
--- a/API/Schema/Jobs/JobType.cs
+++ b/API/Schema/Jobs/JobType.cs
@@ -9,6 +9,7 @@ public enum JobType : byte
MoveFileOrFolderJob = 3,
DownloadMangaCoverJob = 4,
RetrieveChaptersJob = 5,
- UpdateFilesDownloadedJob = 6,
- MoveMangaLibraryJob = 7
+ UpdateChaptersDownloadedJob = 6,
+ MoveMangaLibraryJob = 7,
+ UpdateSingleChapterDownloadedJob = 8,
}
\ No newline at end of file
diff --git a/API/Schema/Jobs/UpdateChaptersDownloadedJob.cs b/API/Schema/Jobs/UpdateChaptersDownloadedJob.cs
new file mode 100644
index 0000000..a3d3859
--- /dev/null
+++ b/API/Schema/Jobs/UpdateChaptersDownloadedJob.cs
@@ -0,0 +1,34 @@
+using System.ComponentModel.DataAnnotations;
+using API.Schema.Contexts;
+using Microsoft.EntityFrameworkCore;
+using Newtonsoft.Json;
+
+namespace API.Schema.Jobs;
+
+public class UpdateChaptersDownloadedJob : Job
+{
+ [StringLength(64)] [Required] public string MangaId { get; init; }
+ [JsonIgnore] public Manga Manga { get; init; } = null!;
+
+ public UpdateChaptersDownloadedJob(Manga manga, ulong recurrenceMs, Job? parentJob = null, ICollection? dependsOnJobs = null)
+ : base(TokenGen.CreateToken(typeof(UpdateChaptersDownloadedJob)), JobType.UpdateChaptersDownloadedJob, recurrenceMs, parentJob, dependsOnJobs)
+ {
+ this.MangaId = manga.MangaId;
+ this.Manga = manga;
+ }
+
+ ///
+ /// EF ONLY!!!
+ ///
+ internal UpdateChaptersDownloadedJob(string mangaId, ulong recurrenceMs, string? parentJobId)
+ : base(TokenGen.CreateToken(typeof(UpdateChaptersDownloadedJob)), JobType.UpdateChaptersDownloadedJob, recurrenceMs, parentJobId)
+ {
+ this.MangaId = mangaId;
+ }
+
+ protected override IEnumerable RunInternal(PgsqlContext context)
+ {
+ context.Attach(Manga);
+ return Manga.Chapters.Select(c => new UpdateSingleChapterDownloadedJob(c, this));
+ }
+}
\ No newline at end of file
diff --git a/API/Schema/Jobs/UpdateFilesDownloadedJob.cs b/API/Schema/Jobs/UpdateFilesDownloadedJob.cs
deleted file mode 100644
index 962b0d2..0000000
--- a/API/Schema/Jobs/UpdateFilesDownloadedJob.cs
+++ /dev/null
@@ -1,46 +0,0 @@
-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? dependsOnJobs = null)
- : base(TokenGen.CreateToken(typeof(UpdateFilesDownloadedJob)), JobType.UpdateFilesDownloadedJob, recurrenceMs, parentJob, dependsOnJobs)
- {
- this.MangaId = manga.MangaId;
- this.Manga = manga;
- }
-
- ///
- /// EF ONLY!!!
- ///
- internal UpdateFilesDownloadedJob(string mangaId, ulong recurrenceMs, string? parentJobId)
- : base(TokenGen.CreateToken(typeof(UpdateFilesDownloadedJob)), JobType.UpdateFilesDownloadedJob, recurrenceMs, parentJobId)
- {
- this.MangaId = mangaId;
- }
-
- protected override IEnumerable RunInternal(PgsqlContext context)
- {
- context.Attach(Manga);
- context.Entry(Manga).Collection(m => m.Chapters).Load();
- foreach (Chapter chapter in Manga.Chapters)
- chapter.Downloaded = chapter.CheckDownloaded();
-
- try
- {
- context.SaveChanges();
- }
- catch (DbUpdateException e)
- {
- Log.Error(e);
- }
- return [];
- }
-}
\ No newline at end of file
diff --git a/API/Schema/Jobs/UpdateSingleChapterDownloadedJob.cs b/API/Schema/Jobs/UpdateSingleChapterDownloadedJob.cs
new file mode 100644
index 0000000..15638c4
--- /dev/null
+++ b/API/Schema/Jobs/UpdateSingleChapterDownloadedJob.cs
@@ -0,0 +1,45 @@
+using System.ComponentModel.DataAnnotations;
+using API.Schema.Contexts;
+using Microsoft.EntityFrameworkCore;
+using Newtonsoft.Json;
+
+namespace API.Schema.Jobs;
+
+public class UpdateSingleChapterDownloadedJob : Job
+{
+ [StringLength(64)] [Required] public string ChapterId { get; init; }
+ [JsonIgnore] public Chapter Chapter { get; init; } = null!;
+
+ public UpdateSingleChapterDownloadedJob(Chapter chapter, Job? parentJob = null, ICollection? dependsOnJobs = null)
+ : base(TokenGen.CreateToken(typeof(UpdateSingleChapterDownloadedJob)), JobType.UpdateSingleChapterDownloadedJob, 0, parentJob, dependsOnJobs)
+ {
+ this.ChapterId = chapter.ChapterId;
+ this.Chapter = chapter;
+ }
+
+ ///
+ /// EF ONLY!!!
+ ///
+ internal UpdateSingleChapterDownloadedJob(string chapterId, string? parentJobId)
+ : base(TokenGen.CreateToken(typeof(UpdateSingleChapterDownloadedJob)), JobType.UpdateSingleChapterDownloadedJob, 0, parentJobId)
+ {
+ this.ChapterId = chapterId;
+ }
+
+ protected override IEnumerable RunInternal(PgsqlContext context)
+ {
+ context.Attach(Chapter);
+ Chapter.Downloaded = Chapter.CheckDownloaded();
+ context.SaveChanges();
+
+ try
+ {
+ context.SaveChanges();
+ }
+ catch (DbUpdateException e)
+ {
+ Log.Error(e);
+ }
+ return [];
+ }
+}
\ No newline at end of file
diff --git a/API/TokenGen.cs b/API/TokenGen.cs
index 75e17c4..1745cb3 100644
--- a/API/TokenGen.cs
+++ b/API/TokenGen.cs
@@ -5,7 +5,7 @@ namespace API;
public static class TokenGen
{
- private const int MinimumLength = 32;
+ private const int MinimumLength = 16;
private const int MaximumLength = 64;
private const string Chars = "abcdefghijklmnopqrstuvwxyz0123456789";