Compare commits

...

7 Commits

Author SHA1 Message Date
ed79ee5d0f Add Manga from Jobs to cachedManga 2023-09-01 23:41:50 +02:00
28e05e549d Added import and export for Jobs
Renamed tasksFilePath -> jobsFilePath and changed to jobs.json
2023-09-01 23:37:50 +02:00
eaab7c5235 Fixed jobs not starting at all 2023-09-01 23:08:31 +02:00
0552b3db82 Fix crash on null Logmessage 2023-09-01 22:53:38 +02:00
c813e1854d Do not add duplicate jobs 2023-09-01 22:39:22 +02:00
32036df057 Added API call to retrieve cover with internalId.
No need to mount imageCache over multiple containers.
2023-09-01 21:40:56 +02:00
394829ee36 Revert "Download Covers only when Downloading Chapters"
This reverts commit e663163d

Covers might be important
2023-09-01 21:17:46 +02:00
14 changed files with 269 additions and 78 deletions

View File

@ -45,14 +45,22 @@ public class MemoryLogger : LoggerBase
public string[] GetNewLines() public string[] GetNewLines()
{ {
int logMessageCount = _logMessages.Count; int logMessageCount = _logMessages.Count;
string[] ret = new string[logMessageCount - _lastLogMessageIndex]; List<string> ret = new();
for (int retIndex = 0; retIndex < ret.Length; retIndex++) int retIndex = 0;
for (; retIndex < logMessageCount - _lastLogMessageIndex; retIndex++)
{ {
ret[retIndex] = _logMessages.GetValueAtIndex(_lastLogMessageIndex + retIndex).ToString(); try
{
ret.Add(_logMessages.GetValueAtIndex(_lastLogMessageIndex + retIndex).ToString());
}
catch (NullReferenceException e)//Called when LogMessage has not finished writing
{
break;
}
} }
_lastLogMessageIndex = logMessageCount; _lastLogMessageIndex = _lastLogMessageIndex + retIndex;
return ret; return ret.ToArray();
} }
} }

View File

@ -19,6 +19,8 @@ public abstract class Job : GlobalBase
this.recurring = recurring; this.recurring = recurring;
if (recurring && recurrenceTime is null) if (recurring && recurrenceTime is null)
throw new ArgumentException("If recurrence is set to true, a recurrence time has to be provided."); throw new ArgumentException("If recurrence is set to true, a recurrence time has to be provided.");
else if(recurring && recurrenceTime is not null)
this.lastExecution = DateTime.Now.Subtract((TimeSpan)recurrenceTime);
this.recurrenceTime = recurrenceTime; this.recurrenceTime = recurrenceTime;
} }

View File

@ -1,4 +1,5 @@
using Tranga.MangaConnectors; using Newtonsoft.Json;
using Tranga.MangaConnectors;
namespace Tranga.Jobs; namespace Tranga.Jobs;
@ -7,16 +8,42 @@ public class JobBoss : GlobalBase
public HashSet<Job> jobs { get; init; } public HashSet<Job> jobs { get; init; }
private Dictionary<MangaConnector, Queue<Job>> mangaConnectorJobQueue { get; init; } private Dictionary<MangaConnector, Queue<Job>> mangaConnectorJobQueue { get; init; }
public JobBoss(GlobalBase clone) : base(clone) public JobBoss(GlobalBase clone, HashSet<MangaConnector> connectors) : base(clone)
{ {
this.jobs = new(); if (File.Exists(settings.jobsFilePath))
this.jobs = JsonConvert.DeserializeObject<HashSet<Job>>(File.ReadAllText(settings.jobsFilePath), new JobJsonConverter(this, new MangaConnectorJsonConverter(this, connectors)))!;
else
this.jobs = new();
foreach (DownloadNewChapters ncJob in this.jobs.Where(job => job is DownloadNewChapters))
cachedPublications.Add(ncJob.manga);
this.mangaConnectorJobQueue = new(); this.mangaConnectorJobQueue = new();
} }
public void AddJob(Job job) public void AddJob(Job job)
{ {
Log($"Added {job}"); if (ContainsJobLike(job))
this.jobs.Add(job); {
Log($"Already Contains Job {job}");
}
else
{
Log($"Added {job}");
this.jobs.Add(job);
File.WriteAllText(settings.jobsFilePath, JsonConvert.SerializeObject(this.jobs));
}
}
public bool ContainsJobLike(Job job)
{
if (job is DownloadChapter dcJob)
{
return this.GetJobsLike(dcJob.mangaConnector, chapter: dcJob.chapter).Any();
}else if (job is DownloadNewChapters ncJob)
{
return this.GetJobsLike(ncJob.mangaConnector, ncJob.manga).Any();
}
return false;
} }
public void RemoveJob(Job job) public void RemoveJob(Job job)
@ -110,13 +137,13 @@ public class JobBoss : GlobalBase
foreach (Queue<Job> jobQueue in mangaConnectorJobQueue.Values) foreach (Queue<Job> jobQueue in mangaConnectorJobQueue.Values)
{ {
Job queueHead = jobQueue.Peek(); Job queueHead = jobQueue.Peek();
if (queueHead.progressToken.state == ProgressToken.State.Complete) if (queueHead.progressToken.state is ProgressToken.State.Complete)
{ {
if(queueHead.recurring) if(queueHead.recurring)
queueHead.Reset(); queueHead.Reset();
jobQueue.Dequeue(); jobQueue.Dequeue();
}else if(queueHead.progressToken.state is ProgressToken.State.Standby)
AddJobsToQueue(jobQueue.Peek().ExecuteReturnSubTasks()); AddJobsToQueue(jobQueue.Peek().ExecuteReturnSubTasks());
}
} }
} }
} }

