Compare commits
No commits in common. "dcc12ec3ea912ac5c036bfca56e52b761f701795" and "2960a9b8f0a7d00142f90fa2df96f0c950c2c15a" have entirely different histories.
dcc12ec3ea
...
2960a9b8f0
21
.github/ISSUE_TEMPLATE/bug.yaml
vendored
21
.github/ISSUE_TEMPLATE/bug.yaml
vendored
@ -1,21 +0,0 @@
|
|||||||
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
|
|
15
Dockerfile
15
Dockerfile
@ -10,20 +10,7 @@ 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 .
|
||||||
USER 0
|
EXPOSE 6531
|
||||||
RUN chown 1000:1000 /publish
|
|
||||||
ENTRYPOINT ["dotnet", "/publish/Tranga.dll", "-c"]
|
ENTRYPOINT ["dotnet", "/publish/Tranga.dll", "-c"]
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
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;
|
||||||
@ -15,7 +14,6 @@ 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)
|
||||||
{
|
{
|
||||||
@ -54,12 +52,11 @@ public abstract class GlobalBase
|
|||||||
protected void AddNotificationConnector(NotificationConnector notificationConnector)
|
protected void AddNotificationConnector(NotificationConnector notificationConnector)
|
||||||
{
|
{
|
||||||
Log($"Adding {notificationConnector}");
|
Log($"Adding {notificationConnector}");
|
||||||
notificationConnectors.RemoveWhere(nc => nc.notificationConnectorType == notificationConnector.notificationConnectorType);
|
notificationConnectors.RemoveWhere(nc => nc.GetType() == notificationConnector.GetType());
|
||||||
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));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,10 +64,6 @@ 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()
|
||||||
@ -82,12 +75,11 @@ public abstract class GlobalBase
|
|||||||
protected void AddLibraryConnector(LibraryConnector libraryConnector)
|
protected void AddLibraryConnector(LibraryConnector libraryConnector)
|
||||||
{
|
{
|
||||||
Log($"Adding {libraryConnector}");
|
Log($"Adding {libraryConnector}");
|
||||||
libraryConnectors.RemoveWhere(lc => lc.libraryType == libraryConnector.libraryType);
|
libraryConnectors.RemoveWhere(lc => lc.GetType() == libraryConnector.GetType());
|
||||||
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));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,10 +87,6 @@ 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)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
using System.Net;
|
using System.Text;
|
||||||
using Tranga.MangaConnectors;
|
using Tranga.MangaConnectors;
|
||||||
|
|
||||||
namespace Tranga.Jobs;
|
namespace Tranga.Jobs;
|
||||||
@ -31,13 +31,9 @@ public class DownloadChapter : Job
|
|||||||
{
|
{
|
||||||
Task downloadTask = new(delegate
|
Task downloadTask = new(delegate
|
||||||
{
|
{
|
||||||
mangaConnector.CopyCoverFromCacheToDownloadLocation(chapter.parentManga);
|
mangaConnector.DownloadChapter(chapter, this.progressToken);
|
||||||
HttpStatusCode success = mangaConnector.DownloadChapter(chapter, this.progressToken);
|
UpdateLibraries();
|
||||||
if (success == HttpStatusCode.OK)
|
SendNotifications("Chapter downloaded", $"{chapter.parentManga.sortName} - {chapter.chapterNumber}");
|
||||||
{
|
|
||||||
UpdateLibraries();
|
|
||||||
SendNotifications("Chapter downloaded", $"{chapter.parentManga.sortName} - {chapter.chapterNumber}");
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
downloadTask.Start();
|
downloadTask.Start();
|
||||||
return Array.Empty<Job>();
|
return Array.Empty<Job>();
|
||||||
|
@ -36,7 +36,6 @@ 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);
|
||||||
|
@ -59,14 +59,14 @@ public abstract class Job : GlobalBase
|
|||||||
|
|
||||||
public void ResetProgress()
|
public void ResetProgress()
|
||||||
{
|
{
|
||||||
this.progressToken.increments -= progressToken.incrementsCompleted;
|
this.progressToken.increments = this.progressToken.increments - this.progressToken.incrementsCompleted;
|
||||||
this.lastExecution = DateTime.Now;
|
this.lastExecution = DateTime.Now;
|
||||||
this.progressToken.Waiting();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ExecutionEnqueue()
|
public void ExecutionEnqueue()
|
||||||
{
|
{
|
||||||
this.progressToken.increments -= progressToken.incrementsCompleted;
|
this.progressToken.increments = this.progressToken.increments - this.progressToken.incrementsCompleted;
|
||||||
|
this.lastExecution = recurrenceTime is not null ? DateTime.Now.Subtract((TimeSpan)recurrenceTime) : DateTime.UnixEpoch;
|
||||||
this.progressToken.Standby();
|
this.progressToken.Standby();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,7 +14,6 @@ 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)
|
||||||
@ -27,7 +26,7 @@ public class JobBoss : GlobalBase
|
|||||||
{
|
{
|
||||||
Log($"Added {job}");
|
Log($"Added {job}");
|
||||||
this.jobs.Add(job);
|
this.jobs.Add(job);
|
||||||
ExportJob(job);
|
ExportJobsList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,9 +54,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 && job.subJobs.Any())
|
if(job.subJobs is not null)
|
||||||
RemoveJobs(job.subJobs);
|
RemoveJobs(job.subJobs);
|
||||||
ExportJob(job);
|
ExportJobsList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RemoveJobs(IEnumerable<Job?> jobsToRemove)
|
public void RemoveJobs(IEnumerable<Job?> jobsToRemove)
|
||||||
@ -162,39 +161,21 @@ 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");
|
||||||
@ -220,7 +201,8 @@ public class JobBoss : GlobalBase
|
|||||||
|
|
||||||
public void CheckJobs()
|
public void CheckJobs()
|
||||||
{
|
{
|
||||||
AddJobsToQueue(jobs.Where(job => job.progressToken.state == ProgressToken.State.Waiting && job.nextExecution < DateTime.Now && !QueueContainsJob(job)).OrderBy(job => job.nextExecution));
|
foreach (Job job in jobs.Where(job => 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)
|
||||||
@ -228,11 +210,17 @@ 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)
|
||||||
{
|
{
|
||||||
queueHead.ResetProgress();
|
switch (queueHead)
|
||||||
if(!queueHead.recurring)
|
{
|
||||||
RemoveJob(queueHead);
|
case DownloadChapter:
|
||||||
|
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();
|
||||||
|
@ -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, Waiting }
|
public enum State { Running, Complete, Standby, Cancelled }
|
||||||
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.Waiting;
|
this.state = State.Complete;
|
||||||
this.executionStarted = DateTime.UnixEpoch;
|
this.executionStarted = DateTime.UnixEpoch;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,9 +63,4 @@ public class ProgressToken
|
|||||||
{
|
{
|
||||||
state = State.Cancelled;
|
state = State.Cancelled;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Waiting()
|
|
||||||
{
|
|
||||||
state = State.Waiting;
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -20,10 +20,7 @@ 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)
|
||||||
{
|
{
|
||||||
Log($"Creating libraryConnector {Enum.GetName(libraryType)}");
|
this.baseUrl = baseUrl;
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
@ -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 | UserRead | UserWrite);
|
File.SetUnixFileMode(newFilePath, GroupRead | GroupWrite | OtherRead | OtherWrite | UserRead | UserWrite);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -193,14 +193,10 @@ 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);
|
||||||
Directory.CreateDirectory(directoryPath,
|
|
||||||
UserRead | UserWrite | UserExecute | GroupRead | GroupWrite | GroupExecute );
|
|
||||||
else
|
|
||||||
Directory.CreateDirectory(directoryPath);
|
|
||||||
|
|
||||||
if (File.Exists(saveArchiveFilePath)) //Don't download twice.
|
if (File.Exists(saveArchiveFilePath)) //Don't download twice.
|
||||||
return HttpStatusCode.Created;
|
return HttpStatusCode.OK;
|
||||||
|
|
||||||
//Create a temporary folder to store images
|
//Create a temporary folder to store images
|
||||||
string tempFolder = Directory.CreateTempSubdirectory().FullName;
|
string tempFolder = Directory.CreateTempSubdirectory().FullName;
|
||||||
@ -233,7 +229,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, UserRead | UserWrite | UserExecute | GroupRead | GroupWrite | GroupExecute);
|
File.SetUnixFileMode(saveArchiveFilePath, GroupRead | GroupWrite | OtherRead | OtherWrite | UserRead | UserWrite);
|
||||||
Directory.Delete(tempFolder, true); //Cleanup
|
Directory.Delete(tempFolder, true); //Cleanup
|
||||||
|
|
||||||
progressToken?.Complete();
|
progressToken?.Complete();
|
||||||
|
@ -225,27 +225,17 @@ 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>();
|
||||||
|
@ -186,11 +186,7 @@ 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;
|
||||||
@ -198,10 +194,7 @@ 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);
|
||||||
|
|
||||||
|
@ -172,28 +172,17 @@ 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();
|
||||||
|
@ -179,27 +179,16 @@ 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"));
|
||||||
|
@ -134,30 +134,15 @@ public class Mangaworld: MangaConnector
|
|||||||
{
|
{
|
||||||
List<Chapter> ret = new();
|
List<Chapter> ret = new();
|
||||||
|
|
||||||
HtmlNode chaptersWrapper =
|
foreach (HtmlNode volNode in document.DocumentNode.SelectNodes(
|
||||||
document.DocumentNode.SelectSingleNode(
|
"//div[contains(concat(' ',normalize-space(@class),' '),'chapters-wrapper')]//div[contains(concat(' ',normalize-space(@class),' '),'volume-element')]"))
|
||||||
"//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];
|
||||||
{
|
foreach (HtmlNode chNode in volNode.SelectNodes("div").First(node => node.HasClass("volume-chapters")).SelectNodes("div"))
|
||||||
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"))
|
|
||||||
{
|
|
||||||
string number = chNode.SelectSingleNode("a").SelectSingleNode("span").InnerText.Split(" ")[^1];
|
|
||||||
string url = chNode.SelectSingleNode("a").GetAttributeValue("href", "");
|
|
||||||
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 number = chNode.SelectSingleNode("a").SelectSingleNode("span").InnerText.Split(" ")[^1];
|
||||||
string url = chNode.SelectSingleNode("a").GetAttributeValue("href", "");
|
string url = chNode.SelectSingleNode("a").GetAttributeValue("href", "");
|
||||||
ret.Add(new Chapter(manga, null, null, number, url));
|
ret.Add(new Chapter(manga, null, volume, number, url));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,28 +153,17 @@ 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();
|
||||||
|
@ -13,9 +13,7 @@ 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)
|
||||||
{
|
{
|
||||||
if (!baseUrlRex.IsMatch(endpoint))
|
this.endpoint = endpoint;
|
||||||
throw new ArgumentException("endpoint does not match pattern");
|
|
||||||
this.endpoint = baseUrlRex.Match(endpoint).Value;;
|
|
||||||
this.appToken = appToken;
|
this.appToken = appToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,7 +6,6 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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).OrderBy(jjob => jjob.nextExecution));
|
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).OrderBy(jjob => ((DownloadNewChapters)jjob).manga.sortName));
|
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);
|
||||||
|
@ -27,8 +27,6 @@ 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)
|
||||||
@ -72,6 +70,11 @@ public partial class Tranga : GlobalBase
|
|||||||
jobBoss.CheckJobs();
|
jobBoss.CheckJobs();
|
||||||
Thread.Sleep(100);
|
Thread.Sleep(100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach (MangaConnector connector in _connectors)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
});
|
});
|
||||||
t.Start();
|
t.Start();
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user