Compare commits

..

No commits in common. "14ba71005f06592fc552366ef2fb179ae5765e3b" and "ed79ee5d0f320e38e0af82f81ebb817496fda1ad" have entirely different histories.

9 changed files with 81 additions and 146 deletions

View File

@ -7,12 +7,7 @@ public class DownloadChapter : Job
{ {
public Chapter chapter { get; init; } public Chapter chapter { get; init; }
public DownloadChapter(GlobalBase clone, MangaConnector connector, Chapter chapter, DateTime lastExecution, string? parentJobId = null) : base(clone, connector, lastExecution, parentJobId: parentJobId) public DownloadChapter(GlobalBase clone, MangaConnector connector, Chapter chapter) : base(clone, connector)
{
this.chapter = chapter;
}
public DownloadChapter(GlobalBase clone, MangaConnector connector, Chapter chapter, string? parentJobId = null) : base(clone, connector, parentJobId: parentJobId)
{ {
this.chapter = chapter; this.chapter = chapter;
} }

View File

@ -7,14 +7,7 @@ public class DownloadNewChapters : Job
{ {
public Manga manga { get; init; } public Manga manga { get; init; }
public DownloadNewChapters(GlobalBase clone, MangaConnector connector, Manga manga, DateTime lastExecution, public DownloadNewChapters(GlobalBase clone, MangaConnector connector, Manga manga, bool recurring = false, TimeSpan? recurrence = null) : base (clone, connector, recurring, recurrence)
bool recurring = false, TimeSpan? recurrence = null, string? parentJobId = null) : base(clone, connector, lastExecution, recurring,
recurrence, parentJobId)
{
this.manga = manga;
}
public DownloadNewChapters(GlobalBase clone, MangaConnector connector, Manga manga, bool recurring = false, TimeSpan? recurrence = null, string? parentJobId = null) : base (clone, connector, recurring, recurrence, parentJobId)
{ {
this.manga = manga; this.manga = manga;
} }
@ -33,13 +26,13 @@ public class DownloadNewChapters : Job
{ {
Chapter[] chapters = mangaConnector.GetNewChapters(manga); Chapter[] chapters = mangaConnector.GetNewChapters(manga);
this.progressToken.increments = chapters.Length; this.progressToken.increments = chapters.Length;
List<Job> jobs = new(); List<Job> subJobs = new();
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);
jobs.Add(downloadChapterJob); subJobs.Add(downloadChapterJob);
} }
progressToken.Complete(); progressToken.Complete();
return jobs; return subJobs;
} }
} }

View File