View File

@ -0,0 +1,66 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Tranga.MangaConnectors;
namespace Tranga.Jobs;
public class JobJsonConverter : JsonConverter
{
private GlobalBase _clone;
private MangaConnectorJsonConverter _mangaConnectorJsonConverter;
internal JobJsonConverter(GlobalBase clone, MangaConnectorJsonConverter mangaConnectorJsonConverter)
{
this._clone = clone;
this._mangaConnectorJsonConverter = mangaConnectorJsonConverter;
}
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(Job));
}
public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
JObject jo = JObject.Load(reader);
if (jo.ContainsKey("manga"))//DownloadNewChapters
{
return new DownloadNewChapters(this._clone,
jo.GetValue("mangaConnector")!.ToObject<MangaConnector>(JsonSerializer.Create(new JsonSerializerSettings()
{
Converters =
{
this._mangaConnectorJsonConverter
}
}))!,
jo.GetValue("manga")!.ToObject<Manga>(),
jo.GetValue("recurring")!.Value<bool>(),
jo.GetValue("recurrenceTime")!.ToObject<TimeSpan?>());
}
if (jo.ContainsKey("chapter"))//DownloadChapter
{
return new DownloadChapter(this._clone,
jo.GetValue("mangaConnector")!.ToObject<MangaConnector>(JsonSerializer.Create(new JsonSerializerSettings()
{
Converters =
{
this._mangaConnectorJsonConverter
}
}))!,
jo.GetValue("chapter")!.ToObject<Chapter>());
}
throw new Exception();
}
public override bool CanWrite => false;
/// <summary>
/// Don't call this
/// </summary>
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
throw new Exception("Dont call this");
}
}

View File

@ -36,14 +36,14 @@ public struct Manga
private static readonly Regex LegalCharacters = new (@"[A-Z]*[a-z]*[0-9]* *\.*-*,*'*\'*\)*\(*~*!*"); private static readonly Regex LegalCharacters = new (@"[A-Z]*[a-z]*[0-9]* *\.*-*,*'*\'*\)*\(*~*!*");
[JsonConstructor] [JsonConstructor]
public Manga(string sortName, List<string> authors, string? description, Dictionary<string,string> altTitles, string[] tags, string? coverUrl, Dictionary<string,string>? links, int? year, string? originalLanguage, string status, string publicationId, string? folderName = null, float? ignoreChaptersBelow = 0) public Manga(string sortName, List<string> authors, string? description, Dictionary<string,string> altTitles, string[] tags, string? coverUrl, string? coverFileNameInCache, Dictionary<string,string>? links, int? year, string? originalLanguage, string status, string publicationId, string? folderName = null, float? ignoreChaptersBelow = 0)
{ {
this.sortName = sortName; this.sortName = sortName;
this.authors = authors; this.authors = authors;
this.description = description; this.description = description;
this.altTitles = altTitles; this.altTitles = altTitles;
this.tags = tags; this.tags = tags;
this.coverFileNameInCache = null; this.coverFileNameInCache = coverFileNameInCache;
this.coverUrl = coverUrl; this.coverUrl = coverUrl;
this.links = links ?? new Dictionary<string, string>(); this.links = links ?? new Dictionary<string, string>();
this.year = year; this.year = year;

View File

@ -0,0 +1,49 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Tranga.MangaConnectors;
public class MangaConnectorJsonConverter : JsonConverter
{
private GlobalBase _clone;
private HashSet<MangaConnector> connectors;
internal MangaConnectorJsonConverter(GlobalBase clone, HashSet<MangaConnector> connectors)
{
this._clone = clone;
this.connectors = connectors;
}
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(MangaConnector));
}
public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
JObject jo = JObject.Load(reader);
switch (jo.GetValue("name")!.Value<string>()!)
{
case "MangaDex":
return this.connectors.First(c => c is MangaDex);
case "Manganato":
return this.connectors.First(c => c is Manganato);
case "MangaKatana":
return this.connectors.First(c => c is MangaKatana);
case "Mangasee":
return this.connectors.First(c => c is Mangasee);
}
throw new Exception();
}
public override bool CanWrite => false;
/// <summary>
/// Don't call this
/// </summary>
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
throw new Exception("Dont call this");
}
}

