Compare commits

...

35 Commits

Author SHA1 Message Date
dcc12ec3ea Merge remote-tracking branch 'github/master' 2023-09-26 18:28:23 +02:00
8c554076b2 Merge branch 'cuttingedge' 2023-09-26 18:28:15 +02:00
a10fbdf3a5
Merge pull request #59 from C9Glax/C9Glax-patch-1
Update issue templates
2023-09-26 18:27:38 +02:00
f246209685 Changed to template 2023-09-26 18:26:42 +02:00
41c561bd1d
Update issue templates 2023-09-26 18:18:06 +02:00
fc7d5463c3 Fix #58
Mangaworld: Manga without volumes crash
2023-09-26 18:03:18 +02:00
3c2ce266f6 Changed (fixed?) queuelogic 2023-09-20 21:59:39 +02:00
306cb87d67 Fix Check for subjobs 2023-09-20 21:34:04 +02:00
23cda74487 Fix wrong domain regex 2023-09-20 21:33:53 +02:00
3ceee63dfc Only send notification on successful downloads 2023-09-20 14:40:03 +02:00
4e5a6fe97b Export Library and notification connectors on deletion
Added logging
2023-09-20 14:11:31 +02:00
b3b1971dad Startup notification 2023-09-20 13:58:10 +02:00
2699f35b62 housekeeping 2023-09-20 13:33:13 +02:00
7a14583d6a Moved Regex for baseUrl to Globalbase 2023-09-20 13:30:52 +02:00
660f6a1648 Logmessages for creation of library and notification Connector 2023-09-20 13:28:09 +02:00
482fcb7102 better logging for removing files 2023-09-19 23:24:39 +02:00
b6cdb07e3f Remove filewrites 2023-09-19 23:15:18 +02:00
0875e7ee12 Remove log clutter and filewrites 2023-09-19 23:07:26 +02:00
cb6482ebae Add logmessage on startup for next job 2023-09-19 20:04:25 +02:00
87ea077281 Remove log clutter and filewrites 2023-09-19 20:02:56 +02:00
c1aa4cf6b5 Fi bug with exportjobslist not exporting updated jobs 2023-09-19 19:59:51 +02:00
f5b6b1785f small improvements 2023-09-19 19:57:35 +02:00
2553a150d1 Add log to see wait time 2023-09-19 19:54:26 +02:00
b149d377dc Add log to see wait time 2023-09-19 19:54:00 +02:00
0209159c5c Add log to see wait time 2023-09-19 19:50:39 +02:00
e31820eb00 Export Jobs list when finished. 2023-09-19 19:49:42 +02:00
c4d69c27a4 copy cover 2023-09-19 19:43:58 +02:00
3ee53b7436 copy cover 2023-09-19 19:43:39 +02:00
64ec0963e1 copy cover 2023-09-19 19:42:50 +02:00
27c4ed719c Cancel failed jobs 2023-09-19 19:33:43 +02:00
4f4b0cb3a8 LibraryConnector baseUrl regex 2023-09-19 19:22:49 +02:00
48d312da0b File Permissions 2023-09-19 19:21:37 +02:00
1fe4b75ac7 Folder permissions 2023-09-19 19:04:55 +02:00
c580fafc62 Added user tranga to container and set permissions 2023-09-19 19:00:00 +02:00
58040ecb10 Order of returned API Jobs/MonitorJobs And Jobs/Waiting 2023-09-19 18:06:08 +02:00
19 changed files with 199 additions and 59 deletions

21
.github/ISSUE_TEMPLATE/bug.yaml vendored Normal file
View File

@ -0,0 +1,21 @@
name: Bug Report
description: File a bug report
title: "[It broke]: "
labels: ["bug"]
body:
- type: textarea
attributes:
label: What is broken?
description: What happened? How did we get here?
placeholder: The place where you tell me what you expected to happen, and what happened instead.
validations:
required: true
- type: textarea
attributes:
label: Log-output
description: The output of `docker logs tranga-api`
render: C#
- type: textarea
attributes:
label: Additional stuff
description: Screenshots, anything you think might help

View File

