mirror of
https://github.com/C9Glax/tranga.git
synced 2025-04-15 12:53:17 +02:00
Add NewtonsoftJson to Swagger
Add RetrieveChaptersJob.cs Add UpdateFilesDownloadedJob.cs Remove DownloadNewChaptersJob.cs and instead use DownloadAvailableChaptersJob.cs
This commit is contained in:
parent
ecfc8f349b
commit
ffc0e7555a
@ -27,6 +27,7 @@
|
|||||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.7" />
|
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.7" />
|
||||||
<PackageReference Include="Soenneker.Utils.String.NeedlemanWunsch" Version="3.0.920" />
|
<PackageReference Include="Soenneker.Utils.String.NeedlemanWunsch" Version="3.0.920" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="7.3.1" />
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="7.3.1" />
|
||||||
|
<PackageReference Include="Swashbuckle.AspNetCore.Newtonsoft" Version="7.3.1" />
|
||||||
<PackageReference Include="System.Drawing.Common" Version="9.0.2" />
|
<PackageReference Include="System.Drawing.Common" Version="9.0.2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
@ -83,19 +83,24 @@ public class JobController(PgsqlContext context) : Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a new CreateNewDownloadChapterJob
|
/// Create a new DownloadAvailableChaptersJob
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="MangaId">ID of Manga</param>
|
/// <param name="MangaId">ID of Manga</param>
|
||||||
/// <param name="recurrenceTime">How often should we check for new chapters</param>
|
/// <param name="recurrenceTime">How often should we check for new chapters</param>
|
||||||
/// <response code="201">Created new Job</response>
|
/// <response code="201">Created new Job</response>
|
||||||
|
/// <response code="404">Could not find Manga with ID</response>
|
||||||
/// <response code="500">Error during Database Operation</response>
|
/// <response code="500">Error during Database Operation</response>
|
||||||
[HttpPut("NewDownloadChapterJob/{MangaId}")]
|
[HttpPut("DownloadAvailableChaptersJob/{MangaId}")]
|
||||||
[ProducesResponseType(Status201Created)]
|
[ProducesResponseType<string[]>(Status201Created, "application/json")]
|
||||||
|
[ProducesResponseType(Status404NotFound)]
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||||
public IActionResult CreateNewDownloadChapterJob(string MangaId, [FromBody]ulong recurrenceTime)
|
public IActionResult CreateNewDownloadChapterJob(string MangaId, [FromBody]ulong recurrenceTime)
|
||||||
{
|
{
|
||||||
Job job = new DownloadNewChaptersJob(recurrenceTime, MangaId);
|
if (context.Manga.Find(MangaId) is null)
|
||||||
return AddJob(job);
|
return NotFound();
|
||||||
|
Job dep = new RetrieveChaptersJob(recurrenceTime, MangaId);
|
||||||
|
Job job = new DownloadAvailableChaptersJob(recurrenceTime, MangaId, null, [dep.JobId]);
|
||||||
|
return AddJobs([dep, job]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -103,29 +108,37 @@ public class JobController(PgsqlContext context) : Controller
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="ChapterId">ID of the Chapter</param>
|
/// <param name="ChapterId">ID of the Chapter</param>
|
||||||
/// <response code="201">Created new Job</response>
|
/// <response code="201">Created new Job</response>
|
||||||
|
/// <response code="404">Could not find Chapter with ID</response>
|
||||||
/// <response code="500">Error during Database Operation</response>
|
/// <response code="500">Error during Database Operation</response>
|
||||||
[HttpPut("DownloadSingleChapterJob/{ChapterId}")]
|
[HttpPut("DownloadSingleChapterJob/{ChapterId}")]
|
||||||
[ProducesResponseType(Status201Created)]
|
[ProducesResponseType<string[]>(Status201Created, "application/json")]
|
||||||
|
[ProducesResponseType(Status404NotFound)]
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||||
public IActionResult CreateNewDownloadChapterJob(string ChapterId)
|
public IActionResult CreateNewDownloadChapterJob(string ChapterId)
|
||||||
{
|
{
|
||||||
|
if(context.Chapters.Find(ChapterId) is null)
|
||||||
|
return NotFound();
|
||||||
Job job = new DownloadSingleChapterJob(ChapterId);
|
Job job = new DownloadSingleChapterJob(ChapterId);
|
||||||
return AddJob(job);
|
return AddJobs([job]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a new UpdateMetadataJob
|
/// Create a new UpdateFilesDownloadedJob
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="MangaId">ID of the Manga</param>
|
/// <param name="MangaId">ID of the Manga</param>
|
||||||
/// <response code="201">Created new Job</response>
|
/// <response code="201">Created new Job</response>
|
||||||
|
/// <response code="201">Could not find Manga with ID</response>
|
||||||
/// <response code="500">Error during Database Operation</response>
|
/// <response code="500">Error during Database Operation</response>
|
||||||
[HttpPut("UpdateMetadataJob/{MangaId}")]
|
[HttpPut("UpdateFilesJob/{MangaId}")]
|
||||||
[ProducesResponseType(Status201Created)]
|
[ProducesResponseType<string[]>(Status201Created, "application/json")]
|
||||||
|
[ProducesResponseType(Status404NotFound)]
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||||
public IActionResult CreateUpdateMetadataJob(string MangaId)
|
public IActionResult CreateUpdateFilesDownloadedJob(string MangaId)
|
||||||
{
|
{
|
||||||
Job job = new UpdateMetadataJob(0, MangaId);
|
if(context.Manga.Find(MangaId) is null)
|
||||||
return AddJob(job);
|
return NotFound();
|
||||||
|
Job job = new UpdateFilesDownloadedJob(0, MangaId);
|
||||||
|
return AddJobs([job]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -133,7 +146,50 @@ public class JobController(PgsqlContext context) : Controller
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <response code="201">Created new Job</response>
|
/// <response code="201">Created new Job</response>
|
||||||
/// <response code="500">Error during Database Operation</response>
|
/// <response code="500">Error during Database Operation</response>
|
||||||
[HttpPut("UpdateMetadataJob")]
|
[HttpPut("UpdateAllFilesJob")]
|
||||||
|
[ProducesResponseType(Status201Created)]
|
||||||
|
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||||
|
public IActionResult CreateUpdateAllFilesDownloadedJob()
|
||||||
|
{
|
||||||
|
List<string> ids = context.Manga.Select(m => m.MangaId).ToList();
|
||||||
|
List<UpdateFilesDownloadedJob> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new UpdateMetadataJob
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="MangaId">ID of the Manga</param>
|
||||||
|
/// <response code="201">Created new Job</response>
|
||||||
|
/// <response code="404">Could not find Manga with ID</response>
|
||||||
|
/// <response code="500">Error during Database Operation</response>
|
||||||
|
[HttpPut("UpdateMetadataJob/{MangaId}")]
|
||||||
|
[ProducesResponseType<string[]>(Status201Created, "application/json")]
|
||||||
|
[ProducesResponseType(Status404NotFound)]
|
||||||
|
[ProducesResponseType<string>(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]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new UpdateMetadataJob for all Manga
|
||||||
|
/// </summary>
|
||||||
|
/// <response code="201">Created new Job</response>
|
||||||
|
/// <response code="500">Error during Database Operation</response>
|
||||||
|
[HttpPut("UpdateAllMetadataJob")]
|
||||||
[ProducesResponseType(Status201Created)]
|
[ProducesResponseType(Status201Created)]
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||||
public IActionResult CreateUpdateAllMetadataJob()
|
public IActionResult CreateUpdateAllMetadataJob()
|
||||||
@ -152,13 +208,13 @@ public class JobController(PgsqlContext context) : Controller
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private IActionResult AddJob(Job job)
|
private IActionResult AddJobs(Job[] jobs)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
context.Jobs.Add(job);
|
context.Jobs.AddRange(jobs);
|
||||||
context.SaveChanges();
|
context.SaveChanges();
|
||||||
return new CreatedResult(job.JobId, job);
|
return new CreatedResult((string?)null, jobs.Select(j => j.JobId).ToArray());
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
@ -142,8 +142,8 @@ public class SearchController(PgsqlContext context) : Controller
|
|||||||
MangaTag? inDb = context.Tags.FirstOrDefault(t => t.Equals(mt));
|
MangaTag? inDb = context.Tags.FirstOrDefault(t => t.Equals(mt));
|
||||||
return inDb ?? mt;
|
return inDb ?? mt;
|
||||||
});
|
});
|
||||||
manga.Tags = mergedTags.ToList();
|
manga.MangaTags = mergedTags.ToList();
|
||||||
IEnumerable<MangaTag> newTags = manga.Tags.Where(mt => !context.Tags.Any(t => t.Tag.Equals(mt.Tag)));
|
IEnumerable<MangaTag> newTags = manga.MangaTags.Where(mt => !context.Tags.Any(t => t.Tag.Equals(mt.Tag)));
|
||||||
context.Tags.AddRange(newTags);
|
context.Tags.AddRange(newTags);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -195,6 +195,7 @@ public class SearchController(PgsqlContext context) : Controller
|
|||||||
context.Manga.Add(manga);
|
context.Manga.Add(manga);
|
||||||
|
|
||||||
context.Jobs.Add(new DownloadMangaCoverJob(manga.MangaId));
|
context.Jobs.Add(new DownloadMangaCoverJob(manga.MangaId));
|
||||||
|
context.Jobs.Add(new RetrieveChaptersJob(0, manga.MangaId));
|
||||||
|
|
||||||
context.SaveChanges();
|
context.SaveChanges();
|
||||||
return existing ?? manga;
|
return existing ?? manga;
|
||||||
|
@ -367,17 +367,17 @@ namespace API.Migrations
|
|||||||
b.Property<string>("MangaId")
|
b.Property<string>("MangaId")
|
||||||
.HasColumnType("character varying(64)");
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
b.Property<string>("TagsTag")
|
b.Property<string>("MangaTagsTag")
|
||||||
.HasColumnType("text");
|
.HasColumnType("text");
|
||||||
|
|
||||||
b.HasKey("MangaId", "TagsTag");
|
b.HasKey("MangaId", "MangaTagsTag");
|
||||||
|
|
||||||
b.HasIndex("TagsTag");
|
b.HasIndex("MangaTagsTag");
|
||||||
|
|
||||||
b.ToTable("MangaMangaTag");
|
b.ToTable("MangaMangaTag");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Jobs.DownloadMangaCoverJob", b =>
|
modelBuilder.Entity("API.Schema.Jobs.DownloadAvailableChaptersJob", b =>
|
||||||
{
|
{
|
||||||
b.HasBaseType("API.Schema.Jobs.Job");
|
b.HasBaseType("API.Schema.Jobs.Job");
|
||||||
|
|
||||||
@ -391,13 +391,13 @@ namespace API.Migrations
|
|||||||
b.ToTable("Jobs", t =>
|
b.ToTable("Jobs", t =>
|
||||||
{
|
{
|
||||||
t.Property("MangaId")
|
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");
|
b.HasBaseType("API.Schema.Jobs.Job");
|
||||||
|
|
||||||
@ -408,7 +408,7 @@ namespace API.Migrations
|
|||||||
|
|
||||||
b.HasIndex("MangaId");
|
b.HasIndex("MangaId");
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue((byte)1);
|
b.HasDiscriminator().HasValue((byte)4);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Jobs.DownloadSingleChapterJob", b =>
|
modelBuilder.Entity("API.Schema.Jobs.DownloadSingleChapterJob", b =>
|
||||||
@ -440,6 +440,46 @@ namespace API.Migrations
|
|||||||
b.HasDiscriminator().HasValue((byte)3);
|
b.HasDiscriminator().HasValue((byte)3);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("API.Schema.Jobs.RetrieveChaptersJob", b =>
|
||||||
|
{
|
||||||
|
b.HasBaseType("API.Schema.Jobs.Job");
|
||||||
|
|
||||||
|
b.Property<string>("MangaId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(64)
|
||||||
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
|
b.HasIndex("MangaId");
|
||||||
|
|
||||||
|
b.ToTable("Jobs", t =>
|
||||||
|
{
|
||||||
|
t.Property("MangaId")
|
||||||
|
.HasColumnName("RetrieveChaptersJob_MangaId");
|
||||||
|
});
|
||||||
|
|
||||||
|
b.HasDiscriminator().HasValue((byte)5);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("API.Schema.Jobs.UpdateFilesDownloadedJob", b =>
|
||||||
|
{
|
||||||
|
b.HasBaseType("API.Schema.Jobs.Job");
|
||||||
|
|
||||||
|
b.Property<string>("MangaId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(64)
|
||||||
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
|
b.HasIndex("MangaId");
|
||||||
|
|
||||||
|
b.ToTable("Jobs", t =>
|
||||||
|
{
|
||||||
|
t.Property("MangaId")
|
||||||
|
.HasColumnName("UpdateFilesDownloadedJob_MangaId");
|
||||||
|
});
|
||||||
|
|
||||||
|
b.HasDiscriminator().HasValue((byte)6);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Jobs.UpdateMetadataJob", b =>
|
modelBuilder.Entity("API.Schema.Jobs.UpdateMetadataJob", b =>
|
||||||
{
|
{
|
||||||
b.HasBaseType("API.Schema.Jobs.Job");
|
b.HasBaseType("API.Schema.Jobs.Job");
|
||||||
@ -604,12 +644,12 @@ namespace API.Migrations
|
|||||||
|
|
||||||
b.HasOne("API.Schema.MangaTag", null)
|
b.HasOne("API.Schema.MangaTag", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("TagsTag")
|
.HasForeignKey("MangaTagsTag")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Jobs.DownloadMangaCoverJob", b =>
|
modelBuilder.Entity("API.Schema.Jobs.DownloadAvailableChaptersJob", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("API.Schema.Manga", "Manga")
|
b.HasOne("API.Schema.Manga", "Manga")
|
||||||
.WithMany()
|
.WithMany()
|
||||||
@ -620,7 +660,7 @@ namespace API.Migrations
|
|||||||
b.Navigation("Manga");
|
b.Navigation("Manga");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Jobs.DownloadNewChaptersJob", b =>
|
modelBuilder.Entity("API.Schema.Jobs.DownloadMangaCoverJob", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("API.Schema.Manga", "Manga")
|
b.HasOne("API.Schema.Manga", "Manga")
|
||||||
.WithMany()
|
.WithMany()
|
||||||
@ -642,6 +682,28 @@ namespace API.Migrations
|
|||||||
b.Navigation("Chapter");
|
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 =>
|
modelBuilder.Entity("API.Schema.Jobs.UpdateMetadataJob", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("API.Schema.Manga", "Manga")
|
b.HasOne("API.Schema.Manga", "Manga")
|
||||||
|
@ -8,6 +8,7 @@ using Asp.Versioning;
|
|||||||
using Asp.Versioning.Builder;
|
using Asp.Versioning.Builder;
|
||||||
using Asp.Versioning.Conventions;
|
using Asp.Versioning.Conventions;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Converters;
|
using Newtonsoft.Json.Converters;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
@ -25,25 +26,27 @@ builder.Services.AddCors(options =>
|
|||||||
});
|
});
|
||||||
|
|
||||||
builder.Services.AddApiVersioning(option =>
|
builder.Services.AddApiVersioning(option =>
|
||||||
{
|
{
|
||||||
option.AssumeDefaultVersionWhenUnspecified = true;
|
option.AssumeDefaultVersionWhenUnspecified = true;
|
||||||
option.DefaultApiVersion = new ApiVersion(2);
|
option.DefaultApiVersion = new ApiVersion(2);
|
||||||
option.ReportApiVersions = true;
|
option.ReportApiVersions = true;
|
||||||
option.ApiVersionReader = ApiVersionReader.Combine(
|
option.ApiVersionReader = ApiVersionReader.Combine(
|
||||||
new UrlSegmentApiVersionReader(),
|
new UrlSegmentApiVersionReader(),
|
||||||
new QueryStringApiVersionReader("api-version"),
|
new QueryStringApiVersionReader("api-version"),
|
||||||
new HeaderApiVersionReader("X-Version"),
|
new HeaderApiVersionReader("X-Version"),
|
||||||
new MediaTypeApiVersionReader("x-version"));
|
new MediaTypeApiVersionReader("x-version"));
|
||||||
})
|
})
|
||||||
.AddMvc(options =>
|
.AddMvc(options =>
|
||||||
{
|
{
|
||||||
options.Conventions.Add(new VersionByNamespaceConvention());
|
options.Conventions.Add(new VersionByNamespaceConvention());
|
||||||
})
|
})
|
||||||
.AddApiExplorer(options => {
|
.AddApiExplorer(options =>
|
||||||
options.GroupNameFormat = "'v'V";
|
{
|
||||||
options.SubstituteApiVersionInUrl = true;
|
options.GroupNameFormat = "'v'V";
|
||||||
});
|
options.SubstituteApiVersionInUrl = true;
|
||||||
|
});
|
||||||
builder.Services.AddEndpointsApiExplorer();
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
|
builder.Services.AddSwaggerGenNewtonsoftSupport();
|
||||||
builder.Services.AddSwaggerGen(opt =>
|
builder.Services.AddSwaggerGen(opt =>
|
||||||
{
|
{
|
||||||
var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
|
var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
|
||||||
@ -58,12 +61,13 @@ builder.Services.AddDbContext<PgsqlContext>(options =>
|
|||||||
$"Password={Environment.GetEnvironmentVariable("POSTGRES_PASSWORD")??"postgres"}"));
|
$"Password={Environment.GetEnvironmentVariable("POSTGRES_PASSWORD")??"postgres"}"));
|
||||||
|
|
||||||
builder.Services.AddControllers(options =>
|
builder.Services.AddControllers(options =>
|
||||||
{
|
{
|
||||||
options.AllowEmptyInputInBodyModelBinding = true;
|
options.AllowEmptyInputInBodyModelBinding = true;
|
||||||
})
|
});
|
||||||
.AddNewtonsoftJson(opts =>
|
builder.Services.AddControllers().AddNewtonsoftJson(opts =>
|
||||||
{
|
{
|
||||||
opts.SerializerSettings.Converters.Add(new StringEnumConverter());
|
opts.SerializerSettings.Converters.Add(new StringEnumConverter());
|
||||||
|
opts.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.WebHost.UseUrls("http://*:6531");
|
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();
|
MangaConnector[] newConnectors = connectors.Where(c => !context.MangaConnectors.Contains(c)).ToArray();
|
||||||
context.MangaConnectors.AddRange(newConnectors);
|
context.MangaConnectors.AddRange(newConnectors);
|
||||||
|
|
||||||
IQueryable<string> updateMetadataJobMangaIds = context.Jobs.Where(j => j.JobType == JobType.UpdateMetaDataJob).Select(j => ((UpdateMetadataJob)j).MangaId);
|
context.Jobs.AddRange(context.Manga.AsEnumerable().Select(m => new UpdateFilesDownloadedJob(0, m.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.RemoveRange(context.Jobs.Where(j => j.state == JobState.Completed && j.RecurrenceMs < 1));
|
context.Jobs.RemoveRange(context.Jobs.Where(j => j.state == JobState.Completed && j.RecurrenceMs < 1));
|
||||||
|
|
||||||
|
@ -39,8 +39,7 @@ public class Chapter : IComparable<Chapter>
|
|||||||
public bool Downloaded { get; internal set; } = false;
|
public bool Downloaded { get; internal set; } = false;
|
||||||
|
|
||||||
public string ParentMangaId { get; internal set; }
|
public string ParentMangaId { get; internal set; }
|
||||||
[JsonIgnore]
|
[JsonIgnore] public Manga? ParentManga { get; init; }
|
||||||
public Manga? ParentManga { get; init; }
|
|
||||||
|
|
||||||
public int CompareTo(Chapter? other)
|
public int CompareTo(Chapter? other)
|
||||||
{
|
{
|
||||||
@ -130,7 +129,7 @@ public class Chapter : IComparable<Chapter>
|
|||||||
internal string GetComicInfoXmlString()
|
internal string GetComicInfoXmlString()
|
||||||
{
|
{
|
||||||
XElement comicInfo = new("ComicInfo",
|
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("LanguageISO", ParentManga.OriginalLanguage),
|
||||||
new XElement("Title", Title),
|
new XElement("Title", Title),
|
||||||
new XElement("Writer", string.Join(',', ParentManga.Authors.Select(author => author.AuthorName))),
|
new XElement("Writer", string.Join(',', ParentManga.Authors.Select(author => author.AuthorName))),
|
||||||
|
21
API/Schema/Jobs/DownloadAvailableChaptersJob.cs
Normal file
21
API/Schema/Jobs/DownloadAvailableChaptersJob.cs
Normal file
@ -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<string>? 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<Job> RunInternal(PgsqlContext context)
|
||||||
|
{
|
||||||
|
return context.Chapters.Where(c => c.ParentMangaId == MangaId).AsEnumerable()
|
||||||
|
.Select(chapter => new DownloadSingleChapterJob(chapter.ChapterId, this.JobId));
|
||||||
|
}
|
||||||
|
}
|
@ -4,8 +4,10 @@
|
|||||||
public enum JobType : byte
|
public enum JobType : byte
|
||||||
{
|
{
|
||||||
DownloadSingleChapterJob = 0,
|
DownloadSingleChapterJob = 0,
|
||||||
DownloadNewChaptersJob = 1,
|
DownloadAvailableChaptersJob = 1,
|
||||||
UpdateMetaDataJob = 2,
|
UpdateMetaDataJob = 2,
|
||||||
MoveFileOrFolderJob = 3,
|
MoveFileOrFolderJob = 3,
|
||||||
DownloadMangaCoverJob = 4
|
DownloadMangaCoverJob = 4,
|
||||||
|
RetrieveChaptersJob = 5,
|
||||||
|
UpdateFilesDownloadedJob = 6
|
||||||
}
|
}
|
@ -1,11 +1,11 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using API.Schema.MangaConnectors;
|
using API.Schema.MangaConnectors;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace API.Schema.Jobs;
|
namespace API.Schema.Jobs;
|
||||||
|
|
||||||
public class DownloadNewChaptersJob(ulong recurrenceMs, string mangaId, string? parentJobId = null, ICollection<string>? dependsOnJobsIds = null)
|
public class RetrieveChaptersJob(ulong recurrenceMs, string mangaId, string? parentJobId = null, ICollection<string>? dependsOnJobsIds = null)
|
||||||
: Job(TokenGen.CreateToken(typeof(DownloadNewChaptersJob)), JobType.DownloadNewChaptersJob, recurrenceMs, parentJobId, dependsOnJobsIds)
|
: Job(TokenGen.CreateToken(typeof(RetrieveChaptersJob)), JobType.RetrieveChaptersJob, recurrenceMs, parentJobId, dependsOnJobsIds)
|
||||||
{
|
{
|
||||||
[MaxLength(64)]
|
[MaxLength(64)]
|
||||||
public string MangaId { get; init; } = mangaId;
|
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();
|
Chapter[] newChapters = allNewChapters.Where(chapter => !chapterIds.Contains(chapter.ChapterId)).ToArray();
|
||||||
context.Chapters.AddRangeAsync(newChapters).Wait();
|
context.Chapters.AddRangeAsync(newChapters).Wait();
|
||||||
context.SaveChangesAsync().Wait();
|
context.SaveChangesAsync().Wait();
|
||||||
|
|
||||||
return allNewChapters.Select(chapter => new DownloadSingleChapterJob(chapter.ChapterId, this.JobId));
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
24
API/Schema/Jobs/UpdateFilesDownloadedJob.cs
Normal file
24
API/Schema/Jobs/UpdateFilesDownloadedJob.cs
Normal file
@ -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<string>? 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<Job> RunInternal(PgsqlContext context)
|
||||||
|
{
|
||||||
|
IQueryable<Chapter> chapters = context.Chapters.Where(c => c.ParentMangaId == MangaId);
|
||||||
|
foreach (Chapter chapter in chapters)
|
||||||
|
chapter.Downloaded = chapter.IsDownloaded();
|
||||||
|
|
||||||
|
context.SaveChanges();
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
@ -21,14 +21,6 @@ public class UpdateMetadataJob(ulong recurrenceMs, string mangaId, string? paren
|
|||||||
/// <param name="context"></param>
|
/// <param name="context"></param>
|
||||||
protected override IEnumerable<Job> RunInternal(PgsqlContext context)
|
protected override IEnumerable<Job> RunInternal(PgsqlContext context)
|
||||||
{
|
{
|
||||||
//Manga manga = Manga ?? context.Manga.Find(MangaId)!;
|
throw new NotImplementedException();
|
||||||
IQueryable<Chapter> 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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,4 +1,5 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
@ -6,6 +7,7 @@ using API.MangaDownloadClients;
|
|||||||
using API.Schema.Jobs;
|
using API.Schema.Jobs;
|
||||||
using API.Schema.MangaConnectors;
|
using API.Schema.MangaConnectors;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using static System.IO.UnixFileMode;
|
using static System.IO.UnixFileMode;
|
||||||
|
|
||||||
namespace API.Schema;
|
namespace API.Schema;
|
||||||
@ -30,26 +32,30 @@ public class Manga
|
|||||||
public float IgnoreChapterBefore { get; internal set; }
|
public float IgnoreChapterBefore { get; internal set; }
|
||||||
|
|
||||||
public string MangaConnectorId { get; private set; }
|
public string MangaConnectorId { get; private set; }
|
||||||
|
[JsonIgnore] public MangaConnector? MangaConnector { get; private set; }
|
||||||
|
|
||||||
public MangaConnector? MangaConnector { get; private set; }
|
[JsonIgnore] public ICollection<Author>? Authors { get; internal set; }
|
||||||
|
[NotMapped] public IEnumerable<string> AuthorIds => Authors?.Select(a => a.AuthorId) ?? [];
|
||||||
|
|
||||||
public ICollection<Author>? Authors { get; internal set; }
|
[JsonIgnore] public ICollection<MangaTag>? MangaTags { get; internal set; }
|
||||||
|
[NotMapped] public IEnumerable<string> Tags => MangaTags.Select(t => t.Tag);
|
||||||
|
|
||||||
public ICollection<MangaTag>? Tags { get; internal set; }
|
|
||||||
|
|
||||||
public ICollection<Link>? Links { get; internal set; }
|
[JsonIgnore] public ICollection<Link>? Links { get; internal set; }
|
||||||
|
[NotMapped] public IEnumerable<string> LinkIds => Links?.Select(l => l.LinkId) ?? [];
|
||||||
|
|
||||||
public ICollection<MangaAltTitle>? AltTitles { get; internal set; }
|
[JsonIgnore] public ICollection<MangaAltTitle>? AltTitles { get; internal set; }
|
||||||
|
[NotMapped] public IEnumerable<string> AltTitleIds => AltTitles?.Select(a => a.AltTitleId) ?? [];
|
||||||
|
|
||||||
public Manga(string connectorId, string name, string description, string websiteUrl, string coverUrl,
|
public Manga(string connectorId, string name, string description, string websiteUrl, string coverUrl,
|
||||||
string? coverFileNameInCache, uint year, string? originalLanguage, MangaReleaseStatus releaseStatus,
|
string? coverFileNameInCache, uint year, string? originalLanguage, MangaReleaseStatus releaseStatus,
|
||||||
float ignoreChapterBefore, MangaConnector mangaConnector, ICollection<Author> authors,
|
float ignoreChapterBefore, MangaConnector mangaConnector, ICollection<Author> authors,
|
||||||
ICollection<MangaTag> tags, ICollection<Link> links, ICollection<MangaAltTitle> altTitles)
|
ICollection<MangaTag> mangaTags, ICollection<Link> links, ICollection<MangaAltTitle> altTitles)
|
||||||
: this(connectorId, name, description, websiteUrl, coverUrl, coverFileNameInCache, year, originalLanguage,
|
: this(connectorId, name, description, websiteUrl, coverUrl, coverFileNameInCache, year, originalLanguage,
|
||||||
releaseStatus, ignoreChapterBefore, mangaConnector.Name)
|
releaseStatus, ignoreChapterBefore, mangaConnector.Name)
|
||||||
{
|
{
|
||||||
this.Authors = authors;
|
this.Authors = authors;
|
||||||
this.Tags = tags;
|
this.MangaTags = mangaTags;
|
||||||
this.Links = links;
|
this.Links = links;
|
||||||
this.AltTitles = altTitles;
|
this.AltTitles = altTitles;
|
||||||
}
|
}
|
||||||
@ -89,7 +95,7 @@ public class Manga
|
|||||||
this.OriginalLanguage = other.OriginalLanguage;
|
this.OriginalLanguage = other.OriginalLanguage;
|
||||||
this.Authors = other.Authors;
|
this.Authors = other.Authors;
|
||||||
this.Links = other.Links;
|
this.Links = other.Links;
|
||||||
this.Tags = other.Tags;
|
this.MangaTags = other.MangaTags;
|
||||||
this.AltTitles = other.AltTitles;
|
this.AltTitles = other.AltTitles;
|
||||||
this.ReleaseStatus = other.ReleaseStatus;
|
this.ReleaseStatus = other.ReleaseStatus;
|
||||||
}
|
}
|
||||||
|
@ -36,21 +36,23 @@ public class PgsqlContext(DbContextOptions<PgsqlContext> options) : DbContext(op
|
|||||||
.HasDiscriminator<LibraryType>(l => l.LibraryType)
|
.HasDiscriminator<LibraryType>(l => l.LibraryType)
|
||||||
.HasValue<Komga>(LibraryType.Komga)
|
.HasValue<Komga>(LibraryType.Komga)
|
||||||
.HasValue<Kavita>(LibraryType.Kavita);
|
.HasValue<Kavita>(LibraryType.Kavita);
|
||||||
|
|
||||||
modelBuilder.Entity<Job>()
|
modelBuilder.Entity<Job>()
|
||||||
.HasDiscriminator<JobType>(j => j.JobType)
|
.HasDiscriminator<JobType>(j => j.JobType)
|
||||||
.HasValue<MoveFileOrFolderJob>(JobType.MoveFileOrFolderJob)
|
.HasValue<MoveFileOrFolderJob>(JobType.MoveFileOrFolderJob)
|
||||||
.HasValue<DownloadNewChaptersJob>(JobType.DownloadNewChaptersJob)
|
.HasValue<DownloadAvailableChaptersJob>(JobType.DownloadAvailableChaptersJob)
|
||||||
.HasValue<DownloadSingleChapterJob>(JobType.DownloadSingleChapterJob)
|
.HasValue<DownloadSingleChapterJob>(JobType.DownloadSingleChapterJob)
|
||||||
.HasValue<DownloadMangaCoverJob>(JobType.DownloadMangaCoverJob)
|
.HasValue<DownloadMangaCoverJob>(JobType.DownloadMangaCoverJob)
|
||||||
.HasValue<UpdateMetadataJob>(JobType.UpdateMetaDataJob);
|
.HasValue<UpdateMetadataJob>(JobType.UpdateMetaDataJob)
|
||||||
|
.HasValue<RetrieveChaptersJob>(JobType.RetrieveChaptersJob)
|
||||||
|
.HasValue<UpdateFilesDownloadedJob>(JobType.UpdateFilesDownloadedJob);
|
||||||
modelBuilder.Entity<Job>()
|
modelBuilder.Entity<Job>()
|
||||||
.HasOne<Job>(j => j.ParentJob)
|
.HasOne<Job>(j => j.ParentJob)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey(j => j.ParentJobId);
|
.HasForeignKey(j => j.ParentJobId);
|
||||||
modelBuilder.Entity<Job>()
|
modelBuilder.Entity<Job>()
|
||||||
.HasMany<Job>(j => j.DependsOnJobs);
|
.HasMany<Job>(j => j.DependsOnJobs);
|
||||||
modelBuilder.Entity<DownloadNewChaptersJob>()
|
modelBuilder.Entity<DownloadAvailableChaptersJob>()
|
||||||
.Navigation(dncj => dncj.Manga)
|
.Navigation(dncj => dncj.Manga)
|
||||||
.AutoInclude();
|
.AutoInclude();
|
||||||
modelBuilder.Entity<DownloadSingleChapterJob>()
|
modelBuilder.Entity<DownloadSingleChapterJob>()
|
||||||
@ -74,10 +76,10 @@ public class PgsqlContext(DbContextOptions<PgsqlContext> options) : DbContext(op
|
|||||||
.Navigation(m => m.Authors)
|
.Navigation(m => m.Authors)
|
||||||
.AutoInclude();
|
.AutoInclude();
|
||||||
modelBuilder.Entity<Manga>()
|
modelBuilder.Entity<Manga>()
|
||||||
.HasMany<MangaTag>(m => m.Tags)
|
.HasMany<MangaTag>(m => m.MangaTags)
|
||||||
.WithMany();
|
.WithMany();
|
||||||
modelBuilder.Entity<Manga>()
|
modelBuilder.Entity<Manga>()
|
||||||
.Navigation(m => m.Tags)
|
.Navigation(m => m.MangaTags)
|
||||||
.AutoInclude();
|
.AutoInclude();
|
||||||
modelBuilder.Entity<Manga>()
|
modelBuilder.Entity<Manga>()
|
||||||
.HasMany<Link>(m => m.Links)
|
.HasMany<Link>(m => m.Links)
|
||||||
|
@ -91,10 +91,10 @@ public static class Tranga
|
|||||||
// If the job is already running, skip it
|
// If the job is already running, skip it
|
||||||
if (RunningJobs.Values.Any(j => j.JobId == job.JobId)) continue;
|
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 =>
|
if (RunningJobs.Values.Any(j =>
|
||||||
j is DownloadNewChaptersJob rdncj &&
|
j is DownloadAvailableChaptersJob rdncj &&
|
||||||
rdncj.Manga?.MangaConnector == dncj.Manga?.MangaConnector))
|
rdncj.Manga?.MangaConnector == dncj.Manga?.MangaConnector))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
@ -143,13 +143,15 @@ public static class Tranga
|
|||||||
IEnumerable<Job> ret = new List<Job>();
|
IEnumerable<Job> ret = new List<Job>();
|
||||||
if(jobsByType.ContainsKey(JobType.MoveFileOrFolderJob))
|
if(jobsByType.ContainsKey(JobType.MoveFileOrFolderJob))
|
||||||
ret = ret.Concat(jobsByType[JobType.MoveFileOrFolderJob]);
|
ret = ret.Concat(jobsByType[JobType.MoveFileOrFolderJob]);
|
||||||
if(jobsByType.ContainsKey(JobType.DownloadMangaCoverJob))
|
if(jobsByType.ContainsKey(JobType.DownloadMangaCoverJob))
|
||||||
ret = ret.Concat(jobsByType[JobType.DownloadMangaCoverJob]);
|
ret = ret.Concat(jobsByType[JobType.DownloadMangaCoverJob]);
|
||||||
|
if(jobsByType.ContainsKey(JobType.UpdateFilesDownloadedJob))
|
||||||
|
ret = ret.Concat(jobsByType[JobType.UpdateFilesDownloadedJob]);
|
||||||
|
|
||||||
Dictionary<MangaConnector, List<Job>> metadataJobsByConnector = new();
|
Dictionary<MangaConnector, List<Job>> 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)!;
|
Manga manga = job.Manga ?? context.Manga.Find(job.MangaId)!;
|
||||||
MangaConnector connector = manga.MangaConnector ?? context.MangaConnectors.Find(manga.MangaConnectorId)!;
|
MangaConnector connector = manga.MangaConnector ?? context.MangaConnectors.Find(manga.MangaConnectorId)!;
|
||||||
@ -167,6 +169,16 @@ public static class Tranga
|
|||||||
metadataJobsByConnector[connector].Add(job);
|
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<Job> metadataJobs in metadataJobsByConnector.Values)
|
foreach (List<Job> metadataJobs in metadataJobsByConnector.Values)
|
||||||
ret = ret.Append(metadataJobs.MinBy(j => j.NextExecution))!;
|
ret = ret.Append(metadataJobs.MinBy(j => j.NextExecution))!;
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user