View File

@ -102,6 +102,9 @@ public class MangaDex : MangaConnector
authorIds.Add(node!["id"]!.GetValue<string>()); authorIds.Add(node!["id"]!.GetValue<string>());
} }
string? coverUrl = GetCoverUrl(publicationId, posterId); string? coverUrl = GetCoverUrl(publicationId, posterId);
string? coverCacheName = null;
if (coverUrl is not null)
coverCacheName = SaveCoverImageToCache(coverUrl, (byte)RequestType.AtHomeServer);
List<string> authors = GetAuthors(authorIds); List<string> authors = GetAuthors(authorIds);
@ -132,6 +135,7 @@ public class MangaDex : MangaConnector
altTitlesDict, altTitlesDict,
tags.ToArray(), tags.ToArray(),
coverUrl, coverUrl,
coverCacheName,
linksDict, linksDict,
year, year,
originalLanguage, originalLanguage,
@ -227,8 +231,6 @@ public class MangaDex : MangaConnector
string comicInfoPath = Path.GetTempFileName(); string comicInfoPath = Path.GetTempFileName();
File.WriteAllText(comicInfoPath, chapter.GetComicInfoXmlString()); File.WriteAllText(comicInfoPath, chapter.GetComicInfoXmlString());
if (chapterParentManga.coverUrl is not null)
chapterParentManga.coverFileNameInCache = SaveCoverImageToCache(chapterParentManga.coverUrl, (byte)RequestType.AtHomeServer);
//Download Chapter-Images //Download Chapter-Images
return DownloadChapterImages(imageUrls.ToArray(), chapter.GetArchiveFilePath(settings.downloadLocation), (byte)RequestType.AtHomeServer, comicInfoPath, progressToken:progressToken); return DownloadChapterImages(imageUrls.ToArray(), chapter.GetArchiveFilePath(settings.downloadLocation), (byte)RequestType.AtHomeServer, comicInfoPath, progressToken:progressToken);
} }

View File

@ -119,6 +119,8 @@ public class MangaKatana : MangaConnector
string posterUrl = document.DocumentNode.SelectSingleNode("//*[@id='single_book']/div[1]/div").Descendants("img").First() string posterUrl = document.DocumentNode.SelectSingleNode("//*[@id='single_book']/div[1]/div").Descendants("img").First()
.GetAttributes().First(a => a.Name == "src").Value; .GetAttributes().First(a => a.Name == "src").Value;
string coverFileNameInCache = SaveCoverImageToCache(posterUrl, 1);
string description = document.DocumentNode.SelectSingleNode("//*[@id='single_book']/div[3]/p").InnerText; string description = document.DocumentNode.SelectSingleNode("//*[@id='single_book']/div[3]/p").InnerText;
while (description.StartsWith('\n')) while (description.StartsWith('\n'))
description = description.Substring(1); description = description.Substring(1);
@ -132,7 +134,7 @@ public class MangaKatana : MangaConnector
year = Convert.ToInt32(yearString); year = Convert.ToInt32(yearString);
} }
return new Manga(sortName, authors.ToList(), description, altTitles, tags.ToArray(), posterUrl, links, return new Manga(sortName, authors.ToList(), description, altTitles, tags.ToArray(), posterUrl, coverFileNameInCache, links,
year, originalLanguage, status, publicationId); year, originalLanguage, status, publicationId);
} }
@ -199,9 +201,6 @@ public class MangaKatana : MangaConnector
string comicInfoPath = Path.GetTempFileName(); string comicInfoPath = Path.GetTempFileName();
File.WriteAllText(comicInfoPath, chapter.GetComicInfoXmlString()); File.WriteAllText(comicInfoPath, chapter.GetComicInfoXmlString());
if (chapterParentManga.coverUrl is not null)
chapterParentManga.coverFileNameInCache = SaveCoverImageToCache(chapterParentManga.coverUrl, 1);
return DownloadChapterImages(imageUrls, chapter.GetArchiveFilePath(settings.downloadLocation), 1, comicInfoPath, "https://mangakatana.com/", progressToken:progressToken); return DownloadChapterImages(imageUrls, chapter.GetArchiveFilePath(settings.downloadLocation), 1, comicInfoPath, "https://mangakatana.com/", progressToken:progressToken);
} }

