Compare commits

..

11 Commits

17 changed files with 159 additions and 57 deletions

View File

@ -7,12 +7,12 @@ public class DownloadChapter : Job
{ {
public Chapter chapter { get; init; } public Chapter chapter { get; init; }
public DownloadChapter(GlobalBase clone, MangaConnector connector, Chapter chapter, DateTime lastExecution, string? parentJobId = null) : base(clone, connector, lastExecution, parentJobId: parentJobId) public DownloadChapter(GlobalBase clone, MangaConnector connector, Chapter chapter, DateTime lastExecution, string? parentJobId = null) : base(clone, JobType.DownloadChapterJob, connector, lastExecution, parentJobId: parentJobId)
{ {
this.chapter = chapter; this.chapter = chapter;
} }
public DownloadChapter(GlobalBase clone, MangaConnector connector, Chapter chapter, string? parentJobId = null) : base(clone, connector, parentJobId: parentJobId) public DownloadChapter(GlobalBase clone, MangaConnector connector, Chapter chapter, string? parentJobId = null) : base(clone, JobType.DownloadChapterJob, connector, parentJobId: parentJobId)
{ {
this.chapter = chapter; this.chapter = chapter;
} }
@ -27,7 +27,7 @@ public class DownloadChapter : Job
return $"{id} Chapter: {chapter}"; return $"{id} Chapter: {chapter}";
} }
protected override IEnumerable<Job> ExecuteReturnSubTasksInternal() protected override IEnumerable<Job> ExecuteReturnSubTasksInternal(JobBoss jobBoss)
{ {
Task downloadTask = new(delegate Task downloadTask = new(delegate
{ {

View File

@ -8,14 +8,14 @@ public class DownloadNewChapters : Job
public string translatedLanguage { get; init; } public string translatedLanguage { get; init; }
public DownloadNewChapters(GlobalBase clone, MangaConnector connector, Manga manga, DateTime lastExecution, public DownloadNewChapters(GlobalBase clone, MangaConnector connector, Manga manga, DateTime lastExecution,
bool recurring = false, TimeSpan? recurrence = null, string? parentJobId = null, string translatedLanguage = "en") : base(clone, connector, lastExecution, recurring, bool recurring = false, TimeSpan? recurrence = null, string? parentJobId = null, string translatedLanguage = "en") : base(clone, JobType.DownloadNewChaptersJob, connector, lastExecution, recurring,
recurrence, parentJobId) recurrence, parentJobId)
{ {
this.manga = manga; this.manga = manga;
this.translatedLanguage = translatedLanguage; this.translatedLanguage = translatedLanguage;
} }
public DownloadNewChapters(GlobalBase clone, MangaConnector connector, Manga manga, bool recurring = false, TimeSpan? recurrence = null, string? parentJobId = null, string translatedLanguage = "en") : base (clone, connector, recurring, recurrence, parentJobId) public DownloadNewChapters(GlobalBase clone, MangaConnector connector, Manga manga, bool recurring = false, TimeSpan? recurrence = null, string? parentJobId = null, string translatedLanguage = "en") : base (clone, JobType.DownloadNewChaptersJob, connector, recurring, recurrence, parentJobId)
{ {
this.manga = manga; this.manga = manga;
this.translatedLanguage = translatedLanguage; this.translatedLanguage = translatedLanguage;
@ -31,7 +31,7 @@ public class DownloadNewChapters : Job
return $"{id} Manga: {manga}"; return $"{id} Manga: {manga}";
} }
protected override IEnumerable<Job> ExecuteReturnSubTasksInternal() protected override IEnumerable<Job> ExecuteReturnSubTasksInternal(JobBoss jobBoss)
{ {
manga.SaveSeriesInfoJson(settings.downloadLocation); manga.SaveSeriesInfoJson(settings.downloadLocation);
Chapter[] chapters = mangaConnector.GetNewChapters(manga, this.translatedLanguage); Chapter[] chapters = mangaConnector.GetNewChapters(manga, this.translatedLanguage);
@ -43,6 +43,8 @@ public class DownloadNewChapters : Job
DownloadChapter downloadChapterJob = new(this, this.mangaConnector, chapter, parentJobId: this.id); DownloadChapter downloadChapterJob = new(this, this.mangaConnector, chapter, parentJobId: this.id);
jobs.Add(downloadChapterJob); jobs.Add(downloadChapterJob);
} }
UpdateMetadata updateMetadataJob = new(this, this.mangaConnector, this.manga, parentJobId: this.id);
jobs.Add(updateMetadataJob);
progressToken.Complete(); progressToken.Complete();
return jobs; return jobs;
} }

View File

@ -13,9 +13,13 @@ public abstract class Job : GlobalBase
public string id => GetId(); public string id => GetId();
internal IEnumerable<Job>? subJobs { get; private set; } internal IEnumerable<Job>? subJobs { get; private set; }
public string? parentJobId { get; init; } public string? parentJobId { get; init; }
public enum JobType : byte { DownloadChapterJob, DownloadNewChaptersJob, UpdateMetaDataJob }
internal Job(GlobalBase clone, MangaConnector connector, bool recurring = false, TimeSpan? recurrenceTime = null, string? parentJobId = null) : base(clone) public JobType jobType;
internal Job(GlobalBase clone, JobType jobType, MangaConnector connector, bool recurring = false, TimeSpan? recurrenceTime = null, string? parentJobId = null) : base(clone)
{ {
this.jobType = jobType;
this.mangaConnector = connector; this.mangaConnector = connector;
this.progressToken = new ProgressToken(0); this.progressToken = new ProgressToken(0);
this.recurring = recurring; this.recurring = recurring;
@ -27,9 +31,10 @@ public abstract class Job : GlobalBase
this.parentJobId = parentJobId; this.parentJobId = parentJobId;
} }
internal Job(GlobalBase clone, MangaConnector connector, DateTime lastExecution, bool recurring = false, internal Job(GlobalBase clone, JobType jobType, MangaConnector connector, DateTime lastExecution, bool recurring = false,
TimeSpan? recurrenceTime = null, string? parentJobId = null) : base(clone) TimeSpan? recurrenceTime = null, string? parentJobId = null) : base(clone)
{ {
this.jobType = jobType;
this.mangaConnector = connector; this.mangaConnector = connector;
this.progressToken = new ProgressToken(0); this.progressToken = new ProgressToken(0);
this.recurring = recurring; this.recurring = recurring;
@ -81,13 +86,13 @@ public abstract class Job : GlobalBase
subJob.Cancel(); subJob.Cancel();
} }
public IEnumerable<Job> ExecuteReturnSubTasks() public IEnumerable<Job> ExecuteReturnSubTasks(JobBoss jobBoss)
{ {
progressToken.Start(); progressToken.Start();
subJobs = ExecuteReturnSubTasksInternal(); subJobs = ExecuteReturnSubTasksInternal(jobBoss);
lastExecution = DateTime.Now; lastExecution = DateTime.Now;
return subJobs; return subJobs;
} }
protected abstract IEnumerable<Job> ExecuteReturnSubTasksInternal(); protected abstract IEnumerable<Job> ExecuteReturnSubTasksInternal(JobBoss jobBoss);
} }

View File

@ -253,7 +253,7 @@ public class JobBoss : GlobalBase
Log($"Next job in {jobs.MinBy(job => job.nextExecution)?.nextExecution.Subtract(DateTime.Now)} {jobs.MinBy(job => job.nextExecution)?.id}"); Log($"Next job in {jobs.MinBy(job => job.nextExecution)?.nextExecution.Subtract(DateTime.Now)} {jobs.MinBy(job => job.nextExecution)?.id}");
}else if (queueHead.progressToken.state is ProgressToken.State.Standby) }else if (queueHead.progressToken.state is ProgressToken.State.Standby)
{ {
Job[] subJobs = jobQueue.Peek().ExecuteReturnSubTasks().ToArray(); Job[] subJobs = jobQueue.Peek().ExecuteReturnSubTasks(this).ToArray();
AddJobs(subJobs); AddJobs(subJobs);
AddJobsToQueue(subJobs); AddJobsToQueue(subJobs);
}else if (queueHead.progressToken.state is ProgressToken.State.Running && DateTime.Now.Subtract(queueHead.progressToken.lastUpdate) > TimeSpan.FromMinutes(5)) }else if (queueHead.progressToken.state is ProgressToken.State.Running && DateTime.Now.Subtract(queueHead.progressToken.lastUpdate) > TimeSpan.FromMinutes(5))

