From 563afa1e6fd2d4480efa786eed409bfeb4c0db9e Mon Sep 17 00:00:00 2001 From: Glax Date: Fri, 16 May 2025 20:12:08 +0200 Subject: [PATCH] Split UpdateFilesDownloadedJob.cs to UpdateChaptersDownloadedJob.cs and split into UpdateSingleChapterDownloadedJob.cs --- API/Controllers/JobController.cs | 6 +- API/Controllers/MangaController.cs | 2 +- .../20250515120724_Initial-1.Designer.cs | 4 +- .../20250516121442_AltTitle-Owned.Designer.cs | 4 +- ...0516121725_Manga-Year-Nullable.Designer.cs | 4 +- ...16122242_AltTitle-Owned-WithId.Designer.cs | 4 +- ...dateSingleChapterDownloadedJob.Designer.cs | 720 ++++++++++++++++++ ...b-Into-UpdateSingleChapterDownloadedJob.cs | 94 +++ .../pgsql/PgsqlContextModelSnapshot.cs | 37 +- API/Program.cs | 2 +- API/Schema/Contexts/PgsqlContext.cs | 7 +- API/Schema/Jobs/DownloadSingleChapterJob.cs | 6 +- API/Schema/Jobs/JobType.cs | 5 +- .../Jobs/UpdateChaptersDownloadedJob.cs | 34 + API/Schema/Jobs/UpdateFilesDownloadedJob.cs | 46 -- .../Jobs/UpdateSingleChapterDownloadedJob.cs | 45 ++ API/TokenGen.cs | 2 +- 17 files changed, 951 insertions(+), 71 deletions(-) create mode 100644 API/Migrations/pgsql/20250516180953_Split-UpdateChaptersDownloadedJob-Into-UpdateSingleChapterDownloadedJob.Designer.cs create mode 100644 API/Migrations/pgsql/20250516180953_Split-UpdateChaptersDownloadedJob-Into-UpdateSingleChapterDownloadedJob.cs create mode 100644 API/Schema/Jobs/UpdateChaptersDownloadedJob.cs delete mode 100644 API/Schema/Jobs/UpdateFilesDownloadedJob.cs create mode 100644 API/Schema/Jobs/UpdateSingleChapterDownloadedJob.cs 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";