View File

@ -111,6 +111,8 @@ public class Manganato : MangaConnector
string posterUrl = document.DocumentNode.Descendants("span").First(s => s.HasClass("info-image")).Descendants("img").First() string posterUrl = document.DocumentNode.Descendants("span").First(s => s.HasClass("info-image")).Descendants("img").First()
.GetAttributes().First(a => a.Name == "src").Value; .GetAttributes().First(a => a.Name == "src").Value;
string coverFileNameInCache = SaveCoverImageToCache(posterUrl, 1);
string description = document.DocumentNode.Descendants("div").First(d => d.HasClass("panel-story-info-description")) string description = document.DocumentNode.Descendants("div").First(d => d.HasClass("panel-story-info-description"))
.InnerText.Replace("Description :", ""); .InnerText.Replace("Description :", "");
while (description.StartsWith('\n')) while (description.StartsWith('\n'))
@ -120,7 +122,7 @@ public class Manganato : MangaConnector
.First(s => s.HasClass("chapter-time")).InnerText; .First(s => s.HasClass("chapter-time")).InnerText;
int year = Convert.ToInt32(yearString.Split(',')[^1]) + 2000; int year = Convert.ToInt32(yearString.Split(',')[^1]) + 2000;
return new Manga(sortName, authors.ToList(), description, altTitles, tags.ToArray(), posterUrl, links, return new Manga(sortName, authors.ToList(), description, altTitles, tags.ToArray(), posterUrl, coverFileNameInCache, links,
year, originalLanguage, status, publicationId); year, originalLanguage, status, publicationId);
} }
@ -185,9 +187,6 @@ public class Manganato : MangaConnector
string comicInfoPath = Path.GetTempFileName(); string comicInfoPath = Path.GetTempFileName();
File.WriteAllText(comicInfoPath, chapter.GetComicInfoXmlString()); File.WriteAllText(comicInfoPath, chapter.GetComicInfoXmlString());
if (chapterParentManga.coverUrl is not null)
chapterParentManga.coverFileNameInCache = SaveCoverImageToCache(chapterParentManga.coverUrl, 1);
return DownloadChapterImages(imageUrls, chapter.GetArchiveFilePath(settings.downloadLocation), 1, comicInfoPath, "https://chapmanganato.com/", progressToken:progressToken); return DownloadChapterImages(imageUrls, chapter.GetArchiveFilePath(settings.downloadLocation), 1, comicInfoPath, "https://chapmanganato.com/", progressToken:progressToken);
} }

View File

@ -133,6 +133,7 @@ public class Mangasee : MangaConnector
HtmlNode posterNode = HtmlNode posterNode =
document.DocumentNode.Descendants("img").First(img => img.HasClass("img-fluid") && img.HasClass("bottom-5")); document.DocumentNode.Descendants("img").First(img => img.HasClass("img-fluid") && img.HasClass("bottom-5"));
string posterUrl = posterNode.GetAttributeValue("src", ""); string posterUrl = posterNode.GetAttributeValue("src", "");
string coverFileNameInCache = SaveCoverImageToCache(posterUrl, 1);
HtmlNode attributes = document.DocumentNode.Descendants("div") HtmlNode attributes = document.DocumentNode.Descendants("div")
.First(div => div.HasClass("col-md-9") && div.HasClass("col-sm-8") && div.HasClass("top-5")) .First(div => div.HasClass("col-md-9") && div.HasClass("col-sm-8") && div.HasClass("top-5"))
@ -170,7 +171,7 @@ public class Mangasee : MangaConnector
foreach(string at in a) foreach(string at in a)
altTitles.Add((i++).ToString(), at); altTitles.Add((i++).ToString(), at);
return new Manga(sortName, authors, description, altTitles, tags.ToArray(), posterUrl, links, return new Manga(sortName, authors, description, altTitles, tags.ToArray(), posterUrl, coverFileNameInCache, links,
year, originalLanguage, status, publicationId); year, originalLanguage, status, publicationId);
} }
@ -270,9 +271,6 @@ public class Mangasee : MangaConnector
string comicInfoPath = Path.GetTempFileName(); string comicInfoPath = Path.GetTempFileName();
File.WriteAllText(comicInfoPath, chapter.GetComicInfoXmlString()); File.WriteAllText(comicInfoPath, chapter.GetComicInfoXmlString());
if (chapterParentManga.coverUrl is not null)
chapterParentManga.coverFileNameInCache = SaveCoverImageToCache(chapterParentManga.coverUrl, 1);
return DownloadChapterImages(urls.ToArray(), chapter.GetArchiveFilePath(settings.downloadLocation), 1, comicInfoPath, progressToken:progressToken); return DownloadChapterImages(urls.ToArray(), chapter.GetArchiveFilePath(settings.downloadLocation), 1, comicInfoPath, progressToken:progressToken);
} }
return response.Status; return response.Status;