View File

@ -23,7 +23,20 @@ public class JobJsonConverter : JsonConverter
public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{ {
JObject jo = JObject.Load(reader); JObject jo = JObject.Load(reader);
if (jo.ContainsKey("manga"))//DownloadNewChapters
if (jo.ContainsKey("jobType") && jo["jobType"]!.Value<byte>() == (byte)Job.JobType.UpdateMetaDataJob)
{
return new UpdateMetadata(this._clone,
jo.GetValue("mangaConnector")!.ToObject<MangaConnector>(JsonSerializer.Create(new JsonSerializerSettings()
{
Converters =
{
this._mangaConnectorJsonConverter
}
}))!,
jo.GetValue("manga")!.ToObject<Manga>(),
jo.GetValue("parentJobId")!.Value<string?>());
}else if ((jo.ContainsKey("jobType") && jo["jobType"]!.Value<byte>() == (byte)Job.JobType.DownloadNewChaptersJob) || jo.ContainsKey("translatedLanguage"))//TODO change to jobType
{ {
return new DownloadNewChapters(this._clone, return new DownloadNewChapters(this._clone,
jo.GetValue("mangaConnector")!.ToObject<MangaConnector>(JsonSerializer.Create(new JsonSerializerSettings() jo.GetValue("mangaConnector")!.ToObject<MangaConnector>(JsonSerializer.Create(new JsonSerializerSettings()
@ -38,9 +51,7 @@ public class JobJsonConverter : JsonConverter
jo.GetValue("recurring")!.Value<bool>(), jo.GetValue("recurring")!.Value<bool>(),
jo.GetValue("recurrenceTime")!.ToObject<TimeSpan?>(), jo.GetValue("recurrenceTime")!.ToObject<TimeSpan?>(),
jo.GetValue("parentJobId")!.Value<string?>()); jo.GetValue("parentJobId")!.Value<string?>());
} }else if ((jo.ContainsKey("jobType") && jo["jobType"]!.Value<byte>() == (byte)Job.JobType.DownloadChapterJob) || jo.ContainsKey("chapter"))//TODO change to jobType
if (jo.ContainsKey("chapter"))//DownloadChapter
{ {
return new DownloadChapter(this._clone, return new DownloadChapter(this._clone,
jo.GetValue("mangaConnector")!.ToObject<MangaConnector>(JsonSerializer.Create(new JsonSerializerSettings() jo.GetValue("mangaConnector")!.ToObject<MangaConnector>(JsonSerializer.Create(new JsonSerializerSettings()

View File

@ -4,19 +4,11 @@ namespace Tranga.Jobs;
public class UpdateMetadata : Job public class UpdateMetadata : Job
{ {
private Manga manga { get; set; } public Manga manga { get; set; }
private JobBoss jobBoss { get; init; }
public UpdateMetadata(GlobalBase clone, MangaConnector connector, Manga manga, JobBoss jobBoss, DateTime lastExecution, string? parentJobId = null) : base(clone, connector, lastExecution, parentJobId: parentJobId)
{
this.manga = manga;
this.jobBoss = jobBoss;
}
public UpdateMetadata(GlobalBase clone, MangaConnector connector, Manga manga, JobBoss jobBoss, string? parentJobId = null) : base(clone, connector, parentJobId: parentJobId) public UpdateMetadata(GlobalBase clone, MangaConnector connector, Manga manga, string? parentJobId = null) : base(clone, JobType.UpdateMetaDataJob, connector, parentJobId: parentJobId)
{ {
this.manga = manga; this.manga = manga;
this.jobBoss = jobBoss;
} }
protected override string GetId() protected override string GetId()
@ -29,35 +21,35 @@ public class UpdateMetadata : Job
return $"{id} Manga: {manga}"; return $"{id} Manga: {manga}";
} }
protected override IEnumerable<Job> ExecuteReturnSubTasksInternal() protected override IEnumerable<Job> ExecuteReturnSubTasksInternal(JobBoss jobBoss)
{ {
if(manga.websiteUrl is null) Manga? possibleUpdatedManga = mangaConnector.GetMangaFromId(manga.publicationId);
{
Log($"Legacy manga {manga}");
return Array.Empty<Job>();
}
if (parentJobId is null)
{
Log($"Missing parentJob {this}");
return Array.Empty<Job>();
}
Manga? possibleUpdatedManga = mangaConnector.GetMangaFromUrl(manga.websiteUrl);
if (possibleUpdatedManga is { } updatedManga) if (possibleUpdatedManga is { } updatedManga)
{ {
if(updatedManga.Equals(this.manga))
return Array.Empty<Job>();
cachedPublications.Remove(this.manga); cachedPublications.Remove(this.manga);
this.manga = updatedManga; this.manga = updatedManga;
cachedPublications.Add(updatedManga); cachedPublications.Add(updatedManga);
this.manga.SaveSeriesInfoJson(settings.downloadLocation, true); this.manga.SaveSeriesInfoJson(settings.downloadLocation, true);
DownloadNewChapters dncJob = this.jobBoss.GetJobById(this.parentJobId) as DownloadNewChapters ?? if (parentJobId is not null)
throw new Exception("Jobtype has to be DownloadNewChapters"); {
dncJob.manga = updatedManga;
DownloadNewChapters dncJob = jobBoss.GetJobById(this.parentJobId) as DownloadNewChapters ??
throw new Exception("Jobtype has to be DownloadNewChapters");
dncJob.manga = updatedManga;
}
this.progressToken.Complete();
} }
else else
{ {
Log($"Could not find Manga {manga}"); Log($"Could not find Manga {manga}");
this.progressToken.Cancel();
return Array.Empty<Job>(); return Array.Empty<Job>();
} }
this.progressToken.Cancel();
return Array.Empty<Job>(); return Array.Empty<Job>();
} }
} }

View File

@ -41,15 +41,33 @@ public abstract class LibraryConnector : GlobalBase
Method = HttpMethod.Get, Method = HttpMethod.Get,
RequestUri = new Uri(url) RequestUri = new Uri(url)
}; };
HttpResponseMessage response = client.Send(requestMessage); try
logger?.WriteLine("LibraryManager.NetClient", $"GET {url} -> {(int)response.StatusCode}: {response.ReasonPhrase}"); {
if(response.StatusCode is HttpStatusCode.Unauthorized && response.RequestMessage!.RequestUri!.AbsoluteUri != url) HttpResponseMessage response = client.Send(requestMessage);
return MakeRequest(response.RequestMessage!.RequestUri!.AbsoluteUri, authScheme, auth, logger); logger?.WriteLine("LibraryManager.NetClient",
else if (response.IsSuccessStatusCode) $"GET {url} -> {(int)response.StatusCode}: {response.ReasonPhrase}");
return response.Content.ReadAsStream();
else if (response.StatusCode is HttpStatusCode.Unauthorized &&
response.RequestMessage!.RequestUri!.AbsoluteUri != url)
return MakeRequest(response.RequestMessage!.RequestUri!.AbsoluteUri, authScheme, auth, logger);
else if (response.IsSuccessStatusCode)
return response.Content.ReadAsStream();
else
return Stream.Null;
}
catch (Exception e)
{
switch (e)
{
case HttpRequestException:
logger?.WriteLine("LibraryManager.NetClient", $"Failed to make Request:\n\r{e}\n\rContinuing.");
break;
default:
throw;
}
return Stream.Null; return Stream.Null;
}
} }
public static bool MakePost(string url, string authScheme, string auth, Logger? logger) public static bool MakePost(string url, string authScheme, string auth, Logger? logger)

View File

@ -43,7 +43,6 @@ public struct Manga
public float ignoreChaptersBelow { get; set; } public float ignoreChaptersBelow { get; set; }
public float latestChapterDownloaded { get; set; } public float latestChapterDownloaded { get; set; }
public float latestChapterAvailable { get; set; } public float latestChapterAvailable { get; set; }
public string? websiteUrl { get; }
private static readonly Regex LegalCharacters = new (@"[A-Z]*[a-z]*[0-9]* *\.*-*,*'*\'*\)*\(*~*!*"); private static readonly Regex LegalCharacters = new (@"[A-Z]*[a-z]*[0-9]* *\.*-*,*'*\'*\)*\(*~*!*");
@ -71,7 +70,19 @@ public struct Manga
this.latestChapterDownloaded = 0; this.latestChapterDownloaded = 0;
this.latestChapterAvailable = 0; this.latestChapterAvailable = 0;
this.releaseStatus = releaseStatus; this.releaseStatus = releaseStatus;
this.websiteUrl = websiteUrl; }
public override bool Equals(object? obj)
{
if (obj is not Manga compareManga)
return false;
return this.description == compareManga.description &&
this.year == compareManga.year &&
this.status == compareManga.status &&
this.releaseStatus == compareManga.releaseStatus &&
this.sortName == compareManga.sortName &&
this.latestChapterAvailable.Equals(compareManga.latestChapterAvailable) &&
this.tags.Equals(compareManga.tags);
} }
public override string ToString() public override string ToString()

View File

@ -36,6 +36,11 @@ public class Bato : MangaConnector
return publications; return publications;
} }
public override Manga? GetMangaFromId(string publicationId)
{
return GetMangaFromUrl($"https://bato.to/title/{publicationId}");
}
public override Manga? GetMangaFromUrl(string url) public override Manga? GetMangaFromUrl(string url)
{ {
DownloadClient.RequestResult requestResult = DownloadClient.RequestResult requestResult =

View File

@ -37,6 +37,8 @@ public abstract class MangaConnector : GlobalBase
public abstract Manga[] GetManga(string publicationTitle = ""); public abstract Manga[] GetManga(string publicationTitle = "");
public abstract Manga? GetMangaFromUrl(string url); public abstract Manga? GetMangaFromUrl(string url);
public abstract Manga? GetMangaFromId(string publicationId);
/// <summary> /// <summary>
/// Returns all Chapters of the publication in the provided language. /// Returns all Chapters of the publication in the provided language.

View File

@ -72,13 +72,10 @@ public class MangaDex : MangaConnector
return retManga.ToArray(); return retManga.ToArray();
} }
public override Manga? GetMangaFromUrl(string url) public override Manga? GetMangaFromId(string publicationId)
{ {
Regex idRex = new (@"https:\/\/mangadex.org\/title\/([A-z0-9-]*)\/.*");
string id = idRex.Match(url).Groups[1].Value;
Log($"Got id {id} from {url}");
DownloadClient.RequestResult requestResult = DownloadClient.RequestResult requestResult =
downloadClient.MakeRequest($"https://api.mangadex.org/manga/{id}", (byte)RequestType.Manga); downloadClient.MakeRequest($"https://api.mangadex.org/manga/{publicationId}", (byte)RequestType.Manga);
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300) if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300)
return null; return null;
JsonObject? result = JsonSerializer.Deserialize<JsonObject>(requestResult.result); JsonObject? result = JsonSerializer.Deserialize<JsonObject>(requestResult.result);
@ -87,6 +84,14 @@ public class MangaDex : MangaConnector
return null; return null;
} }
public override Manga? GetMangaFromUrl(string url)
{
Regex idRex = new (@"https:\/\/mangadex.org\/title\/([A-z0-9-]*)\/.*");
string id = idRex.Match(url).Groups[1].Value;
Log($"Got id {id} from {url}");
return GetMangaFromId(id);
}
private Manga? MangaFromJsonObject(JsonObject manga) private Manga? MangaFromJsonObject(JsonObject manga)
{ {
if (!manga.ContainsKey("attributes")) if (!manga.ContainsKey("attributes"))

View File

@ -39,6 +39,11 @@ public class MangaKatana : MangaConnector
return publications; return publications;
} }
public override Manga? GetMangaFromId(string publicationId)
{
return GetMangaFromUrl($"https://mangakatana.com/manga/{publicationId}");
}
public override Manga? GetMangaFromUrl(string url) public override Manga? GetMangaFromUrl(string url)
{ {
DownloadClient.RequestResult requestResult = DownloadClient.RequestResult requestResult =

View File

@ -32,6 +32,11 @@ public class MangaLife : MangaConnector
return publications; return publications;
} }
public override Manga? GetMangaFromId(string publicationId)
{
return GetMangaFromUrl($"https://manga4life.com/manga/{publicationId}");
}
public override Manga? GetMangaFromUrl(string url) public override Manga? GetMangaFromUrl(string url)
{ {
Regex publicationIdRex = new(@"https:\/\/manga4life.com\/manga\/(.*)(\/.*)*"); Regex publicationIdRex = new(@"https:\/\/manga4life.com\/manga\/(.*)(\/.*)*");

View File

@ -54,6 +54,11 @@ public class Manganato : MangaConnector
return ret.ToArray(); return ret.ToArray();
} }
public override Manga? GetMangaFromId(string publicationId)
{
return GetMangaFromUrl($"https://chapmanganato.com/{publicationId}");
}
public override Manga? GetMangaFromUrl(string url) public override Manga? GetMangaFromUrl(string url)
{ {
DownloadClient.RequestResult requestResult = DownloadClient.RequestResult requestResult =

View File

@ -110,7 +110,12 @@ public class Mangasee : MangaConnector
return 1 + (case2 < case3 ? case2 : case3); return 1 + (case2 < case3 ? case2 : case3);
} }
} }
public override Manga? GetMangaFromId(string publicationId)
{
return GetMangaFromUrl($"https://mangasee123.com/manga/{publicationId}");
}
public override Manga? GetMangaFromUrl(string url) public override Manga? GetMangaFromUrl(string url)
{ {
Regex publicationIdRex = new(@"https:\/\/mangasee123.com\/manga\/(.*)(\/.*)*"); Regex publicationIdRex = new(@"https:\/\/mangasee123.com\/manga\/(.*)(\/.*)*");

View File

@ -54,6 +54,11 @@ public class Mangaworld: MangaConnector
return ret.ToArray(); return ret.ToArray();
} }
public override Manga? GetMangaFromId(string publicationId)
{
throw new NotImplementedException();
}
public override Manga? GetMangaFromUrl(string url) public override Manga? GetMangaFromUrl(string url)
{ {
DownloadClient.RequestResult requestResult = DownloadClient.RequestResult requestResult =

View File

@ -304,6 +304,32 @@ public class Server : GlobalBase
_parent.jobBoss.AddJob(new DownloadNewChapters(this, connector!, manga, false, translatedLanguage: translatedLanguage??"en")); _parent.jobBoss.AddJob(new DownloadNewChapters(this, connector!, manga, false, translatedLanguage: translatedLanguage??"en"));
SendResponse(HttpStatusCode.Accepted, response); SendResponse(HttpStatusCode.Accepted, response);
break; break;
case "Jobs/UpdateMetadata":
if (!requestVariables.TryGetValue("internalId", out internalId))
{
foreach (DownloadNewChapters dncJob in (_parent.jobBoss.jobs.Where(possibleDncJob =>
possibleDncJob is DownloadNewChapters) as IEnumerable<DownloadNewChapters>)!)
{
_parent.jobBoss.AddJob(new UpdateMetadata(this, dncJob.mangaConnector, dncJob.manga));
}
SendResponse(HttpStatusCode.Accepted, response);
}
else
{
Job[] possibleDncJobs = _parent.jobBoss.GetJobsLike(internalId: internalId).ToArray();
switch (possibleDncJobs.Length)
{
case <1: SendResponse(HttpStatusCode.BadRequest, response, "Could not find matching release"); break;
case >1: SendResponse(HttpStatusCode.BadRequest, response, "Multiple releases??"); break;
default:
DownloadNewChapters dncJob = possibleDncJobs[0] as DownloadNewChapters ??
throw new Exception("Has to be DownloadNewChapters Job");
_parent.jobBoss.AddJob(new UpdateMetadata(this, dncJob.mangaConnector, dncJob.manga));
SendResponse(HttpStatusCode.Accepted, response);
break;
}
}
break;
case "Jobs/StartNow": case "Jobs/StartNow":
if (!requestVariables.TryGetValue("jobId", out jobId) || if (!requestVariables.TryGetValue("jobId", out jobId) ||
!_parent.jobBoss.TryGetJobById(jobId, out job)) !_parent.jobBoss.TryGetJobById(jobId, out job))