Compare commits

..

No commits in common. "3283dd72903f4e891bb0662407c0a13837664b64" and "a1a5028858053ee201812a278c702c640c9f0236" have entirely different histories.

30 changed files with 169 additions and 1177 deletions

View File

@ -19,7 +19,6 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="9.0.5" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Npgsql" Version="9.0.3" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />

View File

@ -134,10 +134,8 @@ public class JobController(PgsqlContext context, ILog Log) : Controller
}
}
Job retrieveChapters = new RetrieveChaptersJob(m, record.language, record.recurrenceTimeMs);
Job updateFilesDownloaded =
new UpdateChaptersDownloadedJob(m, record.recurrenceTimeMs, dependsOnJobs: [retrieveChapters]);
Job downloadChapters = new DownloadAvailableChaptersJob(m, record.recurrenceTimeMs, dependsOnJobs: [retrieveChapters, updateFilesDownloaded]);
return AddJobs([retrieveChapters, downloadChapters, updateFilesDownloaded]);
Job downloadChapters = new DownloadAvailableChaptersJob(m, record.recurrenceTimeMs, dependsOnJobs: [retrieveChapters]);
return AddJobs([retrieveChapters, downloadChapters]);
}
/// <summary>
@ -160,7 +158,7 @@ public class JobController(PgsqlContext context, ILog Log) : Controller
}
/// <summary>
/// Create a new UpdateChaptersDownloadedJob
/// Create a new UpdateFilesDownloadedJob
/// </summary>
/// <param name="MangaId">ID of the Manga</param>
/// <response code="201">Job-IDs</response>
@ -174,7 +172,7 @@ public class JobController(PgsqlContext context, ILog Log) : Controller
{
if(context.Mangas.Find(MangaId) is not { } m)
return NotFound();
Job job = new UpdateChaptersDownloadedJob(m, 0);
Job job = new UpdateFilesDownloadedJob(m, 0);
return AddJobs([job]);
}
@ -188,7 +186,7 @@ public class JobController(PgsqlContext context, ILog Log) : Controller
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
public IActionResult CreateUpdateAllFilesDownloadedJob()
{
List<UpdateChaptersDownloadedJob> jobs = context.Mangas.Select(m => new UpdateChaptersDownloadedJob(m, 0, null, null)).ToList();
List<UpdateFilesDownloadedJob> jobs = context.Mangas.Select(m => new UpdateFilesDownloadedJob(m, 0, null, null)).ToList();
try
{
context.Jobs.AddRange(jobs);

View File

@ -343,7 +343,7 @@ public class MangaController(PgsqlContext context, ILog Log) : Controller
return NotFound();
MoveMangaLibraryJob moveLibrary = new(manga, library);
UpdateChaptersDownloadedJob updateDownloadedFiles = new(manga, 0, dependsOnJobs: [moveLibrary]);
UpdateFilesDownloadedJob updateDownloadedFiles = new(manga, 0, dependsOnJobs: [moveLibrary]);
try
{

View File

@ -25,7 +25,7 @@ public class SearchController(PgsqlContext context, ILog Log) : Controller
/// <response code="404">MangaConnector with ID not found</response>
/// <response code="406">MangaConnector with ID is disabled</response>
/// <response code="500">Error during Database Operation</response>
[HttpGet("{MangaConnectorName}/{Query}")]
[HttpPost("{MangaConnectorName}/{Query}")]
[ProducesResponseType<Manga[]>(Status200OK, "application/json")]
[ProducesResponseType(Status404NotFound)]
[ProducesResponseType(Status406NotAcceptable)]
@ -67,25 +67,30 @@ public class SearchController(PgsqlContext context, ILog Log) : Controller
/// <response code="500">Error during Database Operation</response>
[HttpPost("Url")]
[ProducesResponseType<Manga>(Status200OK, "application/json")]
[ProducesResponseType(Status300MultipleChoices)]
[ProducesResponseType(Status400BadRequest)]
[ProducesResponseType(Status404NotFound)]
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
public IActionResult GetMangaFromUrl([FromBody]string url)
{
if (context.MangaConnectors.Find("Global") is not { } connector)
return StatusCode(Status500InternalServerError, "Could not find Global Connector.");
List<MangaConnector> connectors = context.MangaConnectors.AsEnumerable().Where(c => c.UrlMatchesConnector(url)).ToList();
if (connectors.Count == 0)
return NotFound();
else if (connectors.Count > 1)
return StatusCode(Status300MultipleChoices);
if(connector.GetMangaFromUrl(url) is not { } manga)
if(connectors.First().GetMangaFromUrl(url) is not { } manga)
return BadRequest();
try
{
if(AddMangaToContext(manga) is { } add)
return Ok(add);
return StatusCode(Status500InternalServerError);
return StatusCode(500);
}
catch (DbUpdateException e)
{
Log.Error(e);
return StatusCode(Status500InternalServerError, e.Message);
return StatusCode(500, e.Message);
}
}

View File

@ -416,7 +416,7 @@ namespace API.Migrations.pgsql
b.HasDiscriminator().HasValue((byte)5);
});
modelBuilder.Entity("API.Schema.Jobs.UpdateChaptersDownloadedJob", b =>
modelBuilder.Entity("API.Schema.Jobs.UpdateFilesDownloadedJob", b =>
{
b.HasBaseType("API.Schema.Jobs.Job");
@ -661,7 +661,7 @@ namespace API.Migrations.pgsql
b.Navigation("Manga");
});
modelBuilder.Entity("API.Schema.Jobs.UpdateChaptersDownloadedJob", b =>
modelBuilder.Entity("API.Schema.Jobs.UpdateFilesDownloadedJob", b =>
{
b.HasOne("API.Schema.Manga", "Manga")
.WithMany()

View File

@ -416,7 +416,7 @@ namespace API.Migrations.pgsql
b.HasDiscriminator().HasValue((byte)5);
});
modelBuilder.Entity("API.Schema.Jobs.UpdateChaptersDownloadedJob", b =>
modelBuilder.Entity("API.Schema.Jobs.UpdateFilesDownloadedJob", b =>
{
b.HasBaseType("API.Schema.Jobs.Job");
@ -667,7 +667,7 @@ namespace API.Migrations.pgsql
b.Navigation("Manga");
});
modelBuilder.Entity("API.Schema.Jobs.UpdateChaptersDownloadedJob", b =>
modelBuilder.Entity("API.Schema.Jobs.UpdateFilesDownloadedJob", b =>
{
b.HasOne("API.Schema.Manga", "Manga")
.WithMany()

View File

@ -416,7 +416,7 @@ namespace API.Migrations.pgsql
b.HasDiscriminator().HasValue((byte)5);
});
modelBuilder.Entity("API.Schema.Jobs.UpdateChaptersDownloadedJob", b =>
modelBuilder.Entity("API.Schema.Jobs.UpdateFilesDownloadedJob", b =>
{
b.HasBaseType("API.Schema.Jobs.Job");
@ -667,7 +667,7 @@ namespace API.Migrations.pgsql
b.Navigation("Manga");
});
modelBuilder.Entity("API.Schema.Jobs.UpdateChaptersDownloadedJob", b =>
modelBuilder.Entity("API.Schema.Jobs.UpdateFilesDownloadedJob", b =>
{
b.HasOne("API.Schema.Manga", "Manga")
.WithMany()

View File

@ -416,7 +416,7 @@ namespace API.Migrations.pgsql
b.HasDiscriminator().HasValue((byte)5);
});
modelBuilder.Entity("API.Schema.Jobs.UpdateChaptersDownloadedJob", b =>
modelBuilder.Entity("API.Schema.Jobs.UpdateFilesDownloadedJob", b =>
{
b.HasBaseType("API.Schema.Jobs.Job");
@ -668,7 +668,7 @@ namespace API.Migrations.pgsql
b.Navigation("Manga");
});
modelBuilder.Entity("API.Schema.Jobs.UpdateChaptersDownloadedJob", b =>
modelBuilder.Entity("API.Schema.Jobs.UpdateFilesDownloadedJob", b =>
{
b.HasOne("API.Schema.Manga", "Manga")
.WithMany()

View File

@ -1,720 +0,0 @@
// <auto-generated />
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
{
/// <inheritdoc />
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<string>("AuthorId")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<string>("AuthorName")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("character varying(128)");
b.HasKey("AuthorId");
b.ToTable("Authors");
});
modelBuilder.Entity("API.Schema.Chapter", b =>
{
b.Property<string>("ChapterId")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<string>("ChapterNumber")
.IsRequired()
.HasMaxLength(10)
.HasColumnType("character varying(10)");
b.Property<bool>("Downloaded")
.HasColumnType("boolean");
b.Property<string>("FileName")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("ParentMangaId")
.IsRequired()
.HasColumnType("character varying(64)");
b.Property<string>("Title")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("Url")
.IsRequired()
.HasMaxLength(2048)
.HasColumnType("character varying(2048)");
b.Property<int?>("VolumeNumber")
.HasColumnType("integer");
b.HasKey("ChapterId");
b.HasIndex("ParentMangaId");
b.ToTable("Chapters");
});
modelBuilder.Entity("API.Schema.Jobs.Job", b =>
{
b.Property<string>("JobId")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<bool>("Enabled")
.HasColumnType("boolean");
b.Property<byte>("JobType")
.HasColumnType("smallint");
b.Property<DateTime>("LastExecution")
.HasColumnType("timestamp with time zone");
b.Property<string>("ParentJobId")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<decimal>("RecurrenceMs")
.HasColumnType("numeric(20,0)");
b.Property<byte>("state")
.HasColumnType("smallint");
b.HasKey("JobId");
b.HasIndex("ParentJobId");
b.ToTable("Jobs");
b.HasDiscriminator<byte>("JobType");
b.UseTphMappingStrategy();
});
modelBuilder.Entity("API.Schema.LocalLibrary", b =>
{
b.Property<string>("LocalLibraryId")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<string>("BasePath")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("LibraryName")
.IsRequired()
.HasMaxLength(512)
.HasColumnType("character varying(512)");
b.HasKey("LocalLibraryId");
b.ToTable("LocalLibraries");
});
modelBuilder.Entity("API.Schema.Manga", b =>
{
b.Property<string>("MangaId")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<string>("CoverFileNameInCache")
.HasMaxLength(512)
.HasColumnType("character varying(512)");
b.Property<string>("CoverUrl")
.IsRequired()
.HasMaxLength(512)
.HasColumnType("character varying(512)");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("text");
b.Property<string>("DirectoryName")
.IsRequired()
.HasMaxLength(1024)
.HasColumnType("character varying(1024)");
b.Property<string>("IdOnConnectorSite")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<float>("IgnoreChaptersBefore")
.HasColumnType("real");
b.Property<string>("LibraryId")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<string>("MangaConnectorName")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("character varying(32)");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(512)
.HasColumnType("character varying(512)");
b.Property<string>("OriginalLanguage")
.HasMaxLength(8)
.HasColumnType("character varying(8)");
b.Property<byte>("ReleaseStatus")
.HasColumnType("smallint");
b.Property<string>("WebsiteUrl")
.IsRequired()
.HasMaxLength(512)
.HasColumnType("character varying(512)");
b.Property<long?>("Year")
.HasColumnType("bigint");
b.HasKey("MangaId");
b.HasIndex("LibraryId");
b.HasIndex("MangaConnectorName");
b.ToTable("Mangas");
});
modelBuilder.Entity("API.Schema.MangaConnectors.MangaConnector", b =>
{
b.Property<string>("Name")
.HasMaxLength(32)
.HasColumnType("character varying(32)");
b.PrimitiveCollection<string[]>("BaseUris")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("text[]");
b.Property<bool>("Enabled")
.HasColumnType("boolean");
b.Property<string>("IconUrl")
.IsRequired()
.HasMaxLength(2048)
.HasColumnType("character varying(2048)");
b.PrimitiveCollection<string[]>("SupportedLanguages")
.IsRequired()
.HasMaxLength(8)
.HasColumnType("text[]");
b.HasKey("Name");
b.ToTable("MangaConnectors");
b.HasDiscriminator<string>("Name").HasValue("MangaConnector");
b.UseTphMappingStrategy();
});
modelBuilder.Entity("API.Schema.MangaTag", b =>
{
b.Property<string>("Tag")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.HasKey("Tag");
b.ToTable("Tags");
});
modelBuilder.Entity("AuthorToManga", b =>
{
b.Property<string>("AuthorIds")
.HasColumnType("character varying(64)");
b.Property<string>("MangaIds")
.HasColumnType("character varying(64)");
b.HasKey("AuthorIds", "MangaIds");
b.HasIndex("MangaIds");
b.ToTable("AuthorToManga");
});
modelBuilder.Entity("JobJob", b =>
{
b.Property<string>("DependsOnJobsJobId")
.HasColumnType("character varying(64)");
b.Property<string>("JobId")
.HasColumnType("character varying(64)");
b.HasKey("DependsOnJobsJobId", "JobId");
b.HasIndex("JobId");
b.ToTable("JobJob");
});
modelBuilder.Entity("MangaTagToManga", b =>
{
b.Property<string>("MangaTagIds")
.HasColumnType("character varying(64)");
b.Property<string>("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<string>("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<string>("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<string>("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<string>("FromLocation")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("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<string>("MangaId")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<string>("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<string>("Language")
.IsRequired()
.HasMaxLength(8)
.HasColumnType("character varying(8)");
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.UpdateChaptersDownloadedJob", 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("UpdateChaptersDownloadedJob_MangaId");
});
b.HasDiscriminator().HasValue((byte)6);
});
modelBuilder.Entity("API.Schema.Jobs.UpdateSingleChapterDownloadedJob", b =>
{
b.HasBaseType("API.Schema.Jobs.Job");
b.Property<string>("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<string>("LinkId")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b1.Property<string>("LinkProvider")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b1.Property<string>("LinkUrl")
.IsRequired()
.HasMaxLength(2048)
.HasColumnType("character varying(2048)");
b1.Property<string>("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<string>("AltTitleId")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b1.Property<string>("Language")
.IsRequired()
.HasMaxLength(8)
.HasColumnType("character varying(8)");
b1.Property<string>("MangaId")
.IsRequired()
.HasColumnType("character varying(64)");
b1.Property<string>("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
}
}
}

View File

@ -1,94 +0,0 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace API.Migrations.pgsql
{
/// <inheritdoc />
public partial class SplitUpdateChaptersDownloadedJobIntoUpdateSingleChapterDownloadedJob : Migration
{
/// <inheritdoc />
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<string>(
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);
}
/// <inheritdoc />
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);
}
}
}

View File

@ -413,7 +413,7 @@ namespace API.Migrations.pgsql
b.HasDiscriminator().HasValue((byte)5);
});
modelBuilder.Entity("API.Schema.Jobs.UpdateChaptersDownloadedJob", b =>
modelBuilder.Entity("API.Schema.Jobs.UpdateFilesDownloadedJob", b =>
{
b.HasBaseType("API.Schema.Jobs.Job");
@ -427,32 +427,12 @@ namespace API.Migrations.pgsql
b.ToTable("Jobs", t =>
{
t.Property("MangaId")
.HasColumnName("UpdateChaptersDownloadedJob_MangaId");
.HasColumnName("UpdateFilesDownloadedJob_MangaId");
});
b.HasDiscriminator().HasValue((byte)6);
});
modelBuilder.Entity("API.Schema.Jobs.UpdateSingleChapterDownloadedJob", b =>
{
b.HasBaseType("API.Schema.Jobs.Job");
b.Property<string>("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");
@ -685,7 +665,7 @@ namespace API.Migrations.pgsql
b.Navigation("Manga");
});
modelBuilder.Entity("API.Schema.Jobs.UpdateChaptersDownloadedJob", b =>
modelBuilder.Entity("API.Schema.Jobs.UpdateFilesDownloadedJob", b =>
{
b.HasOne("API.Schema.Manga", "Manga")
.WithMany()
@ -696,17 +676,6 @@ 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");

View File

@ -122,9 +122,12 @@ using (IServiceScope scope = app.Services.CreateScope())
context.LocalLibraries.Add(new LocalLibrary(TrangaSettings.downloadLocation, "Default Library"));
context.Jobs.AddRange(context.Jobs.Where(j => j.JobType == JobType.DownloadAvailableChaptersJob)
.Include(downloadAvailableChaptersJob => ((DownloadAvailableChaptersJob)downloadAvailableChaptersJob).Manga)
.ToList()
.Select(dacj => new UpdateChaptersDownloadedJob(((DownloadAvailableChaptersJob)dacj).Manga, 0)));
.AsEnumerable()
.Select(dacj =>
{
DownloadAvailableChaptersJob? j = dacj as DownloadAvailableChaptersJob;
return new UpdateFilesDownloadedJob(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))
{
@ -150,7 +153,7 @@ using (IServiceScope scope = app.Services.CreateScope())
TrangaSettings.Load();
Tranga.StartLogger();
Tranga.JobStarterThread.Start(app.Services);
//Tranga.NotificationSenderThread.Start(app.Services); //TODO RE-ENABLE
Tranga.NotificationSenderThread.Start(app.Services);
app.UseCors("AllowAll");

View File

@ -26,7 +26,7 @@ public class Chapter : IComparable<Chapter>
[StringLength(256)] [Required] public string FileName { get; private set; }
[Required] public bool Downloaded { get; internal set; }
[NotMapped] public string FullArchiveFilePath => Path.Join(ParentManga.FullDirectoryPath, FileName);
[JsonIgnore] [NotMapped] public string FullArchiveFilePath => Path.Join(ParentManga.FullDirectoryPath, FileName);
public Chapter(Manga parentManga, string url, string chapterNumber, int? volumeNumber = null, string? title = null)
{

View File

@ -1,26 +1,12 @@
using API.Schema.LibraryConnectors;
using log4net;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
namespace API.Schema.Contexts;
public class LibraryContext(DbContextOptions<LibraryContext> options) : DbContext(options)
{
public DbSet<LibraryConnector> LibraryConnectors { get; set; }
private ILog Log => LogManager.GetLogger(GetType());
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
optionsBuilder.EnableSensitiveDataLogging();
optionsBuilder.LogTo(s =>
{
Log.Debug(s);
}, [DbLoggerCategory.Query.Name], LogLevel.Trace, DbContextLoggerOptions.Level | DbContextLoggerOptions.Category);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//LibraryConnector Types

View File

@ -1,7 +1,5 @@
using API.Schema.NotificationConnectors;
using log4net;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
namespace API.Schema.Contexts;
@ -9,15 +7,4 @@ public class NotificationsContext(DbContextOptions<NotificationsContext> options
{
public DbSet<NotificationConnector> NotificationConnectors { get; set; }
public DbSet<Notification> Notifications { get; set; }
private ILog Log => LogManager.GetLogger(GetType());
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
optionsBuilder.EnableSensitiveDataLogging();
optionsBuilder.LogTo(s =>
{
Log.Debug(s);
}, [DbLoggerCategory.Query.Name], LogLevel.Trace, DbContextLoggerOptions.Level | DbContextLoggerOptions.Category);
}
}

View File

@ -1,8 +1,6 @@
using API.Schema.Jobs;
using API.Schema.MangaConnectors;
using log4net;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
namespace API.Schema.Contexts;
@ -15,18 +13,7 @@ public class PgsqlContext(DbContextOptions<PgsqlContext> options) : DbContext(op
public DbSet<Chapter> Chapters { get; set; }
public DbSet<Author> Authors { get; set; }
public DbSet<MangaTag> Tags { get; set; }
private ILog Log => LogManager.GetLogger(GetType());
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
optionsBuilder.EnableSensitiveDataLogging();
optionsBuilder.LogTo(s =>
{
Log.Debug(s);
}, [DbLoggerCategory.Query.Name], LogLevel.Trace, DbContextLoggerOptions.Level | DbContextLoggerOptions.Category);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//Job Types
@ -38,8 +25,7 @@ public class PgsqlContext(DbContextOptions<PgsqlContext> options) : DbContext(op
.HasValue<DownloadSingleChapterJob>(JobType.DownloadSingleChapterJob)
.HasValue<DownloadMangaCoverJob>(JobType.DownloadMangaCoverJob)
.HasValue<RetrieveChaptersJob>(JobType.RetrieveChaptersJob)
.HasValue<UpdateChaptersDownloadedJob>(JobType.UpdateChaptersDownloadedJob)
.HasValue<UpdateSingleChapterDownloadedJob>(JobType.UpdateSingleChapterDownloadedJob);
.HasValue<UpdateFilesDownloadedJob>(JobType.UpdateFilesDownloadedJob);
//Job specification
modelBuilder.Entity<DownloadAvailableChaptersJob>()
@ -50,7 +36,7 @@ public class PgsqlContext(DbContextOptions<PgsqlContext> options) : DbContext(op
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<DownloadAvailableChaptersJob>()
.Navigation(j => j.Manga)
.EnableLazyLoading();
.AutoInclude();
modelBuilder.Entity<DownloadMangaCoverJob>()
.HasOne<Manga>(j => j.Manga)
.WithMany()
@ -59,7 +45,7 @@ public class PgsqlContext(DbContextOptions<PgsqlContext> options) : DbContext(op
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<DownloadMangaCoverJob>()
.Navigation(j => j.Manga)
.EnableLazyLoading();
.AutoInclude();
modelBuilder.Entity<DownloadSingleChapterJob>()
.HasOne<Chapter>(j => j.Chapter)
.WithMany()
@ -68,7 +54,7 @@ public class PgsqlContext(DbContextOptions<PgsqlContext> options) : DbContext(op
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<DownloadSingleChapterJob>()
.Navigation(j => j.Chapter)
.EnableLazyLoading();
.AutoInclude();
modelBuilder.Entity<MoveMangaLibraryJob>()
.HasOne<Manga>(j => j.Manga)
.WithMany()
@ -77,7 +63,7 @@ public class PgsqlContext(DbContextOptions<PgsqlContext> options) : DbContext(op
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<MoveMangaLibraryJob>()
.Navigation(j => j.Manga)
.EnableLazyLoading();
.AutoInclude();
modelBuilder.Entity<MoveMangaLibraryJob>()
.HasOne<LocalLibrary>(j => j.ToLibrary)
.WithMany()
@ -86,7 +72,7 @@ public class PgsqlContext(DbContextOptions<PgsqlContext> options) : DbContext(op
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<MoveMangaLibraryJob>()
.Navigation(j => j.ToLibrary)
.EnableLazyLoading();
.AutoInclude();
modelBuilder.Entity<RetrieveChaptersJob>()
.HasOne<Manga>(j => j.Manga)
.WithMany()
@ -95,22 +81,23 @@ public class PgsqlContext(DbContextOptions<PgsqlContext> options) : DbContext(op
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<RetrieveChaptersJob>()
.Navigation(j => j.Manga)
.EnableLazyLoading();
modelBuilder.Entity<UpdateChaptersDownloadedJob>()
.AutoInclude();
modelBuilder.Entity<UpdateFilesDownloadedJob>()
.HasOne<Manga>(j => j.Manga)
.WithMany()
.HasForeignKey(j => j.MangaId)
.IsRequired()
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<UpdateChaptersDownloadedJob>()
modelBuilder.Entity<UpdateFilesDownloadedJob>()
.Navigation(j => j.Manga)
.EnableLazyLoading();
.AutoInclude();
//Job has possible ParentJob
modelBuilder.Entity<Job>()
.HasOne<Job>(childJob => childJob.ParentJob)
.WithMany()
.HasMany<Job>()
.WithOne(childJob => childJob.ParentJob)
.HasForeignKey(childjob => childjob.ParentJobId)
.IsRequired(false)
.OnDelete(DeleteBehavior.Cascade);
//Job might be dependent on other Jobs
modelBuilder.Entity<Job>()
@ -118,8 +105,7 @@ public class PgsqlContext(DbContextOptions<PgsqlContext> options) : DbContext(op
.WithMany();
modelBuilder.Entity<Job>()
.Navigation(j => j.DependsOnJobs)
.AutoInclude(false)
.EnableLazyLoading();
.AutoInclude(false);
//MangaConnector Types
modelBuilder.Entity<MangaConnector>()
@ -150,8 +136,7 @@ public class PgsqlContext(DbContextOptions<PgsqlContext> options) : DbContext(op
.AutoInclude();
modelBuilder.Entity<Manga>()
.Navigation(m => m.Chapters)
.AutoInclude(false)
.EnableLazyLoading();
.AutoInclude(false);
//Manga owns MangaAltTitles
modelBuilder.Entity<Manga>()
.OwnsMany<MangaAltTitle>(m => m.AltTitles)

View File

@ -1,6 +1,5 @@
using System.ComponentModel.DataAnnotations;
using API.Schema.Contexts;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Newtonsoft.Json;
namespace API.Schema.Jobs;
@ -8,15 +7,7 @@ namespace API.Schema.Jobs;
public class DownloadAvailableChaptersJob : Job
{
[StringLength(64)] [Required] public string MangaId { get; init; }
private Manga _manga = null!;
[JsonIgnore]
public Manga Manga
{
get => LazyLoader.Load(this, ref _manga);
init => _manga = value;
}
[JsonIgnore] public Manga Manga { get; init; } = null!;
public DownloadAvailableChaptersJob(Manga manga, ulong recurrenceMs, Job? parentJob = null, ICollection<Job>? dependsOnJobs = null)
: base(TokenGen.CreateToken(typeof(DownloadAvailableChaptersJob)), JobType.DownloadAvailableChaptersJob, recurrenceMs, parentJob, dependsOnJobs)
@ -28,14 +19,15 @@ public class DownloadAvailableChaptersJob : Job
/// <summary>
/// EF ONLY!!!
/// </summary>
internal DownloadAvailableChaptersJob(ILazyLoader lazyLoader, string mangaId, ulong recurrenceMs, string? parentJobId)
: base(lazyLoader, TokenGen.CreateToken(typeof(DownloadAvailableChaptersJob)), JobType.DownloadAvailableChaptersJob, recurrenceMs, parentJobId)
internal DownloadAvailableChaptersJob(string mangaId, ulong recurrenceMs, string? parentJobId)
: base(TokenGen.CreateToken(typeof(DownloadAvailableChaptersJob)), JobType.DownloadAvailableChaptersJob, recurrenceMs, parentJobId)
{
this.MangaId = mangaId;
}
protected override IEnumerable<Job> RunInternal(PgsqlContext context)
{
return Manga.Chapters.Where(c => c.Downloaded == false).Select(chapter => new DownloadSingleChapterJob(chapter, this));
context.Attach(Manga);
return Manga.Chapters.Select(chapter => new DownloadSingleChapterJob(chapter, this));
}
}

View File

@ -1,7 +1,6 @@
using System.ComponentModel.DataAnnotations;
using API.Schema.Contexts;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Newtonsoft.Json;
namespace API.Schema.Jobs;
@ -9,15 +8,7 @@ namespace API.Schema.Jobs;
public class DownloadMangaCoverJob : Job
{
[StringLength(64)] [Required] public string MangaId { get; init; }
private Manga _manga = null!;
[JsonIgnore]
public Manga Manga
{
get => LazyLoader.Load(this, ref _manga);
init => _manga = value;
}
[JsonIgnore] public Manga Manga { get; init; } = null!;
public DownloadMangaCoverJob(Manga manga, Job? parentJob = null, ICollection<Job>? dependsOnJobs = null)
: base(TokenGen.CreateToken(typeof(DownloadMangaCoverJob)), JobType.DownloadMangaCoverJob, 0, parentJob, dependsOnJobs)
@ -29,14 +20,15 @@ public class DownloadMangaCoverJob : Job
/// <summary>
/// EF ONLY!!!
/// </summary>
internal DownloadMangaCoverJob(ILazyLoader lazyLoader, string mangaId, string? parentJobId)
: base(lazyLoader, TokenGen.CreateToken(typeof(DownloadMangaCoverJob)), JobType.DownloadMangaCoverJob, 0, parentJobId)
internal DownloadMangaCoverJob(string mangaId, string? parentJobId)
: base(TokenGen.CreateToken(typeof(DownloadMangaCoverJob)), JobType.DownloadMangaCoverJob, 0, parentJobId)
{
this.MangaId = mangaId;
}
protected override IEnumerable<Job> RunInternal(PgsqlContext context)
{
context.Attach(Manga);
try
{
Manga.CoverFileNameInCache = Manga.MangaConnector.SaveCoverImageToCache(Manga);

View File

@ -3,8 +3,6 @@ using System.IO.Compression;
using System.Runtime.InteropServices;
using API.MangaDownloadClients;
using API.Schema.Contexts;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Newtonsoft.Json;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Jpeg;
@ -18,14 +16,7 @@ public class DownloadSingleChapterJob : Job
{
[StringLength(64)] [Required] public string ChapterId { get; init; }
private Chapter _chapter = null!;
[JsonIgnore]
public Chapter Chapter
{
get => LazyLoader.Load(this, ref _chapter);
init => _chapter = value;
}
[JsonIgnore] public Chapter Chapter { get; init; } = null!;
public DownloadSingleChapterJob(Chapter chapter, Job? parentJob = null, ICollection<Job>? dependsOnJobs = null)
: base(TokenGen.CreateToken(typeof(DownloadSingleChapterJob)), JobType.DownloadSingleChapterJob, 0, parentJob, dependsOnJobs)
@ -37,14 +28,15 @@ public class DownloadSingleChapterJob : Job
/// <summary>
/// EF ONLY!!!
/// </summary>
internal DownloadSingleChapterJob(ILazyLoader lazyLoader, string chapterId, string? parentJobId)
: base(lazyLoader, TokenGen.CreateToken(typeof(DownloadSingleChapterJob)), JobType.DownloadSingleChapterJob, 0, parentJobId)
internal DownloadSingleChapterJob(string chapterId, string? parentJobId)
: base(TokenGen.CreateToken(typeof(DownloadSingleChapterJob)), JobType.DownloadSingleChapterJob, 0, parentJobId)
{
this.ChapterId = chapterId;
}
protected override IEnumerable<Job> RunInternal(PgsqlContext context)
{
context.Attach(Chapter);
string[] imageUrls = Chapter.ParentManga.MangaConnector.GetChapterImageUrls(Chapter);
if (imageUrls.Length < 1)
{
@ -105,16 +97,16 @@ public class DownloadSingleChapterJob : Job
Chapter.Downloaded = true;
context.SaveChanges();
if (context.Jobs.ToList().Any(j =>
if (context.Jobs.AsEnumerable().Any(j =>
{
if (j.JobType != JobType.UpdateChaptersDownloadedJob)
if (j.JobType != JobType.UpdateFilesDownloadedJob)
return false;
UpdateChaptersDownloadedJob job = (UpdateChaptersDownloadedJob)j;
UpdateFilesDownloadedJob job = (UpdateFilesDownloadedJob)j;
return job.MangaId == this.Chapter.ParentMangaId;
}))
return [];
return [new UpdateChaptersDownloadedJob(Chapter.ParentManga, 0, this.ParentJob)];
return [new UpdateFilesDownloadedJob(Chapter.ParentManga, 0, this.ParentJob)];
}
private void ProcessImage(string imagePath)

View File

@ -3,7 +3,6 @@ using System.ComponentModel.DataAnnotations.Schema;
using API.Schema.Contexts;
using log4net;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Newtonsoft.Json;
namespace API.Schema.Jobs;
@ -17,12 +16,7 @@ public abstract class Job
[StringLength(64)] public string? ParentJobId { get; init; }
[JsonIgnore] public Job? ParentJob { get; init; }
private ICollection<Job> _dependsOnJobs = null!;
[JsonIgnore] public ICollection<Job> DependsOnJobs
{
get => LazyLoader.Load(this, ref _dependsOnJobs);
init => _dependsOnJobs = value;
}
[JsonIgnore] public ICollection<Job> DependsOnJobs { get; init; }
[Required] public JobType JobType { get; init; }
@ -38,7 +32,6 @@ public abstract class Job
[JsonIgnore] [NotMapped] internal bool DependenciesFulfilled => DependsOnJobs.All(j => j.IsCompleted);
[NotMapped] [JsonIgnore] protected ILog Log { get; init; }
[NotMapped] [JsonIgnore] protected ILazyLoader LazyLoader { get; init; }
protected Job(string jobId, JobType jobType, ulong recurrenceMs, Job? parentJob = null, ICollection<Job>? dependsOnJobs = null)
{
@ -55,9 +48,8 @@ public abstract class Job
/// <summary>
/// EF ONLY!!!
/// </summary>
protected internal Job(ILazyLoader lazyLoader, string jobId, JobType jobType, ulong recurrenceMs, string? parentJobId)
protected internal Job(string jobId, JobType jobType, ulong recurrenceMs, string? parentJobId)
{
this.LazyLoader = lazyLoader;
this.JobId = jobId;
this.JobType = jobType;
this.RecurrenceMs = recurrenceMs;
@ -69,39 +61,29 @@ public abstract class Job
public IEnumerable<Job> Run(IServiceProvider serviceProvider)
{
Log.Info($"Running job {JobId}");
DateTime jobStart = DateTime.UtcNow;
Job[]? ret = null;
Log.Debug($"Running job {JobId}");
using IServiceScope scope = serviceProvider.CreateScope();
PgsqlContext context = scope.ServiceProvider.GetRequiredService<PgsqlContext>();
try
{
PgsqlContext context = scope.ServiceProvider.GetRequiredService<PgsqlContext>();
context.Attach(this);
this.state = JobState.Running;
context.SaveChanges();
ret = RunInternal(context).ToArray();
Job[] newJobs = RunInternal(context).ToArray();
this.state = JobState.Completed;
context.Jobs.AddRange(ret);
Log.Info($"Job {JobId} completed. Generated {ret.Length} new jobs.");
context.SaveChanges();
context.Jobs.AddRange(newJobs);
context.SaveChanges();
Log.Info($"Job {JobId} completed. Generated {newJobs.Length} new jobs.");
return newJobs;
}
catch (Exception e)
catch (DbUpdateException e)
{
if (e is not DbUpdateException)
{
this.state = JobState.Failed;
Log.Error($"Failed to run job {JobId}", e);
context.SaveChanges();
}
else
{
Log.Error($"Failed to update Database {JobId}", e);
}
this.state = JobState.Failed;
Log.Error($"Failed to run job {JobId}", e);
return [];
}
Log.Info($"Finished Job {JobId}! (took {DateTime.UtcNow.Subtract(jobStart).TotalMilliseconds}ms)");
return ret ?? [];
}
protected abstract IEnumerable<Job> RunInternal(PgsqlContext context);

View File

@ -9,7 +9,6 @@ public enum JobType : byte
MoveFileOrFolderJob = 3,
DownloadMangaCoverJob = 4,
RetrieveChaptersJob = 5,
UpdateChaptersDownloadedJob = 6,
MoveMangaLibraryJob = 7,
UpdateSingleChapterDownloadedJob = 8,
UpdateFilesDownloadedJob = 6,
MoveMangaLibraryJob = 7
}

View File

@ -1,6 +1,5 @@
using System.ComponentModel.DataAnnotations;
using API.Schema.Contexts;
using Microsoft.EntityFrameworkCore.Infrastructure;
namespace API.Schema.Jobs;
@ -23,8 +22,8 @@ public class MoveFileOrFolderJob : Job
/// <summary>
/// EF ONLY!!!
/// </summary>
internal MoveFileOrFolderJob(ILazyLoader lazyLoader, string jobId, string fromLocation, string toLocation, string? parentJobId)
: base(lazyLoader, jobId, JobType.MoveFileOrFolderJob, 0, parentJobId)
internal MoveFileOrFolderJob(string jobId, string fromLocation, string toLocation, string? parentJobId)
: base(jobId, JobType.MoveFileOrFolderJob, 0, parentJobId)
{
this.FromLocation = fromLocation;
this.ToLocation = toLocation;

View File

@ -1,7 +1,6 @@
using System.ComponentModel.DataAnnotations;
using API.Schema.Contexts;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Newtonsoft.Json;
namespace API.Schema.Jobs;
@ -9,15 +8,7 @@ namespace API.Schema.Jobs;
public class MoveMangaLibraryJob : Job
{
[StringLength(64)] [Required] public string MangaId { get; init; }
private Manga _manga = null!;
[JsonIgnore]
public Manga Manga
{
get => LazyLoader.Load(this, ref _manga);
init => _manga = value;
}
[JsonIgnore] public Manga Manga { get; init; } = null!;
[StringLength(64)] [Required] public string ToLibraryId { get; init; }
public LocalLibrary ToLibrary { get; init; } = null!;
@ -33,8 +24,8 @@ public class MoveMangaLibraryJob : Job
/// <summary>
/// EF ONLY!!!
/// </summary>
internal MoveMangaLibraryJob(ILazyLoader lazyLoader, string mangaId, string toLibraryId, string? parentJobId)
: base(lazyLoader, TokenGen.CreateToken(typeof(MoveMangaLibraryJob)), JobType.MoveMangaLibraryJob, 0, parentJobId)
internal MoveMangaLibraryJob(string mangaId, string toLibraryId, string? parentJobId)
: base(TokenGen.CreateToken(typeof(MoveMangaLibraryJob)), JobType.MoveMangaLibraryJob, 0, parentJobId)
{
this.MangaId = mangaId;
this.ToLibraryId = toLibraryId;
@ -42,6 +33,8 @@ public class MoveMangaLibraryJob : Job
protected override IEnumerable<Job> RunInternal(PgsqlContext context)
{
context.Attach(Manga);
context.Entry(Manga).Collection<Chapter>(m => m.Chapters).Load();
Dictionary<Chapter, string> oldPath = Manga.Chapters.ToDictionary(c => c, c => c.FullArchiveFilePath);
Manga.Library = ToLibrary;
try

View File

@ -1,7 +1,6 @@
using System.ComponentModel.DataAnnotations;
using API.Schema.Contexts;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Newtonsoft.Json;
namespace API.Schema.Jobs;
@ -9,15 +8,7 @@ namespace API.Schema.Jobs;
public class RetrieveChaptersJob : Job
{
[StringLength(64)] [Required] public string MangaId { get; init; }
private Manga _manga = null!;
[JsonIgnore]
public Manga Manga
{
get => LazyLoader.Load(this, ref _manga);
init => _manga = value;
}
[JsonIgnore] public Manga Manga { get; init; } = null!;
[StringLength(8)] [Required] public string Language { get; private set; }
public RetrieveChaptersJob(Manga manga, string language, ulong recurrenceMs, Job? parentJob = null, ICollection<Job>? dependsOnJobs = null)
@ -31,8 +22,8 @@ public class RetrieveChaptersJob : Job
/// <summary>
/// EF ONLY!!!
/// </summary>
internal RetrieveChaptersJob(ILazyLoader lazyLoader, string mangaId, string language, ulong recurrenceMs, string? parentJobId)
: base(lazyLoader, TokenGen.CreateToken(typeof(RetrieveChaptersJob)), JobType.RetrieveChaptersJob, recurrenceMs, parentJobId)
internal RetrieveChaptersJob(string mangaId, string language, ulong recurrenceMs, string? parentJobId)
: base(TokenGen.CreateToken(typeof(RetrieveChaptersJob)), JobType.RetrieveChaptersJob, recurrenceMs, parentJobId)
{
this.MangaId = mangaId;
this.Language = language;
@ -40,9 +31,10 @@ public class RetrieveChaptersJob : Job
protected override IEnumerable<Job> RunInternal(PgsqlContext context)
{
context.Attach(Manga);
// This gets all chapters that are not downloaded
Chapter[] allChapters = Manga.MangaConnector.GetChapters(Manga, Language);
Chapter[] newChapters = allChapters.Where(chapter => Manga.Chapters.Contains(chapter) == false).ToArray();
Chapter[] newChapters = allChapters.Where(chapter => context.Chapters.Contains(chapter) == false).ToArray();
Log.Info($"{newChapters.Length} new chapters.");
try

View File

@ -1,41 +0,0 @@
using System.ComponentModel.DataAnnotations;
using API.Schema.Contexts;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Newtonsoft.Json;
namespace API.Schema.Jobs;
public class UpdateChaptersDownloadedJob : Job
{
[StringLength(64)] [Required] public string MangaId { get; init; }
private Manga _manga = null!;
[JsonIgnore]
public Manga Manga
{
get => LazyLoader.Load(this, ref _manga);
init => _manga = value;
}
public UpdateChaptersDownloadedJob(Manga manga, ulong recurrenceMs, Job? parentJob = null, ICollection<Job>? dependsOnJobs = null)
: base(TokenGen.CreateToken(typeof(UpdateChaptersDownloadedJob)), JobType.UpdateChaptersDownloadedJob, recurrenceMs, parentJob, dependsOnJobs)
{
this.MangaId = manga.MangaId;
this.Manga = manga;
}
/// <summary>
/// EF ONLY!!!
/// </summary>
internal UpdateChaptersDownloadedJob(ILazyLoader lazyLoader, string mangaId, ulong recurrenceMs, string? parentJobId)
: base(lazyLoader, TokenGen.CreateToken(typeof(UpdateChaptersDownloadedJob)), JobType.UpdateChaptersDownloadedJob, recurrenceMs, parentJobId)
{
this.MangaId = mangaId;
}
protected override IEnumerable<Job> RunInternal(PgsqlContext context)
{
return Manga.Chapters.Select(c => new UpdateSingleChapterDownloadedJob(c, this));
}
}

View File

@ -0,0 +1,46 @@
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<Job>? dependsOnJobs = null)
: base(TokenGen.CreateToken(typeof(UpdateFilesDownloadedJob)), JobType.UpdateFilesDownloadedJob, recurrenceMs, parentJob, dependsOnJobs)
{
this.MangaId = manga.MangaId;
this.Manga = manga;
}
/// <summary>
/// EF ONLY!!!
/// </summary>
internal UpdateFilesDownloadedJob(string mangaId, ulong recurrenceMs, string? parentJobId)
: base(TokenGen.CreateToken(typeof(UpdateFilesDownloadedJob)), JobType.UpdateFilesDownloadedJob, recurrenceMs, parentJobId)
{
this.MangaId = mangaId;
}
protected override IEnumerable<Job> RunInternal(PgsqlContext context)
{
context.Attach(Manga);
context.Entry(Manga).Collection<Chapter>(m => m.Chapters).Load();
foreach (Chapter chapter in Manga.Chapters)
chapter.Downloaded = chapter.CheckDownloaded();
try
{
context.SaveChanges();
}
catch (DbUpdateException e)
{
Log.Error(e);
}
return [];
}
}

View File

@ -1,52 +0,0 @@
using System.ComponentModel.DataAnnotations;
using API.Schema.Contexts;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Newtonsoft.Json;
namespace API.Schema.Jobs;
public class UpdateSingleChapterDownloadedJob : Job
{
[StringLength(64)] [Required] public string ChapterId { get; init; }
private Chapter _chapter = null!;
[JsonIgnore]
public Chapter Chapter
{
get => LazyLoader.Load(this, ref _chapter);
init => _chapter = value;
}
public UpdateSingleChapterDownloadedJob(Chapter chapter, Job? parentJob = null, ICollection<Job>? dependsOnJobs = null)
: base(TokenGen.CreateToken(typeof(UpdateSingleChapterDownloadedJob)), JobType.UpdateSingleChapterDownloadedJob, 0, parentJob, dependsOnJobs)
{
this.ChapterId = chapter.ChapterId;
this.Chapter = chapter;
}
/// <summary>
/// EF ONLY!!!
/// </summary>
internal UpdateSingleChapterDownloadedJob(ILazyLoader lazyLoader, string chapterId, string? parentJobId)
: base(lazyLoader, TokenGen.CreateToken(typeof(UpdateSingleChapterDownloadedJob)), JobType.UpdateSingleChapterDownloadedJob, 0, parentJobId)
{
this.ChapterId = chapterId;
}
protected override IEnumerable<Job> RunInternal(PgsqlContext context)
{
Chapter.Downloaded = Chapter.CheckDownloaded();
try
{
context.SaveChanges();
}
catch (DbUpdateException e)
{
Log.Error(e);
}
return [];
}
}

View File

@ -4,7 +4,6 @@ using System.Runtime.InteropServices;
using System.Text;
using API.Schema.MangaConnectors;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Newtonsoft.Json;
using static System.IO.UnixFileMode;
@ -46,16 +45,8 @@ public class Manga
[JsonIgnore]
[NotMapped]
public string? FullDirectoryPath => Library is not null ? Path.Join(Library.BasePath, DirectoryName) : null;
[NotMapped] public ICollection<string> ChapterIds => Chapters.Select(c => c.ChapterId).ToList();
private readonly ILazyLoader _lazyLoader = null!;
private ICollection<Chapter> _chapters = null!;
[JsonIgnore]
public ICollection<Chapter> Chapters
{
get => _lazyLoader.Load(this, ref _chapters);
init => _chapters = value;
}
[JsonIgnore] public ICollection<Chapter> Chapters { get; internal set; } = [];
public Manga(string idOnConnector, string name, string description, string websiteUrl, string coverUrl, MangaReleaseStatus releaseStatus,
MangaConnector mangaConnector, ICollection<Author> authors, ICollection<MangaTag> mangaTags, ICollection<Link> links, ICollection<MangaAltTitle> altTitles,
@ -80,16 +71,14 @@ public class Manga
this.DirectoryName = CleanDirectoryName(name);
this.Year = year;
this.OriginalLanguage = originalLanguage;
this.Chapters = [];
}
/// <summary>
/// EF ONLY!!!
/// </summary>
public Manga(ILazyLoader lazyLoader, string mangaId, string idOnConnectorSite, string name, string description, string websiteUrl, string coverUrl, MangaReleaseStatus releaseStatus,
public Manga(string mangaId, string idOnConnectorSite, string name, string description, string websiteUrl, string coverUrl, MangaReleaseStatus releaseStatus,
string mangaConnectorName, string directoryName, float ignoreChaptersBefore, string? libraryId, uint? year, string? originalLanguage)
{
this._lazyLoader = lazyLoader;
this.MangaId = mangaId;
this.IdOnConnectorSite = idOnConnectorSite;
this.Name = name;
@ -108,9 +97,7 @@ public class Manga
public string CreatePublicationFolder()
{
string? publicationFolder = FullDirectoryPath;
if (publicationFolder is null)
throw new DirectoryNotFoundException("Publication folder not found");
string publicationFolder = FullDirectoryPath;
if(!Directory.Exists(publicationFolder))
Directory.CreateDirectory(publicationFolder);
if(RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
@ -144,7 +131,7 @@ public class Manga
StringBuilder sb = new ();
foreach (char c in name)
{
if (c >= 32 && c < 127 && ForbiddenCharsBelow127.Contains(c) == false)
if (c > 32 && c < 127 && ForbiddenCharsBelow127.Contains(c) == false)
sb.Append(c);
else if (c > 127 && c < 152 && IncludeCharsAbove127.Contains(c))
sb.Append(c);

View File

@ -5,7 +5,7 @@ namespace API;
public static class TokenGen
{
private const int MinimumLength = 16;
private const int MinimumLength = 32;
private const int MaximumLength = 64;
private const string Chars = "abcdefghijklmnopqrstuvwxyz0123456789";

View File

@ -12,16 +12,6 @@ namespace API;
public static class Tranga
{
// ReSharper disable once InconsistentNaming
private const string TRANGA =
"\n\n" +
" _______ v2\n" +
"|_ _|.----..---.-..-----..-----..---.-.\n" +
" | | | _|| _ || || _ || _ |\n" +
" |___| |__| |___._||__|__||___ ||___._|\n" +
" |_____| \n\n";
public static Thread NotificationSenderThread { get; } = new (NotificationSender);
public static Thread JobStarterThread { get; } = new (JobStarter);
private static readonly ILog Log = LogManager.GetLogger(typeof(Tranga));
@ -30,7 +20,6 @@ public static class Tranga
{
BasicConfigurator.Configure();
Log.Info("Logger Configured.");
Log.Info(TRANGA);
}
private static void NotificationSender(object? serviceProviderObj)
@ -93,10 +82,17 @@ public static class Tranga
Log.Error("Error sending notifications.", e);
}
}
private const string TRANGA =
"\n\n" +
" _______ \n" +
"|_ _|.----..---.-..-----..-----..---.-.\n" +
" | | | _|| _ || || _ || _ |\n" +
" |___| |__| |___._||__|__||___ ||___._|\n" +
" |_____| \n\n";
private static readonly Dictionary<Thread, Job> RunningJobs = new();
private static void JobStarter(object? serviceProviderObj)
{
Log.Info("JobStarter Thread running.");
if (serviceProviderObj is null)
{
Log.Error("serviceProviderObj is null");
@ -104,20 +100,23 @@ public static class Tranga
}
IServiceProvider serviceProvider = (IServiceProvider)serviceProviderObj;
using IServiceScope scope = serviceProvider.CreateScope();
PgsqlContext context = scope.ServiceProvider.GetRequiredService<PgsqlContext>();
PgsqlContext? context = scope.ServiceProvider.GetService<PgsqlContext>();
if (context is null)
{
Log.Error("PgsqlContext is null");
return;
}
Log.Info(TRANGA);
Log.Info("Loading Jobs");
context.Jobs.Load();
Log.Info("JobStarter Thread running.");
while (true)
{
Log.Debug("Starting Job-Cycle...");
DateTime cycleStart = DateTime.UtcNow;
Log.Debug("Loading Jobs...");
DateTime loadStart = DateTime.UtcNow;
context.Jobs.Load();
Log.Debug("Updating Entries...");
{
foreach (EntityEntry entityEntry in context.ChangeTracker.Entries().ToArray())
entityEntry.Reload();
Log.Debug($"Jobs Loaded! (took {DateTime.UtcNow.Subtract(loadStart).TotalMilliseconds}ms)");
//Update finished Jobs to new states
context.Jobs.Load();
List<Job> completedJobs = context.Jobs.Local.Where(j => j.state == JobState.Completed).ToList();
foreach (Job completedJob in completedJobs)
if (completedJob.RecurrenceMs <= 0)
@ -137,8 +136,6 @@ public static class Tranga
//Retrieve waiting and due Jobs
List<Job> runningJobs = context.Jobs.Local.Where(j => j.state == JobState.Running).ToList();
DateTime filterStart = DateTime.UtcNow;
Log.Debug("Filtering Jobs...");
List<MangaConnector> busyConnectors = GetBusyConnectors(runningJobs);
List<Job> waitingJobs = GetWaitingJobs(context.Jobs.Local.ToList());
@ -149,7 +146,6 @@ public static class Tranga
List<Job> jobsWithoutDownloading =
jobsWithoutMissingDependencies
.Where(j => j.JobType != JobType.DownloadSingleChapterJob)
.DistinctBy(j => j.JobType)
.ToList();
List<Job> firstChapterPerConnector =
jobsWithoutMissingDependencies
@ -167,8 +163,6 @@ public static class Tranga
.ToList();
List<Job> startJobs = jobsWithoutDownloading.Concat(firstChapterPerConnector).ToList();
Log.Debug($"Jobs Filtered! (took {DateTime.UtcNow.Subtract(filterStart).TotalMilliseconds}ms)");
//Start Jobs that are allowed to run (preconditions match)
foreach (Job job in startJobs)
@ -201,7 +195,6 @@ public static class Tranga
{
Log.Error("Failed saving Job changes.", e);
}
Log.Debug($"Job-Cycle over! (took {DateTime.UtcNow.Subtract(cycleStart).TotalMilliseconds}ms)");
Thread.Sleep(TrangaSettings.startNewJobTimeoutMs);
}
}