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))!;