Fix some Jobs

This commit is contained in:
Glax 2025-01-09 01:34:03 +01:00
parent 7cf7eb85d2
commit 94adefa8e6
12 changed files with 278 additions and 204 deletions

View File

@ -203,7 +203,6 @@ public class JobController(PgsqlContext context) : Controller
Job? ret = context.Jobs.Find(id);
if (ret is null)
return NotFound();
ret.NextExecution = DateTime.UnixEpoch;
try
{
context.Update(ret);

View File

@ -47,10 +47,6 @@ namespace API.Migrations
.IsRequired()
.HasColumnType("text");
b.Property<string>("ChapterIds")
.IsRequired()
.HasColumnType("text");
b.Property<float>("ChapterNumber")
.HasColumnType("real");
@ -59,7 +55,6 @@ namespace API.Migrations
b.Property<string>("ParentMangaId")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<string>("Title")
@ -85,7 +80,7 @@ namespace API.Migrations
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.PrimitiveCollection<string[]>("DependsOnJobIds")
b.PrimitiveCollection<string[]>("DependsOnJobsIds")
.HasMaxLength(64)
.HasColumnType("text[]");
@ -98,9 +93,6 @@ namespace API.Migrations
b.Property<DateTime>("LastExecution")
.HasColumnType("timestamp with time zone");
b.Property<DateTime>("NextExecution")
.HasColumnType("timestamp with time zone");
b.Property<string>("ParentJobId")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
@ -115,6 +107,8 @@ namespace API.Migrations
b.HasIndex("JobId1");
b.HasIndex("ParentJobId");
b.ToTable("Jobs");
b.HasDiscriminator<byte>("JobType");
@ -154,9 +148,6 @@ namespace API.Migrations
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<string>("LinkIds")
.HasColumnType("text");
b.Property<string>("LinkProvider")
.IsRequired()
.HasColumnType("text");
@ -166,7 +157,6 @@ namespace API.Migrations
.HasColumnType("text");
b.Property<string>("MangaId")
.IsRequired()
.HasColumnType("character varying(64)");
b.HasKey("LinkId");
@ -182,14 +172,6 @@ namespace API.Migrations
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.PrimitiveCollection<string[]>("AltTitleIds")
.IsRequired()
.HasColumnType("text[]");
b.PrimitiveCollection<string[]>("AuthorIds")
.IsRequired()
.HasColumnType("text[]");
b.Property<string>("ConnectorId")
.IsRequired()
.HasMaxLength(64)
@ -213,24 +195,10 @@ namespace API.Migrations
b.Property<float>("IgnoreChapterBefore")
.HasColumnType("real");
b.Property<string>("LatestChapterAvailableId")
.HasColumnType("character varying(64)");
b.Property<string>("LatestChapterDownloadedId")
.HasColumnType("character varying(64)");
b.PrimitiveCollection<string[]>("LinkIds")
.IsRequired()
.HasColumnType("text[]");
b.Property<string>("MangaConnectorName")
b.Property<string>("MangaConnectorId")
.IsRequired()
.HasColumnType("character varying(32)");
b.Property<string>("MangaIds")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
@ -241,26 +209,16 @@ namespace API.Migrations
b.Property<byte>("ReleaseStatus")
.HasColumnType("smallint");
b.PrimitiveCollection<string[]>("TagIds")
.IsRequired()
.HasColumnType("text[]");
b.Property<string>("WebsiteUrl")
.IsRequired()
.HasColumnType("text");
b.Property<long>("year")
b.Property<long>("Year")
.HasColumnType("bigint");
b.HasKey("MangaId");
b.HasIndex("LatestChapterAvailableId")
.IsUnique();
b.HasIndex("LatestChapterDownloadedId")
.IsUnique();
b.HasIndex("MangaConnectorName");
b.HasIndex("MangaConnectorId");
b.ToTable("Manga");
});
@ -271,16 +229,12 @@ namespace API.Migrations
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<string>("AltTitleIds")
.HasColumnType("text");
b.Property<string>("Language")
.IsRequired()
.HasMaxLength(8)
.HasColumnType("character varying(8)");
b.Property<string>("MangaId")
.IsRequired()
.HasColumnType("character varying(64)");
b.Property<string>("Title")
@ -333,6 +287,9 @@ namespace API.Migrations
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<DateTime>("Date")
.HasColumnType("timestamp with time zone");
b.Property<string>("Message")
.IsRequired()
.HasColumnType("text");
@ -367,49 +324,34 @@ namespace API.Migrations
b.UseTphMappingStrategy();
});
modelBuilder.Entity("MangaAuthor", b =>
modelBuilder.Entity("AuthorManga", b =>
{
b.Property<string>("AuthorsAuthorId")
.HasColumnType("character varying(64)");
b.Property<string>("MangaId")
.HasColumnType("character varying(64)");
b.Property<string>("AuthorId")
.HasColumnType("character varying(64)");
b.HasKey("AuthorsAuthorId", "MangaId");
b.Property<string>("AuthorIds")
.HasColumnType("text");
b.HasIndex("MangaId");
b.Property<string>("MangaIds")
.HasColumnType("text");
b.HasKey("MangaId", "AuthorId");
b.HasIndex("AuthorId");
b.ToTable("MangaAuthor");
b.ToTable("AuthorManga");
});
modelBuilder.Entity("MangaTag", b =>
modelBuilder.Entity("MangaMangaTag", b =>
{
b.Property<string>("MangaId")
.HasColumnType("character varying(64)");
b.Property<string>("Tag")
b.Property<string>("TagsTag")
.HasColumnType("text");
b.Property<string>("MangaIds")
.IsRequired()
.HasColumnType("text");
b.HasKey("MangaId", "TagsTag");
b.Property<string>("TagIds")
.HasColumnType("text");
b.HasIndex("TagsTag");
b.HasKey("MangaId", "Tag");
b.HasIndex("MangaIds");
b.HasIndex("Tag");
b.ToTable("MangaTag");
b.ToTable("MangaMangaTag");
});
modelBuilder.Entity("API.Schema.Jobs.DownloadNewChaptersJob", b =>
@ -528,7 +470,7 @@ namespace API.Migrations
{
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
b.HasDiscriminator().HasValue("MangaLife");
b.HasDiscriminator().HasValue("Manga4Life");
});
modelBuilder.Entity("API.Schema.MangaConnectors.Manganato", b =>
@ -620,7 +562,7 @@ namespace API.Migrations
modelBuilder.Entity("API.Schema.Chapter", b =>
{
b.HasOne("API.Schema.Manga", "ParentManga")
.WithMany("Chapters")
.WithMany()
.HasForeignKey("ParentMangaId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
@ -633,58 +575,44 @@ namespace API.Migrations
b.HasOne("API.Schema.Jobs.Job", null)
.WithMany("DependsOnJobs")
.HasForeignKey("JobId1");
b.HasOne("API.Schema.Jobs.Job", "ParentJob")
.WithMany()
.HasForeignKey("ParentJobId");
b.Navigation("ParentJob");
});
modelBuilder.Entity("API.Schema.Link", b =>
{
b.HasOne("API.Schema.Manga", "Manga")
b.HasOne("API.Schema.Manga", null)
.WithMany("Links")
.HasForeignKey("MangaId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Manga");
.HasForeignKey("MangaId");
});
modelBuilder.Entity("API.Schema.Manga", b =>
{
b.HasOne("API.Schema.Chapter", "LatestChapterAvailable")
.WithOne()
.HasForeignKey("API.Schema.Manga", "LatestChapterAvailableId");
b.HasOne("API.Schema.Chapter", "LatestChapterDownloaded")
.WithOne()
.HasForeignKey("API.Schema.Manga", "LatestChapterDownloadedId");
b.HasOne("API.Schema.MangaConnectors.MangaConnector", "MangaConnector")
.WithMany("Mangas")
.HasForeignKey("MangaConnectorName")
.WithMany()
.HasForeignKey("MangaConnectorId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("LatestChapterAvailable");
b.Navigation("LatestChapterDownloaded");
b.Navigation("MangaConnector");
});
modelBuilder.Entity("API.Schema.MangaAltTitle", b =>
{
b.HasOne("API.Schema.Manga", "Manga")
b.HasOne("API.Schema.Manga", null)
.WithMany("AltTitles")
.HasForeignKey("MangaId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Manga");
.HasForeignKey("MangaId");
});
modelBuilder.Entity("MangaAuthor", b =>
modelBuilder.Entity("AuthorManga", b =>
{
b.HasOne("API.Schema.Author", null)
.WithMany()
.HasForeignKey("AuthorId")
.HasForeignKey("AuthorsAuthorId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
@ -695,7 +623,7 @@ namespace API.Migrations
.IsRequired();
});
modelBuilder.Entity("MangaTag", b =>
modelBuilder.Entity("MangaMangaTag", b =>
{
b.HasOne("API.Schema.Manga", null)
.WithMany()
@ -705,13 +633,7 @@ namespace API.Migrations
b.HasOne("API.Schema.MangaTag", null)
.WithMany()
.HasForeignKey("MangaIds")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("API.Schema.MangaTag", null)
.WithMany()
.HasForeignKey("Tag")
.HasForeignKey("TagsTag")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
@ -758,15 +680,8 @@ namespace API.Migrations
{
b.Navigation("AltTitles");
b.Navigation("Chapters");
b.Navigation("Links");
});
modelBuilder.Entity("API.Schema.MangaConnectors.MangaConnector", b =>
{
b.Navigation("Mangas");
});
#pragma warning restore 612, 618
}
}

View File

@ -0,0 +1,136 @@
using System.ComponentModel.DataAnnotations;
using System.IO.Compression;
using System.Runtime.InteropServices;
using API.MangaDownloadClients;
using API.Schema.MangaConnectors;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Binarization;
using static System.IO.UnixFileMode;
namespace API.Schema.Jobs;
public class DownloadMangaCoverJob(string chapterId, string? parentJobId = null, ICollection<string>? dependsOnJobsIds = null)
: Job(TokenGen.CreateToken(typeof(DownloadMangaCoverJob), 64), JobType.DownloadMangaCoverJob, 0, parentJobId, dependsOnJobsIds)
{
[MaxLength(64)]
public string ChapterId { get; init; } = chapterId;
public Chapter? Chapter { get; init; }
protected override IEnumerable<Job> RunInternal(PgsqlContext context)
{
MangaConnector connector = Chapter.ParentManga?.MangaConnector ?? context.MangaConnectors.Find(context.Manga.Find(Chapter.ParentMangaId)?.MangaId)!;
DownloadChapterImages(Chapter, connector);
return [];
}
private bool DownloadChapterImages(Chapter chapter, MangaConnector connector)
{
string[] imageUrls = connector.GetChapterImageUrls(Chapter);
string saveArchiveFilePath = chapter.GetArchiveFilePath();
//Check if Publication Directory already exists
string directoryPath = Path.GetDirectoryName(saveArchiveFilePath)!;
if (!Directory.Exists(directoryPath))
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
Directory.CreateDirectory(directoryPath,
UserRead | UserWrite | UserExecute | GroupRead | GroupWrite | GroupExecute );
else
Directory.CreateDirectory(directoryPath);
if (File.Exists(saveArchiveFilePath)) //Don't download twice. Redownload
File.Delete(saveArchiveFilePath);
//Create a temporary folder to store images
string tempFolder = Directory.CreateTempSubdirectory("trangatemp").FullName;
int chapterNum = 0;
//Download all Images to temporary Folder
if (imageUrls.Length == 0)
{
Directory.Delete(tempFolder, true);
return false;
}
foreach (string imageUrl in imageUrls)
{
string extension = imageUrl.Split('.')[^1].Split('?')[0];
string imagePath = Path.Join(tempFolder, $"{chapterNum++}.{extension}");
bool status = DownloadImage(imageUrl, imagePath);
if (status is false)
return false;
}
CopyCoverFromCacheToDownloadLocation();
File.WriteAllText(Path.Join(tempFolder, "ComicInfo.xml"), chapter.GetComicInfoXmlString());
//ZIP-it and ship-it
ZipFile.CreateFromDirectory(tempFolder, saveArchiveFilePath);
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
File.SetUnixFileMode(saveArchiveFilePath, UserRead | UserWrite | UserExecute | GroupRead | GroupWrite | GroupExecute | OtherRead | OtherExecute);
Directory.Delete(tempFolder, true); //Cleanup
return true;
}
private void ProcessImage(string imagePath)
{
if (!TrangaSettings.bwImages && TrangaSettings.compression == 100)
return;
DateTime start = DateTime.Now;
using Image image = Image.Load(imagePath);
File.Delete(imagePath);
if(TrangaSettings.bwImages)
image.Mutate(i => i.ApplyProcessor(new AdaptiveThresholdProcessor()));
image.SaveAsJpeg(imagePath, new JpegEncoder()
{
Quality = TrangaSettings.compression
});
}
private void CopyCoverFromCacheToDownloadLocation(int? retries = 1)
{
//Check if Publication already has a Folder and cover
string publicationFolder = Chapter.ParentManga.CreatePublicationFolder();
DirectoryInfo dirInfo = new (publicationFolder);
if (dirInfo.EnumerateFiles().Any(info => info.Name.Contains("cover", StringComparison.InvariantCultureIgnoreCase)))
{
return;
}
string? fileInCache = Chapter.ParentManga.CoverFileNameInCache;
if (fileInCache is null || !File.Exists(fileInCache))
{
if (retries > 0 && Chapter.ParentManga.CoverUrl is not null)
{
Chapter.ParentManga.SaveCoverImageToCache();
CopyCoverFromCacheToDownloadLocation(--retries);
}
return;
}
string newFilePath = Path.Join(publicationFolder, $"cover.{Path.GetFileName(fileInCache).Split('.')[^1]}" );
File.Copy(fileInCache, newFilePath, true);
if(RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
File.SetUnixFileMode(newFilePath, GroupRead | GroupWrite | UserRead | UserWrite);
}
private bool DownloadImage(string imageUrl, string savePath)
{
HttpDownloadClient downloadClient = new();
RequestResult requestResult = downloadClient.MakeRequest(imageUrl, RequestType.MangaImage);
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300)
return false;
if (requestResult.result == Stream.Null)
return false;
FileStream fs = new (savePath, FileMode.Create);
requestResult.result.CopyTo(fs);
fs.Close();
ProcessImage(savePath);
return true;
}
}

View File

@ -1,22 +1,22 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using API.Schema.MangaConnectors;
using Newtonsoft.Json;
namespace API.Schema.Jobs;
public class DownloadNewChaptersJob(ulong recurrenceMs, string mangaId, string? parentJobId = null, string[]? dependsOnJobIds = null)
: Job(TokenGen.CreateToken(typeof(DownloadNewChaptersJob), 64), JobType.DownloadNewChaptersJob, recurrenceMs, parentJobId, dependsOnJobIds)
public class DownloadNewChaptersJob(ulong recurrenceMs, string mangaId, string? parentJobId = null, ICollection<string>? dependsOnJobsIds = null)
: Job(TokenGen.CreateToken(typeof(DownloadNewChaptersJob), 64), JobType.DownloadNewChaptersJob, recurrenceMs, parentJobId, dependsOnJobsIds)
{
[MaxLength(64)]
public string MangaId { get; init; } = mangaId;
public virtual Manga Manga { get; init; }
public Manga? Manga { get; init; }
protected override IEnumerable<Job> RunInternal(PgsqlContext context)
{
MangaConnector connector = Manga.MangaConnector;
Chapter[] newChapters = connector.GetNewChapters(Manga);
Manga m = Manga ?? context.Manga.Find(MangaId)!;
MangaConnector connector = m.MangaConnector ?? context.MangaConnectors.Find(m.MangaConnectorId)!;
Chapter[] newChapters = connector.GetNewChapters(m);
context.Chapters.AddRangeAsync(newChapters).Wait();
context.SaveChangesAsync().Wait();
return newChapters.Select(chapter => new DownloadSingleChapterJob(chapter.ChapterId, this.JobId));
}
}

View File

@ -11,24 +11,25 @@ using static System.IO.UnixFileMode;
namespace API.Schema.Jobs;
public class DownloadSingleChapterJob(string chapterId, string? parentJobId = null, string[]? dependsOnJobIds = null)
: Job(TokenGen.CreateToken(typeof(DownloadSingleChapterJob), 64), JobType.DownloadSingleChapterJob, 0, parentJobId, dependsOnJobIds)
public class DownloadSingleChapterJob(string chapterId, string? parentJobId = null, ICollection<string>? dependsOnJobsIds = null)
: Job(TokenGen.CreateToken(typeof(DownloadSingleChapterJob), 64), JobType.DownloadSingleChapterJob, 0, parentJobId, dependsOnJobsIds)
{
[MaxLength(64)]
public string ChapterId { get; init; } = chapterId;
public virtual Chapter Chapter { get; init; }
public Chapter? Chapter { get; init; }
protected override IEnumerable<Job> RunInternal(PgsqlContext context)
{
MangaConnector connector = Chapter.ParentManga.MangaConnector;
DownloadChapterImages(Chapter);
Chapter c = Chapter ?? context.Chapters.Find(ChapterId)!;
Manga m = c.ParentManga ?? context.Manga.Find(c.ParentMangaId)!;
MangaConnector connector = m.MangaConnector ?? context.MangaConnectors.Find(m.MangaConnectorId)!;
DownloadChapterImages(c, connector, m);
return [];
}
private bool DownloadChapterImages(Chapter chapter)
private bool DownloadChapterImages(Chapter chapter, MangaConnector connector, Manga manga)
{
MangaConnector connector = Chapter.ParentManga.MangaConnector;
string[] imageUrls = connector.GetChapterImageUrls(Chapter);
string[] imageUrls = connector.GetChapterImageUrls(chapter);
string saveArchiveFilePath = chapter.GetArchiveFilePath();
//Check if Publication Directory already exists
@ -63,7 +64,7 @@ public class DownloadSingleChapterJob(string chapterId, string? parentJobId = nu
return false;
}
CopyCoverFromCacheToDownloadLocation();
CopyCoverFromCacheToDownloadLocation(manga);
File.WriteAllText(Path.Join(tempFolder, "ComicInfo.xml"), chapter.GetComicInfoXmlString());
@ -91,23 +92,23 @@ public class DownloadSingleChapterJob(string chapterId, string? parentJobId = nu
});
}
private void CopyCoverFromCacheToDownloadLocation(int? retries = 1)
private void CopyCoverFromCacheToDownloadLocation(Manga manga, int? retries = 1)
{
//Check if Publication already has a Folder and cover
string publicationFolder = Chapter.ParentManga.CreatePublicationFolder();
string publicationFolder = manga.CreatePublicationFolder();
DirectoryInfo dirInfo = new (publicationFolder);
if (dirInfo.EnumerateFiles().Any(info => info.Name.Contains("cover", StringComparison.InvariantCultureIgnoreCase)))
{
return;
}
string? fileInCache = Chapter.ParentManga.CoverFileNameInCache;
string? fileInCache = manga.CoverFileNameInCache;
if (fileInCache is null || !File.Exists(fileInCache))
{
if (retries > 0 && Chapter.ParentManga.CoverUrl is not null)
if (retries > 0)
{
Chapter.ParentManga.SaveCoverImageToCache();
CopyCoverFromCacheToDownloadLocation(--retries);
manga.SaveCoverImageToCache();
CopyCoverFromCacheToDownloadLocation(manga, --retries);
}
return;

View File

@ -12,28 +12,35 @@ public abstract class Job
public string JobId { get; init; }
[MaxLength(64)]
public string? ParentJobId { get; internal set; }
internal virtual Job ParentJob { get; }
public string? ParentJobId { get; init; }
public Job? ParentJob { get; init; }
[MaxLength(64)]
public string[]? DependsOnJobIds { get; init; }
public virtual Job[] DependsOnJobs { get; init; }
public ICollection<string>? DependsOnJobsIds { get; init; }
public ICollection<Job>? DependsOnJobs { get; init; }
public JobType JobType { get; init; }
public ulong RecurrenceMs { get; set; }
public DateTime LastExecution { get; internal set; } = DateTime.UnixEpoch;
public DateTime NextExecution { get; internal set; }
[NotMapped]
public DateTime NextExecution => LastExecution.AddMilliseconds(RecurrenceMs);
public JobState state { get; internal set; } = JobState.Waiting;
public Job(string jobId, JobType jobType, ulong recurrenceMs, string? parentJobId = null,
string[]? dependsOnJobIds = null)
public Job(string jobId, JobType jobType, ulong recurrenceMs, Job? parentJob = null, ICollection<Job>? dependsOnJobs = null)
: this(jobId, jobType, recurrenceMs, parentJob?.JobId, dependsOnJobs?.Select(j => j.JobId).ToList())
{
this.ParentJob = parentJob;
this.DependsOnJobs = dependsOnJobs;
}
public Job(string jobId, JobType jobType, ulong recurrenceMs, string? parentJobId = null, ICollection<string>? dependsOnJobsIds = null)
{
JobId = jobId;
ParentJobId = parentJobId;
DependsOnJobIds = dependsOnJobIds;
DependsOnJobsIds = dependsOnJobsIds;
JobType = jobType;
RecurrenceMs = recurrenceMs;
NextExecution = LastExecution.AddMilliseconds(RecurrenceMs);
}
public IEnumerable<Job> Run(PgsqlContext context)

View File

@ -6,5 +6,6 @@ public enum JobType : byte
DownloadSingleChapterJob = 0,
DownloadNewChaptersJob = 1,
UpdateMetaDataJob = 2,
MoveFileOrFolderJob = 3
MoveFileOrFolderJob = 3,
DownloadMangaCoverJob = 4
}

View File

@ -1,7 +1,7 @@
namespace API.Schema.Jobs;
public class MoveFileOrFolderJob(string fromLocation, string toLocation, string? parentJobId = null, string[]? dependsOnJobIds = null)
: Job(TokenGen.CreateToken(typeof(MoveFileOrFolderJob), 64), JobType.MoveFileOrFolderJob, 0, parentJobId, dependsOnJobIds)
public class MoveFileOrFolderJob(string fromLocation, string toLocation, string? parentJobId = null, ICollection<string>? dependsOnJobsIds = null)
: Job(TokenGen.CreateToken(typeof(MoveFileOrFolderJob), 64), JobType.MoveFileOrFolderJob, 0, parentJobId, dependsOnJobsIds)
{
public string FromLocation { get; init; } = fromLocation;
public string ToLocation { get; init; } = toLocation;

View File

@ -2,8 +2,8 @@
namespace API.Schema.Jobs;
public class UpdateMetadataJob(ulong recurrenceMs, string mangaId, string? parentJobId = null, string[]? dependsOnJobIds = null)
: Job(TokenGen.CreateToken(typeof(UpdateMetadataJob), 64), JobType.UpdateMetaDataJob, recurrenceMs, parentJobId, dependsOnJobIds)
public class UpdateMetadataJob(ulong recurrenceMs, string mangaId, string? parentJobId = null, ICollection<string>? dependsOnJobsIds = null)
: Job(TokenGen.CreateToken(typeof(UpdateMetadataJob), 64), JobType.UpdateMetaDataJob, recurrenceMs, parentJobId, dependsOnJobsIds)
{
[MaxLength(64)]
public string MangaId { get; init; } = mangaId;

View File

@ -11,58 +11,64 @@ using static System.IO.UnixFileMode;
namespace API.Schema;
[PrimaryKey("MangaId")]
public class Manga(
string connectorId,
string name,
string description,
string websiteUrl,
string coverUrl,
string? coverFileNameInCache,
uint year,
string? originalLanguage,
MangaReleaseStatus releaseStatus,
float ignoreChapterBefore,
MangaConnector mangaConnector,
ICollection<Author> authors,
ICollection<MangaTag> tags,
ICollection<Link> links,
ICollection<MangaAltTitle> altTitles)
public class Manga
{
[MaxLength(64)]
public string MangaId { get; init; } = TokenGen.CreateToken(typeof(Manga), 64);
[MaxLength(64)]
public string ConnectorId { get; init; } = connectorId;
public string ConnectorId { get; init; }
public string Name { get; internal set; } = name;
public string Description { get; internal set; } = description;
public string WebsiteUrl { get; internal set; } = websiteUrl;
public string CoverUrl { get; internal set; } = coverUrl;
public string? CoverFileNameInCache { get; internal set; } = coverFileNameInCache;
public uint year { get; internal set; } = year;
public string? OriginalLanguage { get; internal set; } = originalLanguage;
public MangaReleaseStatus ReleaseStatus { get; internal set; } = releaseStatus;
public string FolderName { get; private set; } = BuildFolderName(name);
public float IgnoreChapterBefore { get; internal set; } = ignoreChapterBefore;
public string Name { get; internal set; }
public string Description { get; internal set; }
public string WebsiteUrl { get; internal set; }
public string CoverUrl { get; internal set; }
public string? CoverFileNameInCache { get; internal set; }
public uint Year { get; internal set; }
public string? OriginalLanguage { get; internal set; }
public MangaReleaseStatus ReleaseStatus { get; internal set; }
public string FolderName { get; private set; }
public float IgnoreChapterBefore { get; internal set; }
[ForeignKey("MangaConnectorId")]
public MangaConnector MangaConnector { get; private set; } = mangaConnector;
public string MangaConnectorId { get; private set; }
public MangaConnector? MangaConnector { get; private set; }
public ICollection<Author> Authors { get; internal set; } = authors;
public ICollection<Author>? Authors { get; internal set; }
public ICollection<MangaTag> Tags { get; internal set; } = tags;
public ICollection<MangaTag>? Tags { get; internal set; }
public ICollection<Link> Links { get; internal set; } = links;
public ICollection<Link>? Links { get; internal set; }
public ICollection<MangaAltTitle> AltTitles { get; internal set; } = altTitles;
public ICollection<MangaAltTitle>? AltTitles { get; internal set; }
public Manga(string connectorId, string name, string description, string websiteUrl, string coverUrl,
string? coverFileNameInCache,
uint year, string? originalLanguage, MangaReleaseStatus releaseStatus, float ignoreChapterBefore)
string? coverFileNameInCache, uint year, string? originalLanguage, MangaReleaseStatus releaseStatus,
float ignoreChapterBefore, MangaConnector mangaConnector, ICollection<Author> authors,
ICollection<MangaTag> tags, ICollection<Link> links, ICollection<MangaAltTitle> altTitles)
: this(connectorId, name, description, websiteUrl, coverUrl, coverFileNameInCache, year, originalLanguage,
releaseStatus,
ignoreChapterBefore, null, null, null, null, null)
releaseStatus, ignoreChapterBefore, mangaConnector.Name)
{
this.Authors = authors;
this.Tags = tags;
this.Links = links;
this.AltTitles = altTitles;
}
public Manga(string connectorId, string name, string description, string websiteUrl, string coverUrl,
string? coverFileNameInCache, uint year, string? originalLanguage, MangaReleaseStatus releaseStatus,
float ignoreChapterBefore, string mangaConnectorId)
{
ConnectorId = connectorId;
Name = name;
Description = description;
WebsiteUrl = websiteUrl;
CoverUrl = coverUrl;
CoverFileNameInCache = coverFileNameInCache;
Year = year;
OriginalLanguage = originalLanguage;
ReleaseStatus = releaseStatus;
IgnoreChapterBefore = ignoreChapterBefore;
MangaConnectorId = mangaConnectorId;
FolderName = BuildFolderName(name);
}
public MoveFileOrFolderJob UpdateFolderName(string downloadLocation, string newName)
@ -75,7 +81,7 @@ public class Manga(
internal void UpdateWithInfo(Manga other)
{
this.Name = other.Name;
this.year = other.year;
this.Year = other.Year;
this.Description = other.Description;
this.CoverUrl = other.CoverUrl;
this.OriginalLanguage = other.OriginalLanguage;

View File

@ -51,6 +51,12 @@ public class PgsqlContext(DbContextOptions<PgsqlContext> options) : DbContext(op
.HasValue<DownloadNewChaptersJob>(JobType.DownloadNewChaptersJob)
.HasValue<DownloadSingleChapterJob>(JobType.DownloadSingleChapterJob)
.HasValue<UpdateMetadataJob>(JobType.UpdateMetaDataJob);
modelBuilder.Entity<Job>()
.HasOne<Job>(j => j.ParentJob)
.WithMany()
.HasForeignKey(j => j.ParentJobId);
modelBuilder.Entity<Job>()
.HasMany<Job>(j => j.DependsOnJobs);
modelBuilder.Entity<DownloadNewChaptersJob>()
.Navigation(dncj => dncj.Manga)
.AutoInclude();
@ -62,7 +68,9 @@ public class PgsqlContext(DbContextOptions<PgsqlContext> options) : DbContext(op
.AutoInclude();
modelBuilder.Entity<Manga>()
.HasOne<MangaConnector>(m => m.MangaConnector);
.HasOne<MangaConnector>(m => m.MangaConnector)
.WithMany()
.HasForeignKey(m => m.MangaConnectorId);
modelBuilder.Entity<Manga>()
.Navigation(m => m.MangaConnector)
.AutoInclude();
@ -92,7 +100,8 @@ public class PgsqlContext(DbContextOptions<PgsqlContext> options) : DbContext(op
.AutoInclude();
modelBuilder.Entity<Chapter>()
.HasOne<Manga>(c => c.ParentManga)
.WithMany();
.WithMany()
.HasForeignKey(c => c.ParentMangaId);
modelBuilder.Entity<Chapter>()
.Navigation(c => c.ParentManga)
.AutoInclude();

View File

@ -67,7 +67,7 @@ public static class Tranga
{
List<Job> completedJobs = context.Jobs.Where(j => j.state == JobState.Completed).ToList();
foreach (Job job in completedJobs)
if(job.RecurrenceMs < 1)
if(job.RecurrenceMs <= 0)
context.Jobs.Remove(job);
else
{
@ -76,7 +76,7 @@ public static class Tranga
context.Jobs.Update(job);
}
List<Job> runJobs = context.Jobs.Where(j => j.state <= JobState.Running && j.NextExecution < DateTime.UtcNow).ToList();
List<Job> runJobs = context.Jobs.Where(j => j.state <= JobState.Running).ToList().Where(j => j.NextExecution < DateTime.UtcNow).ToList();
foreach (Job job in runJobs)
{
Thread t = new (() =>