View File

@ -105,68 +105,85 @@ public class Server : GlobalBase
private void HandleGet(HttpListenerRequest request, HttpListenerResponse response) private void HandleGet(HttpListenerRequest request, HttpListenerResponse response)
{ {
Dictionary<string, string> requestVariables = GetRequestVariables(request.Url!.Query); Dictionary<string, string> requestVariables = GetRequestVariables(request.Url!.Query);
string? connectorName, jobId; string? connectorName, jobId, internalId;
MangaConnector connector; MangaConnector? connector;
Manga? manga;
string path = Regex.Match(request.Url!.LocalPath, @"[A-z0-9]+(\/[A-z0-9]+)*").Value; string path = Regex.Match(request.Url!.LocalPath, @"[A-z0-9]+(\/[A-z0-9]+)*").Value;
switch (path) switch (path)
{ {
case "Connectors": case "Connectors":
SendResponse(HttpStatusCode.OK, response, _parent.GetConnectors().Select(con => con.name).ToArray()); SendResponse(HttpStatusCode.OK, response, _parent.GetConnectors().Select(con => con.name).ToArray());
break; break;
case "Manga/Cover":
if (!requestVariables.TryGetValue("internalId", out internalId) ||
!_parent.TryGetPublicationById(internalId, out manga))
{
SendResponse(HttpStatusCode.BadRequest, response);
break;
}
string filePath = settings.GetFullCoverPath((Manga)manga!);
if (File.Exists(filePath))
{
FileStream coverStream = new(filePath, FileMode.Open);
SendResponse(HttpStatusCode.OK, response, coverStream);
}
else
{
SendResponse(HttpStatusCode.NotFound, response);
}
break;
case "Manga/FromConnector": case "Manga/FromConnector":
if (!requestVariables.TryGetValue("connector", out connectorName) || if (!requestVariables.TryGetValue("connector", out connectorName) ||
!requestVariables.TryGetValue("title", out string? title) || !requestVariables.TryGetValue("title", out string? title) ||
_parent.GetConnector(connectorName) is null) !_parent.TryGetConnector(connectorName, out connector))
{ {
SendResponse(HttpStatusCode.BadRequest, response); SendResponse(HttpStatusCode.BadRequest, response);
break; break;
} }
connector = _parent.GetConnector(connectorName)!; SendResponse(HttpStatusCode.OK, response, connector!.GetPublications(title));
SendResponse(HttpStatusCode.OK, response, connector.GetPublications(title));
break; break;
case "Manga/Chapters": case "Manga/Chapters":
if(!requestVariables.TryGetValue("connector", out connectorName) || if(!requestVariables.TryGetValue("connector", out connectorName) ||
!requestVariables.TryGetValue("internalId", out string? internalId) || !requestVariables.TryGetValue("internalId", out internalId) ||
_parent.GetConnector(connectorName) is null || !_parent.TryGetConnector(connectorName, out connector) ||
_parent.GetPublicationById(internalId) is null) !_parent.TryGetPublicationById(internalId, out manga))
{ {
SendResponse(HttpStatusCode.BadRequest, response); SendResponse(HttpStatusCode.BadRequest, response);
break; break;
} }
connector = _parent.GetConnector(connectorName)!; SendResponse(HttpStatusCode.OK, response, connector!.GetChapters((Manga)manga!));
Manga manga = (Manga)_parent.GetPublicationById(internalId)!;
SendResponse(HttpStatusCode.OK, response, connector.GetChapters(manga));
break; break;
case "Jobs": case "Jobs":
if (!requestVariables.TryGetValue("jobId", out jobId)) if (!requestVariables.TryGetValue("jobId", out jobId))
{ {
if(!_parent._jobBoss.jobs.Any(jjob => jjob.id == jobId)) if(!_parent.jobBoss.jobs.Any(jjob => jjob.id == jobId))
SendResponse(HttpStatusCode.BadRequest, response); SendResponse(HttpStatusCode.BadRequest, response);
else else
SendResponse(HttpStatusCode.OK, response, _parent._jobBoss.jobs.First(jjob => jjob.id == jobId)); SendResponse(HttpStatusCode.OK, response, _parent.jobBoss.jobs.First(jjob => jjob.id == jobId));
break; break;
} }
SendResponse(HttpStatusCode.OK, response, _parent._jobBoss.jobs); SendResponse(HttpStatusCode.OK, response, _parent.jobBoss.jobs);
break; break;
case "Jobs/Progress": case "Jobs/Progress":
if (!requestVariables.TryGetValue("jobId", out jobId)) if (!requestVariables.TryGetValue("jobId", out jobId))
{ {
if(!_parent._jobBoss.jobs.Any(jjob => jjob.id == jobId)) if(!_parent.jobBoss.jobs.Any(jjob => jjob.id == jobId))
SendResponse(HttpStatusCode.BadRequest, response); SendResponse(HttpStatusCode.BadRequest, response);
else else
SendResponse(HttpStatusCode.OK, response, _parent._jobBoss.jobs.First(jjob => jjob.id == jobId).progressToken); SendResponse(HttpStatusCode.OK, response, _parent.jobBoss.jobs.First(jjob => jjob.id == jobId).progressToken);
break; break;
} }
SendResponse(HttpStatusCode.OK, response, _parent._jobBoss.jobs.Select(jjob => jjob.progressToken)); SendResponse(HttpStatusCode.OK, response, _parent.jobBoss.jobs.Select(jjob => jjob.progressToken));
break; break;
case "Jobs/Running": case "Jobs/Running":
SendResponse(HttpStatusCode.OK, response, _parent._jobBoss.jobs.Where(jjob => jjob.progressToken.state is ProgressToken.State.Running)); SendResponse(HttpStatusCode.OK, response, _parent.jobBoss.jobs.Where(jjob => jjob.progressToken.state is ProgressToken.State.Running));
break; break;
case "Jobs/Waiting": case "Jobs/Waiting":
SendResponse(HttpStatusCode.OK, response, _parent._jobBoss.jobs.Where(jjob => jjob.progressToken.state is ProgressToken.State.Standby)); SendResponse(HttpStatusCode.OK, response, _parent.jobBoss.jobs.Where(jjob => jjob.progressToken.state is ProgressToken.State.Standby));
break; break;
case "Jobs/MonitorJobs": case "Jobs/MonitorJobs":
SendResponse(HttpStatusCode.OK, response, _parent._jobBoss.jobs.Where(jjob => jjob is DownloadNewChapters)); SendResponse(HttpStatusCode.OK, response, _parent.jobBoss.jobs.Where(jjob => jjob is DownloadNewChapters));
break; break;
case "Settings": case "Settings":
SendResponse(HttpStatusCode.OK, response, settings); SendResponse(HttpStatusCode.OK, response, settings);
@ -213,7 +230,7 @@ public class Server : GlobalBase
} }
connector = _parent.GetConnector(connectorName)!; connector = _parent.GetConnector(connectorName)!;
manga = (Manga)_parent.GetPublicationById(internalId)!; manga = (Manga)_parent.GetPublicationById(internalId)!;
_parent._jobBoss.AddJob(new DownloadNewChapters(this, connector, manga, true, interval)); _parent.jobBoss.AddJob(new DownloadNewChapters(this, connector, manga, true, interval));
SendResponse(HttpStatusCode.Accepted, response); SendResponse(HttpStatusCode.Accepted, response);
break; break;
case "Jobs/DownloadNewChapters": case "Jobs/DownloadNewChapters":
@ -227,17 +244,17 @@ public class Server : GlobalBase
} }
connector = _parent.GetConnector(connectorName)!; connector = _parent.GetConnector(connectorName)!;
manga = (Manga)_parent.GetPublicationById(internalId)!; manga = (Manga)_parent.GetPublicationById(internalId)!;
_parent._jobBoss.AddJob(new DownloadNewChapters(this, connector, manga, false)); _parent.jobBoss.AddJob(new DownloadNewChapters(this, connector, manga, false));
SendResponse(HttpStatusCode.Accepted, response); SendResponse(HttpStatusCode.Accepted, response);
break; break;
case "Jobs/StartNow": case "Jobs/StartNow":
if (!requestVariables.TryGetValue("jobId", out string? jobId) || if (!requestVariables.TryGetValue("jobId", out string? jobId) ||
!_parent._jobBoss.TryGetJobById(jobId, out Job? job)) !_parent.jobBoss.TryGetJobById(jobId, out Job? job))
{ {
SendResponse(HttpStatusCode.BadRequest, response); SendResponse(HttpStatusCode.BadRequest, response);
break; break;
} }
_parent._jobBoss.AddJobToQueue(job!); _parent.jobBoss.AddJobToQueue(job!);
SendResponse(HttpStatusCode.Accepted, response); SendResponse(HttpStatusCode.Accepted, response);
break; break;
case "Settings/UpdateDownloadLocation": case "Settings/UpdateDownloadLocation":
@ -346,12 +363,12 @@ public class Server : GlobalBase
{ {
case "Jobs": case "Jobs":
if (!requestVariables.TryGetValue("jobID", out string? jobId) || if (!requestVariables.TryGetValue("jobID", out string? jobId) ||
!_parent._jobBoss.TryGetJobById(jobId, out Job? job)) !_parent.jobBoss.TryGetJobById(jobId, out Job? job))
{ {
SendResponse(HttpStatusCode.BadRequest, response); SendResponse(HttpStatusCode.BadRequest, response);
break; break;
} }
_parent._jobBoss.RemoveJob(job!); _parent.jobBoss.RemoveJob(job!);
SendResponse(HttpStatusCode.Accepted, response); SendResponse(HttpStatusCode.Accepted, response);
break; break;
case "Jobs/DownloadChapter": case "Jobs/DownloadChapter":
@ -364,7 +381,7 @@ public class Server : GlobalBase
SendResponse(HttpStatusCode.BadRequest, response); SendResponse(HttpStatusCode.BadRequest, response);
break; break;
} }
_parent._jobBoss.RemoveJobs(_parent._jobBoss.GetJobsLike(connectorName, internalId, chapterNumber)); _parent.jobBoss.RemoveJobs(_parent.jobBoss.GetJobsLike(connectorName, internalId, chapterNumber));
SendResponse(HttpStatusCode.Accepted, response); SendResponse(HttpStatusCode.Accepted, response);
break; break;
case "Jobs/MonitorManga": case "Jobs/MonitorManga":
@ -378,7 +395,7 @@ public class Server : GlobalBase
} }
connector = _parent.GetConnector(connectorName)!; connector = _parent.GetConnector(connectorName)!;
manga = (Manga)_parent.GetPublicationById(internalId)!; manga = (Manga)_parent.GetPublicationById(internalId)!;
_parent._jobBoss.RemoveJobs(_parent._jobBoss.GetJobsLike(connector, manga)); _parent.jobBoss.RemoveJobs(_parent.jobBoss.GetJobsLike(connector, manga));
SendResponse(HttpStatusCode.Accepted, response); SendResponse(HttpStatusCode.Accepted, response);
break; break;
case "Jobs/DownloadNewChapters": case "Jobs/DownloadNewChapters":
@ -392,7 +409,7 @@ public class Server : GlobalBase
} }
connector = _parent.GetConnector(connectorName)!; connector = _parent.GetConnector(connectorName)!;
manga = (Manga)_parent.GetPublicationById(internalId)!; manga = (Manga)_parent.GetPublicationById(internalId)!;
_parent._jobBoss.RemoveJobs(_parent._jobBoss.GetJobsLike(connector, manga)); _parent.jobBoss.RemoveJobs(_parent.jobBoss.GetJobsLike(connector, manga));
SendResponse(HttpStatusCode.Accepted, response); SendResponse(HttpStatusCode.Accepted, response);
break; break;
case "NotificationConnectors": case "NotificationConnectors":
@ -430,17 +447,27 @@ public class Server : GlobalBase
response.AddHeader("Access-Control-Allow-Methods", "GET, POST, DELETE"); response.AddHeader("Access-Control-Allow-Methods", "GET, POST, DELETE");
response.AddHeader("Access-Control-Max-Age", "1728000"); response.AddHeader("Access-Control-Max-Age", "1728000");
response.AppendHeader("Access-Control-Allow-Origin", "*"); response.AppendHeader("Access-Control-Allow-Origin", "*");
response.ContentType = "application/json";
try if (content is not FileStream stream)
{ {
response.OutputStream.Write(content is not null response.ContentType = "application/json";
? Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(content)) try
: Array.Empty<byte>()); {
response.OutputStream.Close(); response.OutputStream.Write(content is not null
? Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(content))
: Array.Empty<byte>());
response.OutputStream.Close();
}
catch (HttpListenerException e)
{
Log(e.ToString());
}
} }
catch (HttpListenerException e) else
{ {
Log(e.ToString()); stream.CopyTo(response.OutputStream);
response.OutputStream.Close();
stream.Close();
} }
} }
} }