@ -10,7 +10,20 @@ RUN dotnet restore /src/Tranga/Tranga.csproj
RUN dotnet publish -c Release -o /publish RUN dotnet publish -c Release -o /publish
FROM glax/tranga-base:latest as runtime FROM glax/tranga-base:latest as runtime
EXPOSE 6531
ARG UNAME=tranga
ARG UID=1000
ARG GID=1000
RUN groupadd -g $GID -o $UNAME
RUN useradd -m -u $UID -g $GID -o -s /bin/bash $UNAME
RUN mkdir /usr/share/tranga-api
RUN mkdir /Manga
RUN chown 1000:1000 /usr/share/tranga-api
RUN chown 1000:1000 /Manga
USER $UNAME
WORKDIR /publish WORKDIR /publish
COPY --from=build-env /publish . COPY --from=build-env /publish .
EXPOSE 6531 USER 0
RUN chown 1000:1000 /publish
ENTRYPOINT ["dotnet", "/publish/Tranga.dll", "-c"] ENTRYPOINT ["dotnet", "/publish/Tranga.dll", "-c"]

View File

@ -1,4 +1,5 @@
using System.Globalization; using System.Globalization;
using System.Text.RegularExpressions;
using Logging; using Logging;
using Newtonsoft.Json; using Newtonsoft.Json;
using Tranga.LibraryConnectors; using Tranga.LibraryConnectors;
@ -14,6 +15,7 @@ public abstract class GlobalBase
protected HashSet<LibraryConnector> libraryConnectors { get; init; } protected HashSet<LibraryConnector> libraryConnectors { get; init; }
protected List<Manga> cachedPublications { get; init; } protected List<Manga> cachedPublications { get; init; }
protected static readonly NumberFormatInfo numberFormatDecimalPoint = new (){ NumberDecimalSeparator = "." }; protected static readonly NumberFormatInfo numberFormatDecimalPoint = new (){ NumberDecimalSeparator = "." };
protected static readonly Regex baseUrlRex = new(@"https?:\/\/[0-9A-z\.-]*");
protected GlobalBase(GlobalBase clone) protected GlobalBase(GlobalBase clone)
{ {
@ -52,11 +54,12 @@ public abstract class GlobalBase
protected void AddNotificationConnector(NotificationConnector notificationConnector) protected void AddNotificationConnector(NotificationConnector notificationConnector)
{ {
Log($"Adding {notificationConnector}"); Log($"Adding {notificationConnector}");
notificationConnectors.RemoveWhere(nc => nc.GetType() == notificationConnector.GetType()); notificationConnectors.RemoveWhere(nc => nc.notificationConnectorType == notificationConnector.notificationConnectorType);
notificationConnectors.Add(notificationConnector); notificationConnectors.Add(notificationConnector);
while(IsFileInUse(settings.notificationConnectorsFilePath)) while(IsFileInUse(settings.notificationConnectorsFilePath))
Thread.Sleep(100); Thread.Sleep(100);
Log("Exporting notificationConnectors");
File.WriteAllText(settings.notificationConnectorsFilePath, JsonConvert.SerializeObject(notificationConnectors)); File.WriteAllText(settings.notificationConnectorsFilePath, JsonConvert.SerializeObject(notificationConnectors));
} }
@ -64,6 +67,10 @@ public abstract class GlobalBase
{ {
Log($"Removing {notificationConnectorType}"); Log($"Removing {notificationConnectorType}");
notificationConnectors.RemoveWhere(nc => nc.notificationConnectorType == notificationConnectorType); notificationConnectors.RemoveWhere(nc => nc.notificationConnectorType == notificationConnectorType);
while(IsFileInUse(settings.notificationConnectorsFilePath))
Thread.Sleep(100);
Log("Exporting notificationConnectors");
File.WriteAllText(settings.notificationConnectorsFilePath, JsonConvert.SerializeObject(notificationConnectors));
} }
protected void UpdateLibraries() protected void UpdateLibraries()
@ -75,11 +82,12 @@ public abstract class GlobalBase
protected void AddLibraryConnector(LibraryConnector libraryConnector) protected void AddLibraryConnector(LibraryConnector libraryConnector)
{ {
Log($"Adding {libraryConnector}"); Log($"Adding {libraryConnector}");
libraryConnectors.RemoveWhere(lc => lc.GetType() == libraryConnector.GetType()); libraryConnectors.RemoveWhere(lc => lc.libraryType == libraryConnector.libraryType);
libraryConnectors.Add(libraryConnector); libraryConnectors.Add(libraryConnector);
while(IsFileInUse(settings.libraryConnectorsFilePath)) while(IsFileInUse(settings.libraryConnectorsFilePath))
Thread.Sleep(100); Thread.Sleep(100);
Log("Exporting libraryConnectors");
File.WriteAllText(settings.libraryConnectorsFilePath, JsonConvert.SerializeObject(libraryConnectors)); File.WriteAllText(settings.libraryConnectorsFilePath, JsonConvert.SerializeObject(libraryConnectors));
} }
@ -87,6 +95,10 @@ public abstract class GlobalBase
{ {
Log($"Removing {libraryType}"); Log($"Removing {libraryType}");
libraryConnectors.RemoveWhere(lc => lc.libraryType == libraryType); libraryConnectors.RemoveWhere(lc => lc.libraryType == libraryType);
while(IsFileInUse(settings.libraryConnectorsFilePath))
Thread.Sleep(100);
Log("Exporting libraryConnectors");
File.WriteAllText(settings.libraryConnectorsFilePath, JsonConvert.SerializeObject(libraryConnectors));
} }
protected bool IsFileInUse(string filePath) protected bool IsFileInUse(string filePath)

View File

@ -1,4 +1,4 @@
using System.Text; using System.Net;
using Tranga.MangaConnectors; using Tranga.MangaConnectors;
namespace Tranga.Jobs; namespace Tranga.Jobs;
@ -31,9 +31,13 @@ public class DownloadChapter : Job
{ {
Task downloadTask = new(delegate Task downloadTask = new(delegate
{ {
mangaConnector.DownloadChapter(chapter, this.progressToken); mangaConnector.CopyCoverFromCacheToDownloadLocation(chapter.parentManga);
HttpStatusCode success = mangaConnector.DownloadChapter(chapter, this.progressToken);
if (success == HttpStatusCode.OK)
{
UpdateLibraries(); UpdateLibraries();
SendNotifications("Chapter downloaded", $"{chapter.parentManga.sortName} - {chapter.chapterNumber}"); SendNotifications("Chapter downloaded", $"{chapter.parentManga.sortName} - {chapter.chapterNumber}");
}
}); });
downloadTask.Start(); downloadTask.Start();
return Array.Empty<Job>(); return Array.Empty<Job>();

View File

@ -36,6 +36,7 @@ public class DownloadNewChapters : Job
Chapter[] chapters = mangaConnector.GetNewChapters(manga, this.translatedLanguage); Chapter[] chapters = mangaConnector.GetNewChapters(manga, this.translatedLanguage);
this.progressToken.increments = chapters.Length; this.progressToken.increments = chapters.Length;
List<Job> jobs = new(); List<Job> jobs = new();
mangaConnector.CopyCoverFromCacheToDownloadLocation(manga);
foreach (Chapter chapter in chapters) foreach (Chapter chapter in chapters)
{ {
DownloadChapter downloadChapterJob = new(this, this.mangaConnector, chapter, parentJobId: this.id); DownloadChapter downloadChapterJob = new(this, this.mangaConnector, chapter, parentJobId: this.id);

View File

@ -59,14 +59,14 @@ public abstract class Job : GlobalBase
public void ResetProgress() public void ResetProgress()
{ {
this.progressToken.increments = this.progressToken.increments - this.progressToken.incrementsCompleted; this.progressToken.increments -= progressToken.incrementsCompleted;
this.lastExecution = DateTime.Now; this.lastExecution = DateTime.Now;
this.progressToken.Waiting();
} }
public void ExecutionEnqueue() public void ExecutionEnqueue()
{ {
this.progressToken.increments = this.progressToken.increments - this.progressToken.incrementsCompleted; this.progressToken.increments -= progressToken.incrementsCompleted;
this.lastExecution = recurrenceTime is not null ? DateTime.Now.Subtract((TimeSpan)recurrenceTime) : DateTime.UnixEpoch;
this.progressToken.Standby(); this.progressToken.Standby();
} }

View File

@ -14,6 +14,7 @@ public class JobBoss : GlobalBase
this.jobs = new(); this.jobs = new();
LoadJobsList(connectors); LoadJobsList(connectors);
this.mangaConnectorJobQueue = new(); this.mangaConnectorJobQueue = new();
Log($"Next job in {jobs.MinBy(job => job.nextExecution)?.nextExecution.Subtract(DateTime.Now)} {jobs.MinBy(job => job.nextExecution)?.id}");
} }
public void AddJob(Job job) public void AddJob(Job job)
@ -26,7 +27,7 @@ public class JobBoss : GlobalBase
{ {
Log($"Added {job}"); Log($"Added {job}");
this.jobs.Add(job); this.jobs.Add(job);
ExportJobsList(); ExportJob(job);
} }
} }
@ -54,9 +55,9 @@ public class JobBoss : GlobalBase
Log($"Removing {job}"); Log($"Removing {job}");
job.Cancel(); job.Cancel();
this.jobs.Remove(job); this.jobs.Remove(job);
if(job.subJobs is not null) if(job.subJobs is not null && job.subJobs.Any())
RemoveJobs(job.subJobs); RemoveJobs(job.subJobs);
ExportJobsList(); ExportJob(job);
} }
public void RemoveJobs(IEnumerable<Job?> jobsToRemove) public void RemoveJobs(IEnumerable<Job?> jobsToRemove)
@ -161,21 +162,39 @@ public class JobBoss : GlobalBase
cachedPublications.Add(ncJob.manga); cachedPublications.Add(ncJob.manga);
} }
public void ExportJob(Job job)
{
string jobFilePath = Path.Join(settings.jobsFolderPath, $"{job.id}.json");
if (!this.jobs.Any(jjob => jjob.id == job.id))
{
try
{
Log($"Deleting Job-file {jobFilePath}");
while(IsFileInUse(jobFilePath))
Thread.Sleep(10);
File.Delete(jobFilePath);
}
catch (Exception e)
{
Log(e.ToString());
}
}
else
{
Log($"Exporting Job {jobFilePath}");
string jobStr = JsonConvert.SerializeObject(job);
while(IsFileInUse(jobFilePath))
Thread.Sleep(10);
File.WriteAllText(jobFilePath, jobStr);
}
}
public void ExportJobsList() public void ExportJobsList()
{ {
Log("Exporting Jobs"); Log("Exporting Jobs");
foreach (Job job in this.jobs) foreach (Job job in this.jobs)
{ ExportJob(job);
string jobFilePath = Path.Join(settings.jobsFolderPath, $"{job.id}.json");
if (!File.Exists(jobFilePath))
{
string jobStr = JsonConvert.SerializeObject(job);
while(IsFileInUse(jobFilePath))
Thread.Sleep(10);
Log($"Exporting Job {jobFilePath}");
File.WriteAllText(jobFilePath, jobStr);
}
}
//Remove files with jobs not in this.jobs-list //Remove files with jobs not in this.jobs-list
Regex idRex = new (@"(.*)\.json"); Regex idRex = new (@"(.*)\.json");
@ -201,8 +220,7 @@ public class JobBoss : GlobalBase
public void CheckJobs() public void CheckJobs()
{ {
foreach (Job job in jobs.Where(job => job.nextExecution < DateTime.Now && !QueueContainsJob(job)).OrderBy(job => job.nextExecution)) AddJobsToQueue(jobs.Where(job => job.progressToken.state == ProgressToken.State.Waiting && job.nextExecution < DateTime.Now && !QueueContainsJob(job)).OrderBy(job => job.nextExecution));
AddJobToQueue(job);
foreach (Queue<Job> jobQueue in mangaConnectorJobQueue.Values) foreach (Queue<Job> jobQueue in mangaConnectorJobQueue.Values)
{ {
if(jobQueue.Count < 1) if(jobQueue.Count < 1)
@ -210,17 +228,11 @@ public class JobBoss : GlobalBase
Job queueHead = jobQueue.Peek(); Job queueHead = jobQueue.Peek();
if (queueHead.progressToken.state is ProgressToken.State.Complete or ProgressToken.State.Cancelled) if (queueHead.progressToken.state is ProgressToken.State.Complete or ProgressToken.State.Cancelled)
{ {
switch (queueHead) queueHead.ResetProgress();
{ if(!queueHead.recurring)
case DownloadChapter:
RemoveJob(queueHead); RemoveJob(queueHead);
break;
case DownloadNewChapters:
if(queueHead.recurring)
queueHead.progressToken.Complete();
break;
}
jobQueue.Dequeue(); jobQueue.Dequeue();
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().ToArray();

View File

@ -10,7 +10,7 @@ public class ProgressToken
public DateTime executionStarted { get; private set; } public DateTime executionStarted { get; private set; }
public TimeSpan timeRemaining => GetTimeRemaining(); public TimeSpan timeRemaining => GetTimeRemaining();
public enum State { Running, Complete, Standby, Cancelled } public enum State { Running, Complete, Standby, Cancelled, Waiting }
public State state { get; private set; } public State state { get; private set; }
public ProgressToken(int increments) public ProgressToken(int increments)
@ -18,7 +18,7 @@ public class ProgressToken
this.cancellationRequested = false; this.cancellationRequested = false;
this.increments = increments; this.increments = increments;
this.incrementsCompleted = 0; this.incrementsCompleted = 0;
this.state = State.Complete; this.state = State.Waiting;
this.executionStarted = DateTime.UnixEpoch; this.executionStarted = DateTime.UnixEpoch;
} }
@ -63,4 +63,9 @@ public class ProgressToken
{ {
state = State.Cancelled; state = State.Cancelled;
} }
public void Waiting()
{
state = State.Waiting;
}
} }

View File

@ -20,7 +20,10 @@ public abstract class LibraryConnector : GlobalBase
protected LibraryConnector(GlobalBase clone, string baseUrl, string auth, LibraryType libraryType) : base(clone) protected LibraryConnector(GlobalBase clone, string baseUrl, string auth, LibraryType libraryType) : base(clone)
{ {
this.baseUrl = baseUrl; Log($"Creating libraryConnector {Enum.GetName(libraryType)}");
if (!baseUrlRex.IsMatch(baseUrl))
throw new ArgumentException("Base url does not match pattern");
this.baseUrl = baseUrlRex.Match(baseUrl).Value;
this.auth = auth; this.auth = auth;
this.libraryType = libraryType; this.libraryType = libraryType;
} }

View File

@ -162,7 +162,7 @@ public abstract class MangaConnector : GlobalBase
Log($"Cloning cover {fileInCache} -> {newFilePath}"); Log($"Cloning cover {fileInCache} -> {newFilePath}");
File.Copy(fileInCache, newFilePath, true); File.Copy(fileInCache, newFilePath, true);
if(RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) if(RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
File.SetUnixFileMode(newFilePath, GroupRead | GroupWrite | OtherRead | OtherWrite | UserRead | UserWrite); File.SetUnixFileMode(newFilePath, GroupRead | GroupWrite | UserRead | UserWrite);
} }
/// <summary> /// <summary>
@ -193,10 +193,14 @@ public abstract class MangaConnector : GlobalBase
//Check if Publication Directory already exists //Check if Publication Directory already exists
string directoryPath = Path.GetDirectoryName(saveArchiveFilePath)!; string directoryPath = Path.GetDirectoryName(saveArchiveFilePath)!;
if (!Directory.Exists(directoryPath)) if (!Directory.Exists(directoryPath))
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
Directory.CreateDirectory(directoryPath,
UserRead | UserWrite | UserExecute | GroupRead | GroupWrite | GroupExecute );
else
Directory.CreateDirectory(directoryPath); Directory.CreateDirectory(directoryPath);
if (File.Exists(saveArchiveFilePath)) //Don't download twice. if (File.Exists(saveArchiveFilePath)) //Don't download twice.
return HttpStatusCode.OK; return HttpStatusCode.Created;
//Create a temporary folder to store images //Create a temporary folder to store images
string tempFolder = Directory.CreateTempSubdirectory().FullName; string tempFolder = Directory.CreateTempSubdirectory().FullName;
@ -229,7 +233,7 @@ public abstract class MangaConnector : GlobalBase
//ZIP-it and ship-it //ZIP-it and ship-it
ZipFile.CreateFromDirectory(tempFolder, saveArchiveFilePath); ZipFile.CreateFromDirectory(tempFolder, saveArchiveFilePath);
if(RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) if(RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
File.SetUnixFileMode(saveArchiveFilePath, GroupRead | GroupWrite | OtherRead | OtherWrite | UserRead | UserWrite); File.SetUnixFileMode(saveArchiveFilePath, UserRead | UserWrite | UserExecute | GroupRead | GroupWrite | GroupExecute);
Directory.Delete(tempFolder, true); //Cleanup Directory.Delete(tempFolder, true); //Cleanup
progressToken?.Complete(); progressToken?.Complete();

View File

@ -225,17 +225,27 @@ public class MangaDex : MangaConnector
public override HttpStatusCode DownloadChapter(Chapter chapter, ProgressToken? progressToken = null) public override HttpStatusCode DownloadChapter(Chapter chapter, ProgressToken? progressToken = null)
{ {
if (progressToken?.cancellationRequested ?? false) if (progressToken?.cancellationRequested ?? false)
{
progressToken?.Cancel();
return HttpStatusCode.RequestTimeout; return HttpStatusCode.RequestTimeout;
}
Manga chapterParentManga = chapter.parentManga; Manga chapterParentManga = chapter.parentManga;
Log($"Retrieving chapter-info {chapter} {chapterParentManga}"); Log($"Retrieving chapter-info {chapter} {chapterParentManga}");
//Request URLs for Chapter-Images //Request URLs for Chapter-Images
DownloadClient.RequestResult requestResult = DownloadClient.RequestResult requestResult =
downloadClient.MakeRequest($"https://api.mangadex.org/at-home/server/{chapter.url}?forcePort443=false'", (byte)RequestType.AtHomeServer); downloadClient.MakeRequest($"https://api.mangadex.org/at-home/server/{chapter.url}?forcePort443=false'", (byte)RequestType.AtHomeServer);
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300) if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300)
{
progressToken?.Cancel();
return requestResult.statusCode; return requestResult.statusCode;
}
JsonObject? result = JsonSerializer.Deserialize<JsonObject>(requestResult.result); JsonObject? result = JsonSerializer.Deserialize<JsonObject>(requestResult.result);
if (result is null) if (result is null)
{
progressToken?.Cancel();
return HttpStatusCode.NoContent; return HttpStatusCode.NoContent;
}
string baseUrl = result["baseUrl"]!.GetValue<string>(); string baseUrl = result["baseUrl"]!.GetValue<string>();
string hash = result["chapter"]!["hash"]!.GetValue<string>(); string hash = result["chapter"]!["hash"]!.GetValue<string>();

View File

@ -186,7 +186,11 @@ public class MangaKatana : MangaConnector
public override HttpStatusCode DownloadChapter(Chapter chapter, ProgressToken? progressToken = null) public override HttpStatusCode DownloadChapter(Chapter chapter, ProgressToken? progressToken = null)
{ {
if (progressToken?.cancellationRequested ?? false) if (progressToken?.cancellationRequested ?? false)
{
progressToken?.Cancel();
return HttpStatusCode.RequestTimeout; return HttpStatusCode.RequestTimeout;
}
Manga chapterParentManga = chapter.parentManga; Manga chapterParentManga = chapter.parentManga;
Log($"Retrieving chapter-info {chapter} {chapterParentManga}"); Log($"Retrieving chapter-info {chapter} {chapterParentManga}");
string requestUrl = chapter.url; string requestUrl = chapter.url;
@ -194,7 +198,10 @@ public class MangaKatana : MangaConnector
DownloadClient.RequestResult requestResult = DownloadClient.RequestResult requestResult =
downloadClient.MakeRequest(requestUrl, 1); downloadClient.MakeRequest(requestUrl, 1);
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300) if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300)
{
progressToken?.Cancel();
return requestResult.statusCode; return requestResult.statusCode;
}
string[] imageUrls = ParseImageUrlsFromHtml(requestUrl); string[] imageUrls = ParseImageUrlsFromHtml(requestUrl);

View File

@ -172,17 +172,28 @@ public class Manganato : MangaConnector
public override HttpStatusCode DownloadChapter(Chapter chapter, ProgressToken? progressToken = null) public override HttpStatusCode DownloadChapter(Chapter chapter, ProgressToken? progressToken = null)
{ {
if (progressToken?.cancellationRequested ?? false) if (progressToken?.cancellationRequested ?? false)
{
progressToken?.Cancel();
return HttpStatusCode.RequestTimeout; return HttpStatusCode.RequestTimeout;
}
Manga chapterParentManga = chapter.parentManga; Manga chapterParentManga = chapter.parentManga;
Log($"Retrieving chapter-info {chapter} {chapterParentManga}"); Log($"Retrieving chapter-info {chapter} {chapterParentManga}");
string requestUrl = chapter.url; string requestUrl = chapter.url;
DownloadClient.RequestResult requestResult = DownloadClient.RequestResult requestResult =
downloadClient.MakeRequest(requestUrl, 1); downloadClient.MakeRequest(requestUrl, 1);
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300) if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300)
{
progressToken?.Cancel();
return requestResult.statusCode; return requestResult.statusCode;
}
if (requestResult.htmlDocument is null) if (requestResult.htmlDocument is null)
{
progressToken?.Cancel();
return HttpStatusCode.InternalServerError; return HttpStatusCode.InternalServerError;
}
string[] imageUrls = ParseImageUrlsFromHtml(requestResult.htmlDocument); string[] imageUrls = ParseImageUrlsFromHtml(requestResult.htmlDocument);
string comicInfoPath = Path.GetTempFileName(); string comicInfoPath = Path.GetTempFileName();

View File

@ -179,16 +179,27 @@ public class Mangasee : MangaConnector
public override HttpStatusCode DownloadChapter(Chapter chapter, ProgressToken? progressToken = null) public override HttpStatusCode DownloadChapter(Chapter chapter, ProgressToken? progressToken = null)
{ {
if (progressToken?.cancellationRequested ?? false) if (progressToken?.cancellationRequested ?? false)
{
progressToken?.Cancel();
return HttpStatusCode.RequestTimeout; return HttpStatusCode.RequestTimeout;
}
Manga chapterParentManga = chapter.parentManga; Manga chapterParentManga = chapter.parentManga;
if (progressToken?.cancellationRequested ?? false) if (progressToken?.cancellationRequested ?? false)
{
progressToken?.Cancel();
return HttpStatusCode.RequestTimeout; return HttpStatusCode.RequestTimeout;
}
Log($"Retrieving chapter-info {chapter} {chapterParentManga}"); Log($"Retrieving chapter-info {chapter} {chapterParentManga}");
DownloadClient.RequestResult requestResult = this.downloadClient.MakeRequest(chapter.url, 1); DownloadClient.RequestResult requestResult = this.downloadClient.MakeRequest(chapter.url, 1);
if (requestResult.htmlDocument is null) if (requestResult.htmlDocument is null)
{
progressToken?.Cancel();
return HttpStatusCode.RequestTimeout; return HttpStatusCode.RequestTimeout;
}
HtmlDocument document = requestResult.htmlDocument; HtmlDocument document = requestResult.htmlDocument;
HtmlNode gallery = document.DocumentNode.Descendants("div").First(div => div.HasClass("ImageGallery")); HtmlNode gallery = document.DocumentNode.Descendants("div").First(div => div.HasClass("ImageGallery"));

View File

@ -134,8 +134,13 @@ public class Mangaworld: MangaConnector
{ {
List<Chapter> ret = new(); List<Chapter> ret = new();
foreach (HtmlNode volNode in document.DocumentNode.SelectNodes( HtmlNode chaptersWrapper =
"//div[contains(concat(' ',normalize-space(@class),' '),'chapters-wrapper')]//div[contains(concat(' ',normalize-space(@class),' '),'volume-element')]")) document.DocumentNode.SelectSingleNode(
"//div[contains(concat(' ',normalize-space(@class),' '),'chapters-wrapper')]");
if (chaptersWrapper.Descendants("div").Any(descendant => descendant.HasClass("volume-element")))
{
foreach (HtmlNode volNode in document.DocumentNode.SelectNodes("//div[contains(concat(' ',normalize-space(@class),' '),'volume-element')]"))
{ {
string volume = volNode.SelectNodes("div").First(node => node.HasClass("volume")).SelectSingleNode("p").InnerText.Split(' ')[^1]; string volume = volNode.SelectNodes("div").First(node => node.HasClass("volume")).SelectSingleNode("p").InnerText.Split(' ')[^1];
foreach (HtmlNode chNode in volNode.SelectNodes("div").First(node => node.HasClass("volume-chapters")).SelectNodes("div")) foreach (HtmlNode chNode in volNode.SelectNodes("div").First(node => node.HasClass("volume-chapters")).SelectNodes("div"))
@ -145,6 +150,16 @@ public class Mangaworld: MangaConnector
ret.Add(new Chapter(manga, null, volume, number, url)); ret.Add(new Chapter(manga, null, volume, number, url));
} }
} }
}
else
{
foreach (HtmlNode chNode in chaptersWrapper.SelectNodes("div").Where(node => node.HasClass("chapter")))
{
string number = chNode.SelectSingleNode("a").SelectSingleNode("span").InnerText.Split(" ")[^1];
string url = chNode.SelectSingleNode("a").GetAttributeValue("href", "");
ret.Add(new Chapter(manga, null, null, number, url));
}
}
ret.Reverse(); ret.Reverse();
return ret; return ret;
@ -153,17 +168,28 @@ public class Mangaworld: MangaConnector
public override HttpStatusCode DownloadChapter(Chapter chapter, ProgressToken? progressToken = null) public override HttpStatusCode DownloadChapter(Chapter chapter, ProgressToken? progressToken = null)
{ {
if (progressToken?.cancellationRequested ?? false) if (progressToken?.cancellationRequested ?? false)
{
progressToken?.Cancel();
return HttpStatusCode.RequestTimeout; return HttpStatusCode.RequestTimeout;
}
Manga chapterParentManga = chapter.parentManga; Manga chapterParentManga = chapter.parentManga;
Log($"Retrieving chapter-info {chapter} {chapterParentManga}"); Log($"Retrieving chapter-info {chapter} {chapterParentManga}");
string requestUrl = $"{chapter.url}?style=list"; string requestUrl = $"{chapter.url}?style=list";
DownloadClient.RequestResult requestResult = DownloadClient.RequestResult requestResult =
downloadClient.MakeRequest(requestUrl, 1); downloadClient.MakeRequest(requestUrl, 1);
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300) if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300)
{
progressToken?.Cancel();
return requestResult.statusCode; return requestResult.statusCode;
}
if (requestResult.htmlDocument is null) if (requestResult.htmlDocument is null)
{
progressToken?.Cancel();
return HttpStatusCode.InternalServerError; return HttpStatusCode.InternalServerError;
}
string[] imageUrls = ParseImageUrlsFromHtml(requestResult.htmlDocument); string[] imageUrls = ParseImageUrlsFromHtml(requestResult.htmlDocument);
string comicInfoPath = Path.GetTempFileName(); string comicInfoPath = Path.GetTempFileName();

View File

@ -13,7 +13,9 @@ public class Gotify : NotificationConnector
[JsonConstructor] [JsonConstructor]
public Gotify(GlobalBase clone, string endpoint, string appToken) : base(clone, NotificationConnectorType.Gotify) public Gotify(GlobalBase clone, string endpoint, string appToken) : base(clone, NotificationConnectorType.Gotify)
{ {
this.endpoint = endpoint; if (!baseUrlRex.IsMatch(endpoint))
throw new ArgumentException("endpoint does not match pattern");
this.endpoint = baseUrlRex.Match(endpoint).Value;;
this.appToken = appToken; this.appToken = appToken;
} }

View File

@ -6,6 +6,7 @@ public abstract class NotificationConnector : GlobalBase
protected NotificationConnector(GlobalBase clone, NotificationConnectorType notificationConnectorType) : base(clone) protected NotificationConnector(GlobalBase clone, NotificationConnectorType notificationConnectorType) : base(clone)
{ {
Log($"Creating notificationConnector {Enum.GetName(notificationConnectorType)}");
this.notificationConnectorType = notificationConnectorType; this.notificationConnectorType = notificationConnectorType;
} }

View File

@ -192,10 +192,10 @@ public class Server : GlobalBase
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).OrderBy(jjob => jjob.nextExecution));
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).OrderBy(jjob => ((DownloadNewChapters)jjob).manga.sortName));
break; break;
case "Settings": case "Settings":
SendResponse(HttpStatusCode.OK, response, settings); SendResponse(HttpStatusCode.OK, response, settings);

View File

@ -27,6 +27,8 @@ public partial class Tranga : GlobalBase
jobBoss = new(this, this._connectors); jobBoss = new(this, this._connectors);
StartJobBoss(); StartJobBoss();
this._server = new Server(this); this._server = new Server(this);
string[] emojis = { "(•‿•)", "(づ \u25d5‿\u25d5 )づ", "( \u02d8\u25bd\u02d8)っ\u2668", "=\uff3e\u25cf \u22cf \u25cf\uff3e=", "(ΦωΦ)", "(\u272a\u3268\u272a)", "( ノ・o・ )ノ", "(〜^\u2207^ )〜", "~(\u2267ω\u2266)~","૮ \u00b4• ﻌ \u00b4• ა", "(\u02c3ᆺ\u02c2)", "(=\ud83d\udf66 \u0f1d \ud83d\udf66=)"};
SendNotifications("Tranga Started", emojis[Random.Shared.Next(0,emojis.Length-1)]);
} }
public MangaConnector? GetConnector(string name) public MangaConnector? GetConnector(string name)
@ -70,11 +72,6 @@ public partial class Tranga : GlobalBase
jobBoss.CheckJobs(); jobBoss.CheckJobs();
Thread.Sleep(100); Thread.Sleep(100);
} }
foreach (MangaConnector connector in _connectors)
{
}
}); });
t.Start(); t.Start();
} }