@ -11,10 +11,8 @@ public abstract class Job : GlobalBase
public DateTime? lastExecution { get; private set; } public DateTime? lastExecution { get; private set; }
public DateTime nextExecution => NextExecution(); public DateTime nextExecution => NextExecution();
public string id => GetId(); public string id => GetId();
internal IEnumerable<Job>? subJobs { get; private set; }
public string? parentJobId { get; init; }
internal Job(GlobalBase clone, MangaConnector connector, bool recurring = false, TimeSpan? recurrenceTime = null, string? parentJobId = null) : base(clone) public Job(GlobalBase clone, MangaConnector connector, bool recurring = false, TimeSpan? recurrenceTime = null) : base(clone)
{ {
this.mangaConnector = connector; this.mangaConnector = connector;
this.progressToken = new ProgressToken(0); this.progressToken = new ProgressToken(0);
@ -23,70 +21,57 @@ public abstract class Job : GlobalBase
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) else if(recurring && recurrenceTime is not null)
this.lastExecution = DateTime.Now.Subtract((TimeSpan)recurrenceTime); this.lastExecution = DateTime.Now.Subtract((TimeSpan)recurrenceTime);
this.recurrenceTime = recurrenceTime ?? TimeSpan.Zero; this.recurrenceTime = recurrenceTime;
this.parentJobId = parentJobId;
}
internal Job(GlobalBase clone, MangaConnector connector, DateTime lastExecution, bool recurring = false,
TimeSpan? recurrenceTime = null, string? parentJobId = null) : base(clone)
{
this.mangaConnector = connector;
this.progressToken = new ProgressToken(0);
this.recurring = recurring;
if (recurring && recurrenceTime is null)
throw new ArgumentException("If recurrence is set to true, a recurrence time has to be provided.");
this.lastExecution = lastExecution;
this.recurrenceTime = recurrenceTime ?? TimeSpan.Zero;
this.parentJobId = parentJobId;
} }
protected abstract string GetId(); protected abstract string GetId();
public void AddSubJob(Job job) public Job(GlobalBase clone, MangaConnector connector, ProgressToken progressToken, bool recurring = false, TimeSpan? recurrenceTime = null) : base(clone)
{ {
subJobs ??= new List<Job>(); this.mangaConnector = connector;
subJobs = subJobs.Append(job); this.progressToken = progressToken;
this.recurring = recurring;
if (recurring && recurrenceTime is null)
throw new ArgumentException("If recurrence is set to true, a recurrence time has to be provided.");
this.recurrenceTime = recurrenceTime;
}
public Job(GlobalBase clone, MangaConnector connector, int taskIncrements, bool recurring = false, TimeSpan? recurrenceTime = null) : base(clone)
{
this.mangaConnector = connector;
this.progressToken = new ProgressToken(taskIncrements);
this.recurring = recurring;
if (recurring && recurrenceTime is null)
throw new ArgumentException("If recurrence is set to true, a recurrence time has to be provided.");
this.recurrenceTime = recurrenceTime;
} }
private DateTime NextExecution() private DateTime NextExecution()
{ {
if(recurrenceTime.HasValue && lastExecution.HasValue) if(recurring && recurrenceTime.HasValue && lastExecution.HasValue)
return lastExecution.Value.Add(recurrenceTime.Value); return lastExecution.Value.Add(recurrenceTime.Value);
if(recurrenceTime.HasValue && !lastExecution.HasValue) if(recurring && recurrenceTime.HasValue && !lastExecution.HasValue)
return DateTime.Now; return DateTime.Now;
return DateTime.MaxValue; return DateTime.MaxValue;
} }
public void ResetProgress() public void Reset()
{ {
this.progressToken.increments = this.progressToken.increments - this.progressToken.incrementsCompleted; this.progressToken = new ProgressToken(this.progressToken.increments);
this.lastExecution = DateTime.Now;
}
public void ExecutionEnqueue()
{
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();
} }
public void Cancel() public void Cancel()
{ {
Log($"Cancelling {this}");
this.progressToken.cancellationRequested = true; this.progressToken.cancellationRequested = true;
this.progressToken.Cancel(); this.progressToken.Complete();
this.lastExecution = DateTime.Now;
if(subJobs is not null)
foreach(Job subJob in subJobs)
subJob.Cancel();
} }
public IEnumerable<Job> ExecuteReturnSubTasks() public IEnumerable<Job> ExecuteReturnSubTasks()
{ {
progressToken.Start(); progressToken.Start();
subJobs = ExecuteReturnSubTasksInternal(); IEnumerable<Job> ret = ExecuteReturnSubTasksInternal();
lastExecution = DateTime.Now; lastExecution = DateTime.Now;
return subJobs; return ret;
} }
protected abstract IEnumerable<Job> ExecuteReturnSubTasksInternal(); protected abstract IEnumerable<Job> ExecuteReturnSubTasksInternal();

View File

@ -11,11 +11,7 @@ public class JobBoss : GlobalBase
public JobBoss(GlobalBase clone, HashSet<MangaConnector> connectors) : base(clone) public JobBoss(GlobalBase clone, HashSet<MangaConnector> connectors) : base(clone)
{ {
if (File.Exists(settings.jobsFilePath)) if (File.Exists(settings.jobsFilePath))
{
this.jobs = JsonConvert.DeserializeObject<HashSet<Job>>(File.ReadAllText(settings.jobsFilePath), new JobJsonConverter(this, new MangaConnectorJsonConverter(this, connectors)))!; this.jobs = JsonConvert.DeserializeObject<HashSet<Job>>(File.ReadAllText(settings.jobsFilePath), new JobJsonConverter(this, new MangaConnectorJsonConverter(this, connectors)))!;
foreach (Job job in this.jobs)
this.jobs.FirstOrDefault(jjob => jjob.id == job.parentJobId)?.AddSubJob(job);
}
else else
this.jobs = new(); this.jobs = new();
foreach (DownloadNewChapters ncJob in this.jobs.Where(job => job is DownloadNewChapters)) foreach (DownloadNewChapters ncJob in this.jobs.Where(job => job is DownloadNewChapters))
@ -33,16 +29,10 @@ public class JobBoss : GlobalBase
{ {
Log($"Added {job}"); Log($"Added {job}");
this.jobs.Add(job); this.jobs.Add(job);
ExportJobsList(); File.WriteAllText(settings.jobsFilePath, JsonConvert.SerializeObject(this.jobs));
} }
} }
public void AddJobs(IEnumerable<Job> jobsToAdd)
{
foreach (Job job in jobsToAdd)
AddJob(job);
}
public bool ContainsJobLike(Job job) public bool ContainsJobLike(Job job)
{ {
if (job is DownloadChapter dcJob) if (job is DownloadChapter dcJob)
@ -61,17 +51,13 @@ 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)
RemoveJobs(job.subJobs);
ExportJobsList();
} }
public void RemoveJobs(IEnumerable<Job?> jobsToRemove) public void RemoveJobs(IEnumerable<Job> jobsToRemove)
{ {
Log($"Removing {jobsToRemove.Count()} jobs."); Log($"Removing {jobsToRemove.Count()} jobs.");
foreach (Job? job in jobsToRemove) foreach (Job job in jobsToRemove)
if(job is not null) RemoveJob(job);
RemoveJob(job);
} }
public IEnumerable<Job> GetJobsLike(string? connectorName = null, string? internalId = null, string? chapterNumber = null) public IEnumerable<Job> GetJobsLike(string? connectorName = null, string? internalId = null, string? chapterNumber = null)
@ -101,10 +87,7 @@ public class JobBoss : GlobalBase
public IEnumerable<Job> GetJobsLike(MangaConnector? mangaConnector = null, Manga? publication = null, public IEnumerable<Job> GetJobsLike(MangaConnector? mangaConnector = null, Manga? publication = null,
Chapter? chapter = null) Chapter? chapter = null)
{ {
if (chapter is not null) return GetJobsLike(mangaConnector?.name, publication?.internalId, chapter?.chapterNumber);
return GetJobsLike(mangaConnector?.name, chapter.Value.parentManga.internalId, chapter?.chapterNumber);
else
return GetJobsLike(mangaConnector?.name, publication?.internalId);
} }
public Job? GetJobById(string jobId) public Job? GetJobById(string jobId)
@ -139,7 +122,6 @@ public class JobBoss : GlobalBase
Queue<Job> connectorJobQueue = mangaConnectorJobQueue[job.mangaConnector]; Queue<Job> connectorJobQueue = mangaConnectorJobQueue[job.mangaConnector];
if(!connectorJobQueue.Contains(job)) if(!connectorJobQueue.Contains(job))
connectorJobQueue.Enqueue(job); connectorJobQueue.Enqueue(job);
job.ExecutionEnqueue();
} }
public void AddJobsToQueue(IEnumerable<Job> jobs) public void AddJobsToQueue(IEnumerable<Job> jobs)
@ -148,42 +130,20 @@ public class JobBoss : GlobalBase
AddJobToQueue(job); AddJobToQueue(job);
} }
public void ExportJobsList()
{
Log($"Exporting {settings.jobsFilePath}");
while(IsFileInUse(settings.jobsFilePath))
Thread.Sleep(10);
File.WriteAllText(settings.jobsFilePath, JsonConvert.SerializeObject(this.jobs));
}
public void CheckJobs() public void CheckJobs()
{ {
foreach (Job job in jobs.Where(job => 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); AddJobToQueue(job);
foreach (Queue<Job> jobQueue in mangaConnectorJobQueue.Values) foreach (Queue<Job> jobQueue in mangaConnectorJobQueue.Values)
{ {
if(jobQueue.Count < 1)
continue;
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)
{ {
switch (queueHead) if(queueHead.recurring)
{ queueHead.Reset();
case DownloadChapter:
RemoveJob(queueHead);
break;
case DownloadNewChapters:
if(queueHead.recurring)
queueHead.progressToken.Complete();
break;
}
jobQueue.Dequeue(); jobQueue.Dequeue();
}else if (queueHead.progressToken.state is ProgressToken.State.Standby) }else if(queueHead.progressToken.state is ProgressToken.State.Standby)
{ AddJobsToQueue(jobQueue.Peek().ExecuteReturnSubTasks());
Job[] subJobs = jobQueue.Peek().ExecuteReturnSubTasks().ToArray();
AddJobs(subJobs);
AddJobsToQueue(subJobs);
}
} }
} }
} }

View File

@ -34,10 +34,8 @@ public class JobJsonConverter : JsonConverter
} }
}))!, }))!,
jo.GetValue("manga")!.ToObject<Manga>(), jo.GetValue("manga")!.ToObject<Manga>(),
jo.GetValue("lastExecution")!.ToObject<DateTime>(),
jo.GetValue("recurring")!.Value<bool>(), jo.GetValue("recurring")!.Value<bool>(),
jo.GetValue("recurrenceTime")!.ToObject<TimeSpan?>(), jo.GetValue("recurrenceTime")!.ToObject<TimeSpan?>());
jo.GetValue("parentJobId")!.Value<string?>());
} }
if (jo.ContainsKey("chapter"))//DownloadChapter if (jo.ContainsKey("chapter"))//DownloadChapter
@ -50,9 +48,7 @@ public class JobJsonConverter : JsonConverter
this._mangaConnectorJsonConverter this._mangaConnectorJsonConverter
} }
}))!, }))!,
jo.GetValue("chapter")!.ToObject<Chapter>(), jo.GetValue("chapter")!.ToObject<Chapter>());
DateTime.UnixEpoch,
jo.GetValue("parentJobId")!.Value<string?>());
} }
throw new Exception(); throw new Exception();

View File

@ -7,7 +7,7 @@ public class ProgressToken
public int incrementsCompleted { get; set; } public int incrementsCompleted { get; set; }
public float progress => GetProgress(); public float progress => GetProgress();
public enum State { Running, Complete, Standby, Cancelled } public enum State { Running, Complete, Standby }
public State state { get; private set; } public State state { get; private set; }
public ProgressToken(int increments) public ProgressToken(int increments)
@ -15,7 +15,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.Standby;
} }
private float GetProgress() private float GetProgress()
@ -32,11 +32,6 @@ public class ProgressToken
state = State.Complete; state = State.Complete;
} }
public void Standby()
{
state = State.Standby;
}
public void Start() public void Start()
{ {
state = State.Running; state = State.Running;
@ -46,9 +41,4 @@ public class ProgressToken
{ {
state = State.Complete; state = State.Complete;
} }
public void Cancel()
{
state = State.Cancelled;
}
} }

View File

@ -60,7 +60,7 @@ internal class DownloadClient : GlobalBase
if(referrer is not null) if(referrer is not null)
requestMessage.Headers.Referrer = new Uri(referrer); requestMessage.Headers.Referrer = new Uri(referrer);
_lastExecutedRateLimit[requestType] = DateTime.Now; _lastExecutedRateLimit[requestType] = DateTime.Now;
//Log($"Requesting {requestType} {url}"); Log($"Requesting {requestType} {url}");
response = Client.Send(requestMessage); response = Client.Send(requestMessage);
} }
catch (HttpRequestException e) catch (HttpRequestException e)

View File

@ -45,7 +45,7 @@ public class Server : GlobalBase
try try
{ {
HttpListenerContext context = this._listener.GetContext(); HttpListenerContext context = this._listener.GetContext();
//Log($"{context.Request.HttpMethod} {context.Request.Url} {context.Request.UserAgent}"); Log($"{context.Request.HttpMethod} {context.Request.Url} {context.Request.UserAgent}");
Task t = new(() => Task t = new(() =>
{ {
HandleRequest(context); HandleRequest(context);
@ -211,10 +211,9 @@ public class Server : GlobalBase
private void HandlePost(HttpListenerRequest request, HttpListenerResponse response) private void HandlePost(HttpListenerRequest request, HttpListenerResponse response)
{ {
Dictionary<string, string> requestVariables = GetRequestVariables(request.Url!.Query); Dictionary<string, string> requestVariables = GetRequestVariables(request.Url!.Query);
string? connectorName, internalId, jobId; string? connectorName, internalId;
MangaConnector connector; MangaConnector connector;
Manga manga; Manga manga;
Job? job;
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)
{ {
@ -249,8 +248,8 @@ public class Server : GlobalBase
SendResponse(HttpStatusCode.Accepted, response); SendResponse(HttpStatusCode.Accepted, response);
break; break;
case "Jobs/StartNow": case "Jobs/StartNow":
if (!requestVariables.TryGetValue("jobId", out jobId) || if (!requestVariables.TryGetValue("jobId", out string? jobId) ||
!_parent.jobBoss.TryGetJobById(jobId, out job)) !_parent.jobBoss.TryGetJobById(jobId, out Job? job))
{ {
SendResponse(HttpStatusCode.BadRequest, response); SendResponse(HttpStatusCode.BadRequest, response);
break; break;
@ -258,16 +257,6 @@ public class Server : GlobalBase
_parent.jobBoss.AddJobToQueue(job!); _parent.jobBoss.AddJobToQueue(job!);
SendResponse(HttpStatusCode.Accepted, response); SendResponse(HttpStatusCode.Accepted, response);
break; break;
case "Jobs/Cancel":
if (!requestVariables.TryGetValue("jobId", out jobId) ||
!_parent.jobBoss.TryGetJobById(jobId, out job))
{
SendResponse(HttpStatusCode.BadRequest, response);
break;
}
job!.Cancel();
SendResponse(HttpStatusCode.Accepted, response);
break;
case "Settings/UpdateDownloadLocation": case "Settings/UpdateDownloadLocation":
if (!requestVariables.TryGetValue("downloadLocation", out string? downloadLocation) || if (!requestVariables.TryGetValue("downloadLocation", out string? downloadLocation) ||
!requestVariables.TryGetValue("moveFiles", out string? moveFilesStr) || !requestVariables.TryGetValue("moveFiles", out string? moveFilesStr) ||
@ -373,7 +362,7 @@ public class Server : GlobalBase
switch (path) switch (path)
{ {
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);
@ -382,6 +371,33 @@ public class Server : GlobalBase
_parent.jobBoss.RemoveJob(job!); _parent.jobBoss.RemoveJob(job!);
SendResponse(HttpStatusCode.Accepted, response); SendResponse(HttpStatusCode.Accepted, response);
break; break;
case "Jobs/DownloadChapter":
if(!requestVariables.TryGetValue("connector", out connectorName) ||
!requestVariables.TryGetValue("internalId", out internalId) ||
!requestVariables.TryGetValue("chapterNumber", out string? chapterNumber) ||
_parent.GetConnector(connectorName) is null ||
_parent.GetPublicationById(internalId) is null)
{
SendResponse(HttpStatusCode.BadRequest, response);
break;
}
_parent.jobBoss.RemoveJobs(_parent.jobBoss.GetJobsLike(connectorName, internalId, chapterNumber));
SendResponse(HttpStatusCode.Accepted, response);
break;
case "Jobs/MonitorManga":
if(!requestVariables.TryGetValue("connector", out connectorName) ||
!requestVariables.TryGetValue("internalId", out internalId) ||
_parent.GetConnector(connectorName) is null ||
_parent.GetPublicationById(internalId) is null)
{
SendResponse(HttpStatusCode.BadRequest, response);
break;
}
connector = _parent.GetConnector(connectorName)!;
manga = (Manga)_parent.GetPublicationById(internalId)!;
_parent.jobBoss.RemoveJobs(_parent.jobBoss.GetJobsLike(connector, manga));
SendResponse(HttpStatusCode.Accepted, response);
break;
case "Jobs/DownloadNewChapters": case "Jobs/DownloadNewChapters":
if(!requestVariables.TryGetValue("connector", out connectorName) || if(!requestVariables.TryGetValue("connector", out connectorName) ||
!requestVariables.TryGetValue("internalId", out internalId) || !requestVariables.TryGetValue("internalId", out internalId) ||
@ -425,7 +441,7 @@ public class Server : GlobalBase
private void SendResponse(HttpStatusCode statusCode, HttpListenerResponse response, object? content = null) private void SendResponse(HttpStatusCode statusCode, HttpListenerResponse response, object? content = null)
{ {
//Log($"Response: {statusCode} {content}"); Log($"Response: {statusCode} {content}");
response.StatusCode = (int)statusCode; response.StatusCode = (int)statusCode;
response.AddHeader("Access-Control-Allow-Headers", "Content-Type, Accept, X-Requested-With"); response.AddHeader("Access-Control-Allow-Headers", "Content-Type, Accept, X-Requested-With");
response.AddHeader("Access-Control-Allow-Methods", "GET, POST, DELETE"); response.AddHeader("Access-Control-Allow-Methods", "GET, POST, DELETE");

View File

@ -65,7 +65,7 @@ public partial class Tranga : GlobalBase
while (keepRunning) while (keepRunning)
{ {
jobBoss.CheckJobs(); jobBoss.CheckJobs();
Thread.Sleep(100); Thread.Sleep(1000);
} }
}); });
t.Start(); t.Start();