diff --git a/API/API.csproj b/API/API.csproj index 21839c5..70261c4 100644 --- a/API/API.csproj +++ b/API/API.csproj @@ -27,6 +27,7 @@ + diff --git a/API/Controllers/JobController.cs b/API/Controllers/JobController.cs index 87728c7..1ced7e9 100644 --- a/API/Controllers/JobController.cs +++ b/API/Controllers/JobController.cs @@ -83,19 +83,24 @@ public class JobController(PgsqlContext context) : Controller } /// - /// Create a new CreateNewDownloadChapterJob + /// Create a new DownloadAvailableChaptersJob /// /// ID of Manga /// How often should we check for new chapters /// Created new Job + /// Could not find Manga with ID /// Error during Database Operation - [HttpPut("NewDownloadChapterJob/{MangaId}")] - [ProducesResponseType(Status201Created)] + [HttpPut("DownloadAvailableChaptersJob/{MangaId}")] + [ProducesResponseType(Status201Created, "application/json")] + [ProducesResponseType(Status404NotFound)] [ProducesResponseType(Status500InternalServerError, "text/plain")] public IActionResult CreateNewDownloadChapterJob(string MangaId, [FromBody]ulong recurrenceTime) { - Job job = new DownloadNewChaptersJob(recurrenceTime, MangaId); - return AddJob(job); + if (context.Manga.Find(MangaId) is null) + return NotFound(); + Job dep = new RetrieveChaptersJob(recurrenceTime, MangaId); + Job job = new DownloadAvailableChaptersJob(recurrenceTime, MangaId, null, [dep.JobId]); + return AddJobs([dep, job]); } /// @@ -103,29 +108,37 @@ public class JobController(PgsqlContext context) : Controller /// /// ID of the Chapter /// Created new Job + /// Could not find Chapter with ID /// Error during Database Operation [HttpPut("DownloadSingleChapterJob/{ChapterId}")] - [ProducesResponseType(Status201Created)] + [ProducesResponseType(Status201Created, "application/json")] + [ProducesResponseType(Status404NotFound)] [ProducesResponseType(Status500InternalServerError, "text/plain")] public IActionResult CreateNewDownloadChapterJob(string ChapterId) { + if(context.Chapters.Find(ChapterId) is null) + return NotFound(); Job job = new DownloadSingleChapterJob(ChapterId); - return AddJob(job); + return AddJobs([job]); } /// - /// Create a new UpdateMetadataJob + /// Create a new UpdateFilesDownloadedJob /// /// ID of the Manga /// Created new Job + /// Could not find Manga with ID /// Error during Database Operation - [HttpPut("UpdateMetadataJob/{MangaId}")] - [ProducesResponseType(Status201Created)] + [HttpPut("UpdateFilesJob/{MangaId}")] + [ProducesResponseType(Status201Created, "application/json")] + [ProducesResponseType(Status404NotFound)] [ProducesResponseType(Status500InternalServerError, "text/plain")] - public IActionResult CreateUpdateMetadataJob(string MangaId) + public IActionResult CreateUpdateFilesDownloadedJob(string MangaId) { - Job job = new UpdateMetadataJob(0, MangaId); - return AddJob(job); + if(context.Manga.Find(MangaId) is null) + return NotFound(); + Job job = new UpdateFilesDownloadedJob(0, MangaId); + return AddJobs([job]); } /// @@ -133,7 +146,50 @@ public class JobController(PgsqlContext context) : Controller /// /// Created new Job /// Error during Database Operation - [HttpPut("UpdateMetadataJob")] + [HttpPut("UpdateAllFilesJob")] + [ProducesResponseType(Status201Created)] + [ProducesResponseType(Status500InternalServerError, "text/plain")] + public IActionResult CreateUpdateAllFilesDownloadedJob() + { + List ids = context.Manga.Select(m => m.MangaId).ToList(); + List jobs = ids.Select(id => new UpdateFilesDownloadedJob(0, id)).ToList(); + try + { + context.Jobs.AddRange(jobs); + context.SaveChanges(); + return Created(); + } + catch (Exception e) + { + return StatusCode(500, e.Message); + } + } + + /// + /// Create a new UpdateMetadataJob + /// + /// ID of the Manga + /// Created new Job + /// Could not find Manga with ID + /// Error during Database Operation + [HttpPut("UpdateMetadataJob/{MangaId}")] + [ProducesResponseType(Status201Created, "application/json")] + [ProducesResponseType(Status404NotFound)] + [ProducesResponseType(Status500InternalServerError, "text/plain")] + public IActionResult CreateUpdateMetadataJob(string MangaId) + { + if(context.Manga.Find(MangaId) is null) + return NotFound(); + Job job = new UpdateMetadataJob(0, MangaId); + return AddJobs([job]); + } + + /// + /// Create a new UpdateMetadataJob for all Manga + /// + /// Created new Job + /// Error during Database Operation + [HttpPut("UpdateAllMetadataJob")] [ProducesResponseType(Status201Created)] [ProducesResponseType(Status500InternalServerError, "text/plain")] public IActionResult CreateUpdateAllMetadataJob() @@ -152,13 +208,13 @@ public class JobController(PgsqlContext context) : Controller } } - private IActionResult AddJob(Job job) + private IActionResult AddJobs(Job[] jobs) { try { - context.Jobs.Add(job); + context.Jobs.AddRange(jobs); context.SaveChanges(); - return new CreatedResult(job.JobId, job); + return new CreatedResult((string?)null, jobs.Select(j => j.JobId).ToArray()); } catch (Exception e) { diff --git a/API/Controllers/SearchController.cs b/API/Controllers/SearchController.cs index d841bf6..25d6f12 100644 --- a/API/Controllers/SearchController.cs +++ b/API/Controllers/SearchController.cs @@ -142,8 +142,8 @@ public class SearchController(PgsqlContext context) : Controller MangaTag? inDb = context.Tags.FirstOrDefault(t => t.Equals(mt)); return inDb ?? mt; }); - manga.Tags = mergedTags.ToList(); - IEnumerable newTags = manga.Tags.Where(mt => !context.Tags.Any(t => t.Tag.Equals(mt.Tag))); + manga.MangaTags = mergedTags.ToList(); + IEnumerable newTags = manga.MangaTags.Where(mt => !context.Tags.Any(t => t.Tag.Equals(mt.Tag))); context.Tags.AddRange(newTags); } @@ -195,6 +195,7 @@ public class SearchController(PgsqlContext context) : Controller context.Manga.Add(manga); context.Jobs.Add(new DownloadMangaCoverJob(manga.MangaId)); + context.Jobs.Add(new RetrieveChaptersJob(0, manga.MangaId)); context.SaveChanges(); return existing ?? manga; diff --git a/API/Migrations/PgsqlContextModelSnapshot.cs b/API/Migrations/PgsqlContextModelSnapshot.cs index 4e31d2a..1a6984a 100644 --- a/API/Migrations/PgsqlContextModelSnapshot.cs +++ b/API/Migrations/PgsqlContextModelSnapshot.cs @@ -367,17 +367,17 @@ namespace API.Migrations b.Property("MangaId") .HasColumnType("character varying(64)"); - b.Property("TagsTag") + b.Property("MangaTagsTag") .HasColumnType("text"); - b.HasKey("MangaId", "TagsTag"); + b.HasKey("MangaId", "MangaTagsTag"); - b.HasIndex("TagsTag"); + b.HasIndex("MangaTagsTag"); b.ToTable("MangaMangaTag"); }); - modelBuilder.Entity("API.Schema.Jobs.DownloadMangaCoverJob", b => + modelBuilder.Entity("API.Schema.Jobs.DownloadAvailableChaptersJob", b => { b.HasBaseType("API.Schema.Jobs.Job"); @@ -391,13 +391,13 @@ namespace API.Migrations b.ToTable("Jobs", t => { t.Property("MangaId") - .HasColumnName("DownloadMangaCoverJob_MangaId"); + .HasColumnName("DownloadAvailableChaptersJob_MangaId"); }); - b.HasDiscriminator().HasValue((byte)4); + b.HasDiscriminator().HasValue((byte)1); }); - modelBuilder.Entity("API.Schema.Jobs.DownloadNewChaptersJob", b => + modelBuilder.Entity("API.Schema.Jobs.DownloadMangaCoverJob", b => { b.HasBaseType("API.Schema.Jobs.Job"); @@ -408,7 +408,7 @@ namespace API.Migrations b.HasIndex("MangaId"); - b.HasDiscriminator().HasValue((byte)1); + b.HasDiscriminator().HasValue((byte)4); }); modelBuilder.Entity("API.Schema.Jobs.DownloadSingleChapterJob", b => @@ -440,6 +440,46 @@ namespace API.Migrations b.HasDiscriminator().HasValue((byte)3); }); + modelBuilder.Entity("API.Schema.Jobs.RetrieveChaptersJob", 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("RetrieveChaptersJob_MangaId"); + }); + + b.HasDiscriminator().HasValue((byte)5); + }); + + modelBuilder.Entity("API.Schema.Jobs.UpdateFilesDownloadedJob", 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("UpdateFilesDownloadedJob_MangaId"); + }); + + b.HasDiscriminator().HasValue((byte)6); + }); + modelBuilder.Entity("API.Schema.Jobs.UpdateMetadataJob", b => { b.HasBaseType("API.Schema.Jobs.Job"); @@ -604,12 +644,12 @@ namespace API.Migrations b.HasOne("API.Schema.MangaTag", null) .WithMany() - .HasForeignKey("TagsTag") + .HasForeignKey("MangaTagsTag") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); }); - modelBuilder.Entity("API.Schema.Jobs.DownloadMangaCoverJob", b => + modelBuilder.Entity("API.Schema.Jobs.DownloadAvailableChaptersJob", b => { b.HasOne("API.Schema.Manga", "Manga") .WithMany() @@ -620,7 +660,7 @@ namespace API.Migrations b.Navigation("Manga"); }); - modelBuilder.Entity("API.Schema.Jobs.DownloadNewChaptersJob", b => + modelBuilder.Entity("API.Schema.Jobs.DownloadMangaCoverJob", b => { b.HasOne("API.Schema.Manga", "Manga") .WithMany() @@ -642,6 +682,28 @@ namespace API.Migrations b.Navigation("Chapter"); }); + 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.UpdateFilesDownloadedJob", b => + { + b.HasOne("API.Schema.Manga", "Manga") + .WithMany() + .HasForeignKey("MangaId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Manga"); + }); + modelBuilder.Entity("API.Schema.Jobs.UpdateMetadataJob", b => { b.HasOne("API.Schema.Manga", "Manga") diff --git a/API/Program.cs b/API/Program.cs index cb60ea7..7d1991b 100644 --- a/API/Program.cs +++ b/API/Program.cs @@ -8,6 +8,7 @@ using Asp.Versioning; using Asp.Versioning.Builder; using Asp.Versioning.Conventions; using Microsoft.EntityFrameworkCore; +using Newtonsoft.Json; using Newtonsoft.Json.Converters; var builder = WebApplication.CreateBuilder(args); @@ -25,25 +26,27 @@ builder.Services.AddCors(options => }); builder.Services.AddApiVersioning(option => -{ - option.AssumeDefaultVersionWhenUnspecified = true; - option.DefaultApiVersion = new ApiVersion(2); - option.ReportApiVersions = true; - option.ApiVersionReader = ApiVersionReader.Combine( - new UrlSegmentApiVersionReader(), - new QueryStringApiVersionReader("api-version"), - new HeaderApiVersionReader("X-Version"), - new MediaTypeApiVersionReader("x-version")); -}) -.AddMvc(options => -{ - options.Conventions.Add(new VersionByNamespaceConvention()); -}) - .AddApiExplorer(options => { - options.GroupNameFormat = "'v'V"; - options.SubstituteApiVersionInUrl = true; -}); + { + option.AssumeDefaultVersionWhenUnspecified = true; + option.DefaultApiVersion = new ApiVersion(2); + option.ReportApiVersions = true; + option.ApiVersionReader = ApiVersionReader.Combine( + new UrlSegmentApiVersionReader(), + new QueryStringApiVersionReader("api-version"), + new HeaderApiVersionReader("X-Version"), + new MediaTypeApiVersionReader("x-version")); + }) + .AddMvc(options => + { + options.Conventions.Add(new VersionByNamespaceConvention()); + }) + .AddApiExplorer(options => + { + options.GroupNameFormat = "'v'V"; + options.SubstituteApiVersionInUrl = true; + }); builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGenNewtonsoftSupport(); builder.Services.AddSwaggerGen(opt => { var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; @@ -58,12 +61,13 @@ builder.Services.AddDbContext(options => $"Password={Environment.GetEnvironmentVariable("POSTGRES_PASSWORD")??"postgres"}")); builder.Services.AddControllers(options => - { - options.AllowEmptyInputInBodyModelBinding = true; - }) - .AddNewtonsoftJson(opts => +{ + options.AllowEmptyInputInBodyModelBinding = true; +}); +builder.Services.AddControllers().AddNewtonsoftJson(opts => { opts.SerializerSettings.Converters.Add(new StringEnumConverter()); + opts.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; }); builder.WebHost.UseUrls("http://*:6531"); @@ -115,9 +119,7 @@ using (var scope = app.Services.CreateScope()) MangaConnector[] newConnectors = connectors.Where(c => !context.MangaConnectors.Contains(c)).ToArray(); context.MangaConnectors.AddRange(newConnectors); - IQueryable updateMetadataJobMangaIds = context.Jobs.Where(j => j.JobType == JobType.UpdateMetaDataJob).Select(j => ((UpdateMetadataJob)j).MangaId); - Job[] newUpdateMetadataJobs = context.Manga.Where(m => !updateMetadataJobMangaIds.Contains(m.MangaId)).ToList().Select(m => new UpdateMetadataJob(0, m.MangaId)).ToArray(); - context.Jobs.AddRange(newUpdateMetadataJobs); + context.Jobs.AddRange(context.Manga.AsEnumerable().Select(m => new UpdateFilesDownloadedJob(0, m.MangaId))); context.Jobs.RemoveRange(context.Jobs.Where(j => j.state == JobState.Completed && j.RecurrenceMs < 1)); diff --git a/API/Schema/Chapter.cs b/API/Schema/Chapter.cs index 575df24..6911983 100644 --- a/API/Schema/Chapter.cs +++ b/API/Schema/Chapter.cs @@ -39,8 +39,7 @@ public class Chapter : IComparable public bool Downloaded { get; internal set; } = false; public string ParentMangaId { get; internal set; } - [JsonIgnore] - public Manga? ParentManga { get; init; } + [JsonIgnore] public Manga? ParentManga { get; init; } public int CompareTo(Chapter? other) { @@ -130,7 +129,7 @@ public class Chapter : IComparable internal string GetComicInfoXmlString() { XElement comicInfo = new("ComicInfo", - new XElement("Tags", string.Join(',', ParentManga.Tags.Select(tag => tag.Tag))), + new XElement("Tags", string.Join(',', ParentManga.MangaTags.Select(tag => tag.Tag))), new XElement("LanguageISO", ParentManga.OriginalLanguage), new XElement("Title", Title), new XElement("Writer", string.Join(',', ParentManga.Authors.Select(author => author.AuthorName))), diff --git a/API/Schema/Jobs/DownloadAvailableChaptersJob.cs b/API/Schema/Jobs/DownloadAvailableChaptersJob.cs new file mode 100644 index 0000000..16de9fe --- /dev/null +++ b/API/Schema/Jobs/DownloadAvailableChaptersJob.cs @@ -0,0 +1,21 @@ +using System.ComponentModel.DataAnnotations; +using API.Schema.MangaConnectors; +using Newtonsoft.Json; + +namespace API.Schema.Jobs; + +public class DownloadAvailableChaptersJob(ulong recurrenceMs, string mangaId, string? parentJobId = null, ICollection? dependsOnJobsIds = null) + : Job(TokenGen.CreateToken(typeof(DownloadAvailableChaptersJob)), JobType.DownloadAvailableChaptersJob, recurrenceMs, parentJobId, dependsOnJobsIds) +{ + [MaxLength(64)] + public string MangaId { get; init; } = mangaId; + + [JsonIgnore] + public Manga? Manga { get; init; } + + protected override IEnumerable RunInternal(PgsqlContext context) + { + return context.Chapters.Where(c => c.ParentMangaId == MangaId).AsEnumerable() + .Select(chapter => new DownloadSingleChapterJob(chapter.ChapterId, this.JobId)); + } +} \ No newline at end of file diff --git a/API/Schema/Jobs/JobType.cs b/API/Schema/Jobs/JobType.cs index bf206f3..5f2e870 100644 --- a/API/Schema/Jobs/JobType.cs +++ b/API/Schema/Jobs/JobType.cs @@ -4,8 +4,10 @@ public enum JobType : byte { DownloadSingleChapterJob = 0, - DownloadNewChaptersJob = 1, + DownloadAvailableChaptersJob = 1, UpdateMetaDataJob = 2, MoveFileOrFolderJob = 3, - DownloadMangaCoverJob = 4 + DownloadMangaCoverJob = 4, + RetrieveChaptersJob = 5, + UpdateFilesDownloadedJob = 6 } \ No newline at end of file diff --git a/API/Schema/Jobs/DownloadNewChaptersJob.cs b/API/Schema/Jobs/RetrieveChaptersJob.cs similarity index 74% rename from API/Schema/Jobs/DownloadNewChaptersJob.cs rename to API/Schema/Jobs/RetrieveChaptersJob.cs index cade8e2..441fd88 100644 --- a/API/Schema/Jobs/DownloadNewChaptersJob.cs +++ b/API/Schema/Jobs/RetrieveChaptersJob.cs @@ -1,11 +1,11 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using API.Schema.MangaConnectors; using Newtonsoft.Json; namespace API.Schema.Jobs; -public class DownloadNewChaptersJob(ulong recurrenceMs, string mangaId, string? parentJobId = null, ICollection? dependsOnJobsIds = null) - : Job(TokenGen.CreateToken(typeof(DownloadNewChaptersJob)), JobType.DownloadNewChaptersJob, recurrenceMs, parentJobId, dependsOnJobsIds) +public class RetrieveChaptersJob(ulong recurrenceMs, string mangaId, string? parentJobId = null, ICollection? dependsOnJobsIds = null) + : Job(TokenGen.CreateToken(typeof(RetrieveChaptersJob)), JobType.RetrieveChaptersJob, recurrenceMs, parentJobId, dependsOnJobsIds) { [MaxLength(64)] public string MangaId { get; init; } = mangaId; @@ -30,7 +30,7 @@ public class DownloadNewChaptersJob(ulong recurrenceMs, string mangaId, string? Chapter[] newChapters = allNewChapters.Where(chapter => !chapterIds.Contains(chapter.ChapterId)).ToArray(); context.Chapters.AddRangeAsync(newChapters).Wait(); context.SaveChangesAsync().Wait(); - - return allNewChapters.Select(chapter => new DownloadSingleChapterJob(chapter.ChapterId, this.JobId)); + + return []; } } \ No newline at end of file diff --git a/API/Schema/Jobs/UpdateFilesDownloadedJob.cs b/API/Schema/Jobs/UpdateFilesDownloadedJob.cs new file mode 100644 index 0000000..e02fdec --- /dev/null +++ b/API/Schema/Jobs/UpdateFilesDownloadedJob.cs @@ -0,0 +1,24 @@ +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; + +namespace API.Schema.Jobs; + +public class UpdateFilesDownloadedJob(ulong recurrenceMs, string mangaId, string? parentJobId = null, ICollection? dependsOnJobsIds = null) + : Job(TokenGen.CreateToken(typeof(UpdateFilesDownloadedJob)), JobType.UpdateFilesDownloadedJob, recurrenceMs, parentJobId, dependsOnJobsIds) +{ + [MaxLength(64)] + public string MangaId { get; init; } = mangaId; + + [JsonIgnore] + public virtual Manga? Manga { get; init; } + + protected override IEnumerable RunInternal(PgsqlContext context) + { + IQueryable chapters = context.Chapters.Where(c => c.ParentMangaId == MangaId); + foreach (Chapter chapter in chapters) + chapter.Downloaded = chapter.IsDownloaded(); + + context.SaveChanges(); + return []; + } +} \ No newline at end of file diff --git a/API/Schema/Jobs/UpdateMetadataJob.cs b/API/Schema/Jobs/UpdateMetadataJob.cs index a4bca95..fe27a6f 100644 --- a/API/Schema/Jobs/UpdateMetadataJob.cs +++ b/API/Schema/Jobs/UpdateMetadataJob.cs @@ -21,14 +21,6 @@ public class UpdateMetadataJob(ulong recurrenceMs, string mangaId, string? paren /// protected override IEnumerable RunInternal(PgsqlContext context) { - //Manga manga = Manga ?? context.Manga.Find(MangaId)!; - IQueryable chapters = context.Chapters.Where(c => c.ParentMangaId == MangaId); - foreach (Chapter chapter in chapters) - chapter.Downloaded = chapter.IsDownloaded(); - - context.SaveChanges(); - return []; - - //TODO implement Metadata-Update from MangaConnector + throw new NotImplementedException(); } } \ No newline at end of file diff --git a/API/Schema/Manga.cs b/API/Schema/Manga.cs index f87ec30..f2e7f1d 100644 --- a/API/Schema/Manga.cs +++ b/API/Schema/Manga.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; using System.Net; using System.Runtime.InteropServices; using System.Text.RegularExpressions; @@ -6,6 +7,7 @@ using API.MangaDownloadClients; using API.Schema.Jobs; using API.Schema.MangaConnectors; using Microsoft.EntityFrameworkCore; +using Newtonsoft.Json; using static System.IO.UnixFileMode; namespace API.Schema; @@ -30,26 +32,30 @@ public class Manga public float IgnoreChapterBefore { get; internal set; } public string MangaConnectorId { get; private set; } + [JsonIgnore] public MangaConnector? MangaConnector { get; private set; } - public MangaConnector? MangaConnector { get; private set; } + [JsonIgnore] public ICollection? Authors { get; internal set; } + [NotMapped] public IEnumerable AuthorIds => Authors?.Select(a => a.AuthorId) ?? []; - public ICollection? Authors { get; internal set; } + [JsonIgnore] public ICollection? MangaTags { get; internal set; } + [NotMapped] public IEnumerable Tags => MangaTags.Select(t => t.Tag); - public ICollection? Tags { get; internal set; } - public ICollection? Links { get; internal set; } + [JsonIgnore] public ICollection? Links { get; internal set; } + [NotMapped] public IEnumerable LinkIds => Links?.Select(l => l.LinkId) ?? []; - public ICollection? AltTitles { get; internal set; } + [JsonIgnore] public ICollection? AltTitles { get; internal set; } + [NotMapped] public IEnumerable AltTitleIds => AltTitles?.Select(a => a.AltTitleId) ?? []; public Manga(string connectorId, string name, string description, string websiteUrl, string coverUrl, string? coverFileNameInCache, uint year, string? originalLanguage, MangaReleaseStatus releaseStatus, float ignoreChapterBefore, MangaConnector mangaConnector, ICollection authors, - ICollection tags, ICollection links, ICollection altTitles) + ICollection mangaTags, ICollection links, ICollection altTitles) : this(connectorId, name, description, websiteUrl, coverUrl, coverFileNameInCache, year, originalLanguage, releaseStatus, ignoreChapterBefore, mangaConnector.Name) { this.Authors = authors; - this.Tags = tags; + this.MangaTags = mangaTags; this.Links = links; this.AltTitles = altTitles; } @@ -89,7 +95,7 @@ public class Manga this.OriginalLanguage = other.OriginalLanguage; this.Authors = other.Authors; this.Links = other.Links; - this.Tags = other.Tags; + this.MangaTags = other.MangaTags; this.AltTitles = other.AltTitles; this.ReleaseStatus = other.ReleaseStatus; } diff --git a/API/Schema/PgsqlContext.cs b/API/Schema/PgsqlContext.cs index dffbbcc..a10df6d 100644 --- a/API/Schema/PgsqlContext.cs +++ b/API/Schema/PgsqlContext.cs @@ -36,21 +36,23 @@ public class PgsqlContext(DbContextOptions options) : DbContext(op .HasDiscriminator(l => l.LibraryType) .HasValue(LibraryType.Komga) .HasValue(LibraryType.Kavita); - + modelBuilder.Entity() .HasDiscriminator(j => j.JobType) .HasValue(JobType.MoveFileOrFolderJob) - .HasValue(JobType.DownloadNewChaptersJob) + .HasValue(JobType.DownloadAvailableChaptersJob) .HasValue(JobType.DownloadSingleChapterJob) .HasValue(JobType.DownloadMangaCoverJob) - .HasValue(JobType.UpdateMetaDataJob); + .HasValue(JobType.UpdateMetaDataJob) + .HasValue(JobType.RetrieveChaptersJob) + .HasValue(JobType.UpdateFilesDownloadedJob); modelBuilder.Entity() .HasOne(j => j.ParentJob) .WithMany() .HasForeignKey(j => j.ParentJobId); modelBuilder.Entity() .HasMany(j => j.DependsOnJobs); - modelBuilder.Entity() + modelBuilder.Entity() .Navigation(dncj => dncj.Manga) .AutoInclude(); modelBuilder.Entity() @@ -74,10 +76,10 @@ public class PgsqlContext(DbContextOptions options) : DbContext(op .Navigation(m => m.Authors) .AutoInclude(); modelBuilder.Entity() - .HasMany(m => m.Tags) + .HasMany(m => m.MangaTags) .WithMany(); modelBuilder.Entity() - .Navigation(m => m.Tags) + .Navigation(m => m.MangaTags) .AutoInclude(); modelBuilder.Entity() .HasMany(m => m.Links) diff --git a/API/Tranga.cs b/API/Tranga.cs index aa961b4..3f30222 100644 --- a/API/Tranga.cs +++ b/API/Tranga.cs @@ -91,10 +91,10 @@ public static class Tranga // If the job is already running, skip it if (RunningJobs.Values.Any(j => j.JobId == job.JobId)) continue; - if (job is DownloadNewChaptersJob dncj) + if (job is DownloadAvailableChaptersJob dncj) { if (RunningJobs.Values.Any(j => - j is DownloadNewChaptersJob rdncj && + j is DownloadAvailableChaptersJob rdncj && rdncj.Manga?.MangaConnector == dncj.Manga?.MangaConnector)) { continue; @@ -143,13 +143,15 @@ public static class Tranga IEnumerable ret = new List(); if(jobsByType.ContainsKey(JobType.MoveFileOrFolderJob)) ret = ret.Concat(jobsByType[JobType.MoveFileOrFolderJob]); - if(jobsByType.ContainsKey(JobType.DownloadMangaCoverJob)) - ret = ret.Concat(jobsByType[JobType.DownloadMangaCoverJob]); + if(jobsByType.ContainsKey(JobType.DownloadMangaCoverJob)) + ret = ret.Concat(jobsByType[JobType.DownloadMangaCoverJob]); + if(jobsByType.ContainsKey(JobType.UpdateFilesDownloadedJob)) + ret = ret.Concat(jobsByType[JobType.UpdateFilesDownloadedJob]); Dictionary> metadataJobsByConnector = new(); - if (jobsByType.ContainsKey(JobType.DownloadNewChaptersJob)) + if (jobsByType.ContainsKey(JobType.DownloadAvailableChaptersJob)) { - foreach (DownloadNewChaptersJob job in jobsByType[JobType.DownloadNewChaptersJob]) + foreach (DownloadAvailableChaptersJob job in jobsByType[JobType.DownloadAvailableChaptersJob]) { Manga manga = job.Manga ?? context.Manga.Find(job.MangaId)!; MangaConnector connector = manga.MangaConnector ?? context.MangaConnectors.Find(manga.MangaConnectorId)!; @@ -167,6 +169,16 @@ public static class Tranga metadataJobsByConnector[connector].Add(job); } } + if (jobsByType.ContainsKey(JobType.RetrieveChaptersJob)) + { + foreach (RetrieveChaptersJob job in jobsByType[JobType.RetrieveChaptersJob]) + { + Manga manga = job.Manga ?? context.Manga.Find(job.MangaId)!; + MangaConnector connector = manga.MangaConnector ?? context.MangaConnectors.Find(manga.MangaConnectorId)!; + if(!metadataJobsByConnector.TryAdd(connector, [job])) + metadataJobsByConnector[connector].Add(job); + } + } foreach (List metadataJobs in metadataJobsByConnector.Values) ret = ret.Append(metadataJobs.MinBy(j => j.NextExecution))!;