View File

@ -7,36 +7,42 @@ namespace Tranga;
public partial class Tranga : GlobalBase public partial class Tranga : GlobalBase
{ {
public bool keepRunning; public bool keepRunning;
public JobBoss _jobBoss; public JobBoss jobBoss;
private Server server; private Server _server;
private HashSet<MangaConnector> connectors; private HashSet<MangaConnector> _connectors;
public Tranga(Logger? logger, TrangaSettings settings) : base(logger, settings) public Tranga(Logger? logger, TrangaSettings settings) : base(logger, settings)
{ {
keepRunning = true; keepRunning = true;
_jobBoss = new(this); _connectors = new HashSet<MangaConnector>()
connectors = new HashSet<MangaConnector>()
{ {
new Manganato(this), new Manganato(this),
new Mangasee(this), new Mangasee(this),
new MangaDex(this), new MangaDex(this),
new MangaKatana(this) new MangaKatana(this)
}; };
jobBoss = new(this, this._connectors);
StartJobBoss(); StartJobBoss();
this.server = new Server(this); this._server = new Server(this);
} }
public MangaConnector? GetConnector(string name) public MangaConnector? GetConnector(string name)
{ {
foreach(MangaConnector mc in connectors) foreach(MangaConnector mc in _connectors)
if (mc.name.Equals(name, StringComparison.InvariantCultureIgnoreCase)) if (mc.name.Equals(name, StringComparison.InvariantCultureIgnoreCase))
return mc; return mc;
return null; return null;
} }
public bool TryGetConnector(string name, out MangaConnector? connector)
{
connector = GetConnector(name);
return connector is not null;
}
public IEnumerable<MangaConnector> GetConnectors() public IEnumerable<MangaConnector> GetConnectors()
{ {
return connectors; return _connectors;
} }
public Manga? GetPublicationById(string internalId) public Manga? GetPublicationById(string internalId)
@ -46,13 +52,19 @@ public partial class Tranga : GlobalBase
return null; return null;
} }
public bool TryGetPublicationById(string internalId, out Manga? manga)
{
manga = GetPublicationById(internalId);
return manga is not null;
}
private void StartJobBoss() private void StartJobBoss()
{ {
Thread t = new (() => Thread t = new (() =>
{ {
while (keepRunning) while (keepRunning)
{ {
_jobBoss.CheckJobs(); jobBoss.CheckJobs();
Thread.Sleep(1000); Thread.Sleep(1000);
} }
}); });

View File

@ -14,7 +14,7 @@ public class TrangaSettings
[JsonIgnore] public string settingsFilePath => Path.Join(workingDirectory, "settings.json"); [JsonIgnore] public string settingsFilePath => Path.Join(workingDirectory, "settings.json");
[JsonIgnore] public string libraryConnectorsFilePath => Path.Join(workingDirectory, "libraryConnectors.json"); [JsonIgnore] public string libraryConnectorsFilePath => Path.Join(workingDirectory, "libraryConnectors.json");
[JsonIgnore] public string notificationConnectorsFilePath => Path.Join(workingDirectory, "notificationConnectors.json"); [JsonIgnore] public string notificationConnectorsFilePath => Path.Join(workingDirectory, "notificationConnectors.json");
[JsonIgnore] public string tasksFilePath => Path.Join(workingDirectory, "tasks.json"); [JsonIgnore] public string jobsFilePath => Path.Join(workingDirectory, "jobs.json");
[JsonIgnore] public string coverImageCache => Path.Join(workingDirectory, "imageCache"); [JsonIgnore] public string coverImageCache => Path.Join(workingDirectory, "imageCache");
public ushort? version { get; set; } public ushort? version { get; set; }
@ -127,4 +127,9 @@ public class TrangaSettings
Directory.CreateDirectory(new FileInfo(settingsFilePath).DirectoryName!); Directory.CreateDirectory(new FileInfo(settingsFilePath).DirectoryName!);
File.WriteAllText(settingsFilePath, JsonConvert.SerializeObject(this)); File.WriteAllText(settingsFilePath, JsonConvert.SerializeObject(this));
} }
public string GetFullCoverPath(Manga manga)
{
return Path.Join(this.coverImageCache, manga.coverFileNameInCache);
}
} }

View File

@ -4,7 +4,6 @@ services:
image: glax/tranga-api:latest image: glax/tranga-api:latest
container_name: tranga-api container_name: tranga-api
volumes: volumes:
- ./tranga:/usr/share/Tranga-API #1 when replacing ./tranga replace #2 with same value
- ./Manga:/Manga - ./Manga:/Manga
ports: ports:
- "6531:6531" - "6531:6531"
@ -12,8 +11,6 @@ services:
tranga-website: tranga-website:
image: glax/tranga-website:latest image: glax/tranga-website:latest
container_name: tranga-website container_name: tranga-website
volumes:
- ./tranga/imageCache:/usr/share/nginx/html/imageCache:ro #2 when replacing Point to same value as #1/imageCache
ports: ports:
- "9555:80" - "9555:80"
depends_on: depends_on: