diff --git a/CLI/CLI.csproj b/CLI/CLI.csproj index abf151f..b7368e8 100644 --- a/CLI/CLI.csproj +++ b/CLI/CLI.csproj @@ -8,6 +8,7 @@ + diff --git a/CLI/Program.cs b/CLI/Program.cs index 9543773..403dabb 100644 --- a/CLI/Program.cs +++ b/CLI/Program.cs @@ -1,6 +1,7 @@ using System.ComponentModel; using System.Diagnostics.CodeAnalysis; -using Logging; +using GlaxLogger; +using Microsoft.Extensions.Logging; using Spectre.Console; using Spectre.Console.Cli; using Tranga; @@ -22,16 +23,16 @@ internal sealed class TrangaCli : Command [DefaultValue(null)] public string? workingDirectory { get; init; } - [Description("Enables the file-logger")] - [CommandOption("-f")] - [DefaultValue(null)] - public bool? fileLogger { get; init; } - [Description("Path to save logfile to")] - [CommandOption("-l|--fPath")] + [CommandOption("-f|--fileLogger")] [DefaultValue(null)] public string? fileLoggerPath { get; init; } + [Description("LogLevel")] + [CommandOption("-l|--loglevel")] + [DefaultValue(LogLevel.Information)] + public LogLevel level { get; init; } + [Description("Port on which to run API on")] [CommandOption("-p|--port")] [DefaultValue(null)] @@ -40,12 +41,7 @@ internal sealed class TrangaCli : Command public override int Execute([NotNull] CommandContext context, [NotNull] Settings settings) { - List enabledLoggers = new(); - if(settings.fileLogger is true) - enabledLoggers.Add(Logger.LoggerType.FileLogger); - - string? logFilePath = settings.fileLoggerPath ?? ""; - Logger logger = new(enabledLoggers.ToArray(), Console.Out, Console.OutputEncoding, logFilePath); + Logger logger = new (settings.level, settings.fileLoggerPath, Console.Out); TrangaSettings trangaSettings = new (settings.downloadLocation, settings.workingDirectory, settings.apiPort); @@ -73,7 +69,6 @@ internal sealed class TrangaCli : Command .AddChoices(new[] { "CustomRequest", - "Log", "Exit" })); @@ -116,31 +111,6 @@ internal sealed class TrangaCli : Command AnsiConsole.WriteLine($"Response: {(int)response.StatusCode} {response.StatusCode}"); AnsiConsole.WriteLine(response.Content.ReadAsStringAsync().Result); break; - case "Log": - List lines = logger.Tail(10).ToList(); - Rows rows = new Rows(lines.Select(line => new Text(line))); - - AnsiConsole.Live(rows).Start(context => - { - bool running = true; - while (running) - { - string[] newLines = logger.GetNewLines(); - if (newLines.Length > 0) - { - lines.AddRange(newLines); - rows = new Rows(lines.Select(line => new Text(line))); - context.UpdateTarget(rows); - } - Thread.Sleep(100); - if (AnsiConsole.Console.Input.IsKeyAvailable()) - { - AnsiConsole.Console.Input.ReadKey(true); //Do not process input - running = false; - } - } - }); - break; case "Exit": exit = true; break; diff --git a/Tranga.sln b/Tranga.sln index 501c3c3..98cd9d2 100644 --- a/Tranga.sln +++ b/Tranga.sln @@ -2,8 +2,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tranga", ".\Tranga\Tranga.csproj", "{545E81B9-D96B-4C8F-A97F-2C02414DE566}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Logging", "Logging\Logging.csproj", "{415BE889-BB7D-426F-976F-8D977876A462}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CLI", "CLI\CLI.csproj", "{4324C816-F9D2-468F-8ED6-397FE2F0DCB3}" EndProject Global @@ -16,10 +14,6 @@ Global {545E81B9-D96B-4C8F-A97F-2C02414DE566}.Debug|Any CPU.Build.0 = Debug|Any CPU {545E81B9-D96B-4C8F-A97F-2C02414DE566}.Release|Any CPU.ActiveCfg = Release|Any CPU {545E81B9-D96B-4C8F-A97F-2C02414DE566}.Release|Any CPU.Build.0 = Release|Any CPU - {415BE889-BB7D-426F-976F-8D977876A462}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {415BE889-BB7D-426F-976F-8D977876A462}.Debug|Any CPU.Build.0 = Debug|Any CPU - {415BE889-BB7D-426F-976F-8D977876A462}.Release|Any CPU.ActiveCfg = Release|Any CPU - {415BE889-BB7D-426F-976F-8D977876A462}.Release|Any CPU.Build.0 = Release|Any CPU {4324C816-F9D2-468F-8ED6-397FE2F0DCB3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4324C816-F9D2-468F-8ED6-397FE2F0DCB3}.Debug|Any CPU.Build.0 = Debug|Any CPU {4324C816-F9D2-468F-8ED6-397FE2F0DCB3}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/Tranga.sln.DotSettings.user b/Tranga.sln.DotSettings.user new file mode 100644 index 0000000..4a9c6e9 --- /dev/null +++ b/Tranga.sln.DotSettings.user @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/Tranga/GlobalBase.cs b/Tranga/GlobalBase.cs index e9c68d0..6497b40 100644 --- a/Tranga/GlobalBase.cs +++ b/Tranga/GlobalBase.cs @@ -1,6 +1,7 @@ using System.Globalization; using System.Text.RegularExpressions; -using Logging; +using GlaxLogger; +using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Tranga.LibraryConnectors; using Tranga.NotificationConnectors; @@ -9,13 +10,13 @@ namespace Tranga; public abstract class GlobalBase { - protected Logger? logger { get; init; } - protected TrangaSettings settings { get; init; } - protected HashSet notificationConnectors { get; init; } - protected HashSet libraryConnectors { get; init; } - protected List cachedPublications { get; init; } - public static readonly NumberFormatInfo numberFormatDecimalPoint = new (){ NumberDecimalSeparator = "." }; - protected static readonly Regex baseUrlRex = new(@"https?:\/\/[0-9A-z\.-]+(:[0-9]+)?"); + internal Logger? logger { get; init; } + internal TrangaSettings settings { get; init; } + internal HashSet notificationConnectors { get; init; } + internal HashSet libraryConnectors { get; init; } + internal List cachedPublications { get; init; } + internal static readonly NumberFormatInfo numberFormatDecimalPoint = new (){ NumberDecimalSeparator = "." }; + internal static readonly Regex baseUrlRex = new(@"https?:\/\/[0-9A-z\.-]+(:[0-9]+)?"); protected GlobalBase(GlobalBase clone) { @@ -35,23 +36,23 @@ public abstract class GlobalBase this.cachedPublications = new(); } - protected void Log(string message) + internal void Log(string message) { - logger?.WriteLine(this.GetType().Name, message); + Log(this.GetType().Name, message); } - protected void Log(string fStr, params object?[] replace) + internal void Log(string objectType, string message) { - Log(string.Format(fStr, replace)); + logger?.LogInformation($"{objectType} | {message}"); } - protected void SendNotifications(string title, string text) + internal void SendNotifications(string title, string text) { foreach (NotificationConnector nc in notificationConnectors) nc.SendNotification(title, text); } - protected void AddNotificationConnector(NotificationConnector notificationConnector) + internal void AddNotificationConnector(NotificationConnector notificationConnector) { Log($"Adding {notificationConnector}"); notificationConnectors.RemoveWhere(nc => nc.notificationConnectorType == notificationConnector.notificationConnectorType); @@ -63,7 +64,7 @@ public abstract class GlobalBase File.WriteAllText(settings.notificationConnectorsFilePath, JsonConvert.SerializeObject(notificationConnectors)); } - protected void DeleteNotificationConnector(NotificationConnector.NotificationConnectorType notificationConnectorType) + internal void DeleteNotificationConnector(NotificationConnector.NotificationConnectorType notificationConnectorType) { Log($"Removing {notificationConnectorType}"); notificationConnectors.RemoveWhere(nc => nc.notificationConnectorType == notificationConnectorType); @@ -73,13 +74,13 @@ public abstract class GlobalBase File.WriteAllText(settings.notificationConnectorsFilePath, JsonConvert.SerializeObject(notificationConnectors)); } - protected void UpdateLibraries() + internal void UpdateLibraries() { foreach(LibraryConnector lc in libraryConnectors) lc.UpdateLibrary(); } - protected void AddLibraryConnector(LibraryConnector libraryConnector) + internal void AddLibraryConnector(LibraryConnector libraryConnector) { Log($"Adding {libraryConnector}"); libraryConnectors.RemoveWhere(lc => lc.libraryType == libraryConnector.libraryType); @@ -91,7 +92,7 @@ public abstract class GlobalBase File.WriteAllText(settings.libraryConnectorsFilePath, JsonConvert.SerializeObject(libraryConnectors)); } - protected void DeleteLibraryConnector(LibraryConnector.LibraryType libraryType) + internal void DeleteLibraryConnector(LibraryConnector.LibraryType libraryType) { Log($"Removing {libraryType}"); libraryConnectors.RemoveWhere(lc => lc.libraryType == libraryType); @@ -101,7 +102,7 @@ public abstract class GlobalBase File.WriteAllText(settings.libraryConnectorsFilePath, JsonConvert.SerializeObject(libraryConnectors)); } - protected bool IsFileInUse(string filePath) + internal bool IsFileInUse(string filePath) { if (!File.Exists(filePath)) return false; diff --git a/Tranga/Jobs/DownloadChapter.cs b/Tranga/Jobs/DownloadChapter.cs index 434736e..3d1261c 100644 --- a/Tranga/Jobs/DownloadChapter.cs +++ b/Tranga/Jobs/DownloadChapter.cs @@ -1,4 +1,6 @@ using System.Net; +using JobQueue; +using Microsoft.Extensions.Logging; using Tranga.MangaConnectors; namespace Tranga.Jobs; @@ -6,42 +8,27 @@ namespace Tranga.Jobs; public class DownloadChapter : Job { public Chapter chapter { get; init; } - - public DownloadChapter(GlobalBase clone, MangaConnector connector, Chapter chapter, DateTime lastExecution, string? parentJobId = null) : base(clone, JobType.DownloadChapterJob, connector, lastExecution, parentJobId: parentJobId) + + public DownloadChapter(GlobalBase clone, JobQueue queue, MangaConnector connector, Chapter chapter, int steps, string? jobId = null, string? parentJobId = null, ILogger? logger = null) : base (clone, queue, connector, JobType.DownloadChapterJob, TimeSpan.Zero, TimeSpan.FromSeconds(clone.settings.jobTimeout), steps, jobId, parentJobId, logger) { this.chapter = chapter; - } - - public DownloadChapter(GlobalBase clone, MangaConnector connector, Chapter chapter, string? parentJobId = null) : base(clone, JobType.DownloadChapterJob, connector, parentJobId: parentJobId) - { - this.chapter = chapter; - } - - protected override string GetId() - { - return $"{GetType()}-{chapter.parentManga.internalId}-{chapter.chapterNumber}"; - } - - public override string ToString() - { - return $"{id} Chapter: {chapter}"; - } - - protected override IEnumerable ExecuteReturnSubTasksInternal(JobBoss jobBoss) - { - Task downloadTask = new(delegate + if (jobId is null) { - mangaConnector.CopyCoverFromCacheToDownloadLocation(chapter.parentManga); - HttpStatusCode success = mangaConnector.DownloadChapter(chapter, this.progressToken); - chapter.parentManga.UpdateLatestDownloadedChapter(chapter); - if (success == HttpStatusCode.OK) - { - UpdateLibraries(); - SendNotifications("Chapter downloaded", $"{chapter.parentManga.sortName} - {chapter.chapterNumber}"); - } - }); - downloadTask.Start(); - return Array.Empty(); + const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + this.JobId = $"{this.GetType().Name}-{connector.name}-{chapter}-{new string(Enumerable.Repeat(chars, 4).Select(s => s[Random.Shared.Next(s.Length)]).ToArray())}"; + } + } + + protected override void Execute(CancellationToken cancellationToken) + { + mangaConnector.CopyCoverFromCacheToDownloadLocation(chapter.parentManga); + HttpStatusCode success = mangaConnector.DownloadChapter(chapter, this.ProgressToken); + chapter.parentManga.UpdateLatestDownloadedChapter(chapter); + if (success == HttpStatusCode.OK) + { + this.GlobalBase.UpdateLibraries(); + this.GlobalBase.SendNotifications("Chapter downloaded", $"{chapter.parentManga.sortName} - {chapter.chapterNumber}"); + } } public override bool Equals(object? obj) diff --git a/Tranga/Jobs/DownloadNewChapters.cs b/Tranga/Jobs/DownloadNewChapters.cs index ebcbde7..ec2b2a1 100644 --- a/Tranga/Jobs/DownloadNewChapters.cs +++ b/Tranga/Jobs/DownloadNewChapters.cs @@ -1,59 +1,39 @@ -using Tranga.MangaConnectors; +using JobQueue; +using Microsoft.Extensions.Logging; +using Tranga.MangaConnectors; namespace Tranga.Jobs; -public class DownloadNewChapters : Job +public class DownloadNewChapter : Job { public Manga manga { get; set; } public string translatedLanguage { get; init; } - - public DownloadNewChapters(GlobalBase clone, MangaConnector connector, Manga manga, DateTime lastExecution, - bool recurring = false, TimeSpan? recurrence = null, string? parentJobId = null, string translatedLanguage = "en") : base(clone, JobType.DownloadNewChaptersJob, connector, lastExecution, recurring, - recurrence, parentJobId) - { - this.manga = manga; - this.translatedLanguage = translatedLanguage; - } - - public DownloadNewChapters(GlobalBase clone, MangaConnector connector, Manga manga, bool recurring = false, TimeSpan? recurrence = null, string? parentJobId = null, string translatedLanguage = "en") : base (clone, JobType.DownloadNewChaptersJob, connector, recurring, recurrence, parentJobId) + + public DownloadNewChapter(GlobalBase clone, JobQueue queue, MangaConnector connector, Manga manga, TimeSpan interval, int steps, string? jobId = null, string? parentJobId = null, ILogger? logger = null, string translatedLanguage = "en") : base (clone, queue, connector, JobType.DownloadNewChaptersJob, interval, TimeSpan.FromSeconds(clone.settings.jobTimeout), steps, jobId, parentJobId, logger) { this.manga = manga; this.translatedLanguage = translatedLanguage; + if (jobId is null) + { + const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + this.JobId = $"{this.GetType().Name}-{connector.name}-{manga.sortName}-{new string(Enumerable.Repeat(chars, 4).Select(s => s[Random.Shared.Next(s.Length)]).ToArray())}"; + } } - protected override string GetId() + protected override void Execute(CancellationToken cancellationToken) { - return $"{GetType()}-{manga.internalId}"; - } - - public override string ToString() - { - return $"{id} Manga: {manga}"; - } - - protected override IEnumerable ExecuteReturnSubTasksInternal(JobBoss jobBoss) - { - manga.SaveSeriesInfoJson(settings.downloadLocation); + manga.SaveSeriesInfoJson(GlobalBase.settings.downloadLocation); Chapter[] chapters = mangaConnector.GetNewChapters(manga, this.translatedLanguage); - this.progressToken.increments = chapters.Length; - List jobs = new(); + this.ProgressToken.SetSteps(chapters.Length); mangaConnector.CopyCoverFromCacheToDownloadLocation(manga); foreach (Chapter chapter in chapters) { - DownloadChapter downloadChapterJob = new(this, this.mangaConnector, chapter, parentJobId: this.id); - jobs.Add(downloadChapterJob); + DownloadChapter downloadChapterJob = new(this.GlobalBase, Queue, mangaConnector, chapter, 0, null, + this.JobId, this.logger); + Queue.AddJob(mangaConnector, downloadChapterJob); } - UpdateMetadata updateMetadataJob = new(this, this.mangaConnector, this.manga, parentJobId: this.id); - jobs.Add(updateMetadataJob); - progressToken.Complete(); - return jobs; - } - - public override bool Equals(object? obj) - { - if (obj is not DownloadNewChapters otherJob) - return false; - return otherJob.mangaConnector == this.mangaConnector && - otherJob.manga.Equals(this.manga); + UpdateMetadata updateMetadataJob = new(this.GlobalBase, Queue, mangaConnector, manga, null, this.JobId, this.logger); + Queue.AddJob(mangaConnector, updateMetadataJob); + this.ProgressToken.MarkFinished(); } } \ No newline at end of file diff --git a/Tranga/Jobs/Job.cs b/Tranga/Jobs/Job.cs index deefd77..56dde22 100644 --- a/Tranga/Jobs/Job.cs +++ b/Tranga/Jobs/Job.cs @@ -1,98 +1,23 @@ -using Tranga.MangaConnectors; +using JobQueue; +using Tranga.MangaConnectors; +using Microsoft.Extensions.Logging; namespace Tranga.Jobs; -public abstract class Job : GlobalBase +public abstract class Job : Job { + protected readonly GlobalBase GlobalBase; public MangaConnector mangaConnector { get; init; } - public ProgressToken progressToken { get; private set; } - public bool recurring { get; init; } - public TimeSpan? recurrenceTime { get; set; } - public DateTime? lastExecution { get; private set; } - public DateTime nextExecution => NextExecution(); - public string id => GetId(); - internal IEnumerable? subJobs { get; private set; } - public string? parentJobId { get; init; } public enum JobType : byte { DownloadChapterJob, DownloadNewChaptersJob, UpdateMetaDataJob } public JobType jobType; + protected ILogger? logger; - internal Job(GlobalBase clone, JobType jobType, MangaConnector connector, bool recurring = false, TimeSpan? recurrenceTime = null, string? parentJobId = null) : base(clone) + public Job(GlobalBase clone, JobQueue queue, MangaConnector connector, JobType jobType, TimeSpan interval, TimeSpan maximumTimeBetweenUpdates, int steps, string? jobId = null, string? parentJobId = null, ILogger? logger = null) : base(queue, interval, maximumTimeBetweenUpdates, steps, jobId, parentJobId, logger) { - this.jobType = jobType; + this.GlobalBase = clone; + this.logger = logger; 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."); - else if(recurring && recurrenceTime is not null) - this.lastExecution = DateTime.Now.Subtract((TimeSpan)recurrenceTime); - this.recurrenceTime = recurrenceTime ?? TimeSpan.Zero; - this.parentJobId = parentJobId; - } - - internal Job(GlobalBase clone, JobType jobType, MangaConnector connector, DateTime lastExecution, bool recurring = false, - TimeSpan? recurrenceTime = null, string? parentJobId = null) : base(clone) - { this.jobType = jobType; - 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(); - - public void AddSubJob(Job job) - { - subJobs ??= new List(); - subJobs = subJobs.Append(job); - } - - private DateTime NextExecution() - { - if(recurrenceTime.HasValue && lastExecution.HasValue) - return lastExecution.Value.Add(recurrenceTime.Value); - if(recurrenceTime.HasValue && !lastExecution.HasValue) - return DateTime.Now; - return DateTime.MaxValue; - } - - public void ResetProgress() - { - this.progressToken.increments -= progressToken.incrementsCompleted; - this.lastExecution = DateTime.Now; - this.progressToken.Waiting(); - } - - public void ExecutionEnqueue() - { - this.progressToken.increments -= progressToken.incrementsCompleted; - this.progressToken.Standby(); - } - - public void Cancel() - { - Log($"Cancelling {this}"); - this.progressToken.cancellationRequested = true; - this.progressToken.Cancel(); - this.lastExecution = DateTime.Now; - if(subJobs is not null) - foreach(Job subJob in subJobs) - subJob.Cancel(); - } - - public IEnumerable ExecuteReturnSubTasks(JobBoss jobBoss) - { - progressToken.Start(); - subJobs = ExecuteReturnSubTasksInternal(jobBoss); - lastExecution = DateTime.Now; - return subJobs; - } - - protected abstract IEnumerable ExecuteReturnSubTasksInternal(JobBoss jobBoss); } \ No newline at end of file diff --git a/Tranga/Jobs/JobBoss.cs b/Tranga/Jobs/JobBoss.cs deleted file mode 100644 index f603313..0000000 --- a/Tranga/Jobs/JobBoss.cs +++ /dev/null @@ -1,258 +0,0 @@ -using System.Text.RegularExpressions; -using Newtonsoft.Json; -using Tranga.MangaConnectors; - -namespace Tranga.Jobs; - -public class JobBoss : GlobalBase -{ - public HashSet jobs { get; init; } - private Dictionary> mangaConnectorJobQueue { get; init; } - - public JobBoss(GlobalBase clone, HashSet connectors) : base(clone) - { - this.jobs = new(); - LoadJobsList(connectors); - 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) - { - if (ContainsJobLike(job)) - { - Log($"Already Contains Job {job}"); - } - else - { - Log($"Added {job}"); - this.jobs.Add(job); - UpdateJobFile(job); - } - } - - public void AddJobs(IEnumerable jobsToAdd) - { - foreach (Job job in jobsToAdd) - AddJob(job); - } - - /// - /// Compares contents of the provided job and all current jobs - /// Does not check if objects are the same - /// - public bool ContainsJobLike(Job job) - { - return this.jobs.Any(existingJob => existingJob.Equals(job)); - } - - public void RemoveJob(Job job) - { - Log($"Removing {job}"); - job.Cancel(); - this.jobs.Remove(job); - if(job.subJobs is not null && job.subJobs.Any()) - RemoveJobs(job.subJobs); - UpdateJobFile(job); - } - - public void RemoveJobs(IEnumerable jobsToRemove) - { - List toRemove = jobsToRemove.ToList(); //Prevent multiple enumeration - Log($"Removing {toRemove.Count()} jobs."); - foreach (Job? job in toRemove) - if(job is not null) - RemoveJob(job); - } - - public IEnumerable GetJobsLike(string? connectorName = null, string? internalId = null, string? chapterNumber = null) - { - IEnumerable ret = this.jobs; - if (connectorName is not null) - ret = ret.Where(job => job.mangaConnector.name == connectorName); - - if (internalId is not null && chapterNumber is not null) - ret = ret.Where(jjob => - { - if (jjob is not DownloadChapter job) - return false; - return job.chapter.parentManga.internalId == internalId && - job.chapter.chapterNumber == chapterNumber; - }); - else if (internalId is not null) - ret = ret.Where(jjob => - { - if (jjob is not DownloadNewChapters job) - return false; - return job.manga.internalId == internalId; - }); - return ret; - } - - public IEnumerable GetJobsLike(MangaConnector? mangaConnector = null, Manga? publication = null, - Chapter? chapter = null) - { - if (chapter is not null) - return GetJobsLike(mangaConnector?.name, chapter.Value.parentManga.internalId, chapter.Value.chapterNumber); - else - return GetJobsLike(mangaConnector?.name, publication?.internalId); - } - - public Job? GetJobById(string jobId) - { - if (this.jobs.FirstOrDefault(jjob => jjob.id == jobId) is { } job) - return job; - return null; - } - - public bool TryGetJobById(string jobId, out Job? job) - { - if (this.jobs.FirstOrDefault(jjob => jjob.id == jobId) is { } ret) - { - job = ret; - return true; - } - - job = null; - return false; - } - - private bool QueueContainsJob(Job job) - { - if (mangaConnectorJobQueue.TryAdd(job.mangaConnector, new Queue()))//If we can add the queue, there is certainly no job in it - return true; - return mangaConnectorJobQueue[job.mangaConnector].Contains(job); - } - - public void AddJobToQueue(Job job) - { - Log($"Adding Job to Queue. {job}"); - if(!QueueContainsJob(job)) - mangaConnectorJobQueue[job.mangaConnector].Enqueue(job); - job.ExecutionEnqueue(); - } - - private void AddJobsToQueue(IEnumerable newJobs) - { - foreach(Job job in newJobs) - AddJobToQueue(job); - } - - private void LoadJobsList(HashSet connectors) - { - if (!Directory.Exists(settings.jobsFolderPath)) //No jobs to load - { - Directory.CreateDirectory(settings.jobsFolderPath); - return; - } - Regex idRex = new (@"(.*)\.json"); - - //Load json-job-files - foreach (FileInfo file in new DirectoryInfo(settings.jobsFolderPath).EnumerateFiles().Where(fileInfo => idRex.IsMatch(fileInfo.Name))) - { - Job job = JsonConvert.DeserializeObject(File.ReadAllText(file.FullName), - new JobJsonConverter(this, new MangaConnectorJsonConverter(this, connectors)))!; - this.jobs.Add(job); - } - - //Connect jobs to parent-jobs and add Publications to cache - foreach (Job job in this.jobs) - { - this.jobs.FirstOrDefault(jjob => jjob.id == job.parentJobId)?.AddSubJob(job); - if (job is DownloadNewChapters dncJob) - cachedPublications.Add(dncJob.manga); - } - - HashSet coverFileNames = cachedPublications.Select(manga => manga.coverFileNameInCache!).ToHashSet(); - foreach (string fileName in Directory.GetFiles(settings.coverImageCache)) - { - if(!coverFileNames.Any(existingManga => fileName.Contains(existingManga))) - File.Delete(fileName); - } - } - - private void UpdateJobFile(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); - } - } - - private void UpdateAllJobFiles() - { - Log("Exporting Jobs"); - foreach (Job job in this.jobs) - UpdateJobFile(job); - - //Remove files with jobs not in this.jobs-list - Regex idRex = new (@"(.*)\.json"); - foreach (FileInfo file in new DirectoryInfo(settings.jobsFolderPath).EnumerateFiles()) - { - if (idRex.IsMatch(file.Name)) - { - string id = idRex.Match(file.Name).Groups[1].Value; - if (!this.jobs.Any(job => job.id == id)) - { - try - { - file.Delete(); - } - catch (Exception e) - { - Log(e.ToString()); - } - } - } - } - } - - public void CheckJobs() - { - AddJobsToQueue(jobs.Where(job => job.progressToken.state == ProgressToken.State.Waiting && job.nextExecution < DateTime.Now && !QueueContainsJob(job)).OrderBy(job => job.nextExecution)); - foreach (Queue jobQueue in mangaConnectorJobQueue.Values) - { - if(jobQueue.Count < 1) - continue; - Job queueHead = jobQueue.Peek(); - if (queueHead.progressToken.state is ProgressToken.State.Complete or ProgressToken.State.Cancelled) - { - if(!queueHead.recurring) - RemoveJob(queueHead); - else - queueHead.ResetProgress(); - 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) - { - Job[] subJobs = jobQueue.Peek().ExecuteReturnSubTasks(this).ToArray(); - AddJobs(subJobs); - AddJobsToQueue(subJobs); - }else if (queueHead.progressToken.state is ProgressToken.State.Running && DateTime.Now.Subtract(queueHead.progressToken.lastUpdate) > TimeSpan.FromMinutes(5)) - { - Log($"{queueHead} inactive for more than 5 minutes. Cancelling."); - queueHead.Cancel(); - } - } - } - } \ No newline at end of file diff --git a/Tranga/Jobs/JobJsonConverter.cs b/Tranga/Jobs/JobJsonConverter.cs index 5b12143..13b3ccb 100644 --- a/Tranga/Jobs/JobJsonConverter.cs +++ b/Tranga/Jobs/JobJsonConverter.cs @@ -1,4 +1,6 @@ -using Newtonsoft.Json; +using JobQueue; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Tranga.MangaConnectors; @@ -8,11 +10,15 @@ public class JobJsonConverter : JsonConverter { private GlobalBase _clone; private MangaConnectorJsonConverter _mangaConnectorJsonConverter; + private JobQueue queue; + private ILogger? logger; - internal JobJsonConverter(GlobalBase clone, MangaConnectorJsonConverter mangaConnectorJsonConverter) + internal JobJsonConverter(GlobalBase clone, MangaConnectorJsonConverter mangaConnectorJsonConverter, JobQueue queue, ILogger? logger = null) { this._clone = clone; this._mangaConnectorJsonConverter = mangaConnectorJsonConverter; + this.queue = queue; + this.logger = logger; } public override bool CanConvert(Type objectType) @@ -23,53 +29,52 @@ public class JobJsonConverter : JsonConverter public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) { JObject jo = JObject.Load(reader); - - if (jo.ContainsKey("jobType") && jo["jobType"]!.Value() == (byte)Job.JobType.UpdateMetaDataJob) - { - return new UpdateMetadata(this._clone, - jo.GetValue("mangaConnector")!.ToObject(JsonSerializer.Create(new JsonSerializerSettings() + Job.JobType? jobType = (Job.JobType?)jo["jobType"]?.Value(); + MangaConnector? mangaConnector = jo.GetValue("mangaConnector")?.ToObject(JsonSerializer.Create( + new JsonSerializerSettings() + { + Converters = { - Converters = - { - this._mangaConnectorJsonConverter - } - }))!, - jo.GetValue("manga")!.ToObject(), - jo.GetValue("parentJobId")!.Value()); - }else if ((jo.ContainsKey("jobType") && jo["jobType"]!.Value() == (byte)Job.JobType.DownloadNewChaptersJob) || jo.ContainsKey("translatedLanguage"))//TODO change to jobType + this._mangaConnectorJsonConverter + } + })); + if(mangaConnector is null) + throw new JsonException("Could not deserialize this type"); + + switch (jobType) { - DateTime lastExecution = jo.GetValue("lastExecution") is {} le - ? le.ToObject() - : DateTime.UnixEpoch; //TODO do null checks on all variables - return new DownloadNewChapters(this._clone, - jo.GetValue("mangaConnector")!.ToObject(JsonSerializer.Create(new JsonSerializerSettings() - { - Converters = - { - this._mangaConnectorJsonConverter - } - }))!, - jo.GetValue("manga")!.ToObject(), - lastExecution, - jo.GetValue("recurring")!.Value(), - jo.GetValue("recurrenceTime")!.ToObject(), - jo.GetValue("parentJobId")!.Value()); - }else if ((jo.ContainsKey("jobType") && jo["jobType"]!.Value() == (byte)Job.JobType.DownloadChapterJob) || jo.ContainsKey("chapter"))//TODO change to jobType - { - return new DownloadChapter(this._clone, - jo.GetValue("mangaConnector")!.ToObject(JsonSerializer.Create(new JsonSerializerSettings() - { - Converters = - { - this._mangaConnectorJsonConverter - } - }))!, - jo.GetValue("chapter")!.ToObject(), - DateTime.UnixEpoch, - jo.GetValue("parentJobId")!.Value()); + case Job.JobType.UpdateMetaDataJob: + return new UpdateMetadata(_clone, + queue, + mangaConnector, + jo.GetValue("manga")!.ToObject(), + jo.GetValue("jobId")!.Value(), + jo.GetValue("parentJobId")!.Value(), + logger); + case Job.JobType.DownloadChapterJob: + return new DownloadChapter(_clone, + queue, + mangaConnector, + jo.GetValue("chapter")!.ToObject(), + jo.GetValue("steps")!.Value(), + jo.GetValue("jobId")!.Value(), + jo.GetValue("parentJobId")!.Value(), + logger); + break; + case Job.JobType.DownloadNewChaptersJob: + return new DownloadNewChapter(_clone, + queue, + mangaConnector, + jo.GetValue("manga")!.ToObject(), + jo.GetValue("interval")!.ToObject(), + jo.GetValue("steps")!.Value(), + jo.GetValue("jobId")!.Value(), + jo.GetValue("parentJobId")!.Value(), + logger); + break; + default: + throw new JsonException("Could not deserialize this type"); } - - throw new Exception(); } public override bool CanWrite => false; diff --git a/Tranga/Jobs/ProgressToken.cs b/Tranga/Jobs/ProgressToken.cs deleted file mode 100644 index e718c7d..0000000 --- a/Tranga/Jobs/ProgressToken.cs +++ /dev/null @@ -1,78 +0,0 @@ -namespace Tranga.Jobs; - -public class ProgressToken -{ - public bool cancellationRequested { get; set; } - public int increments { get; set; } - public int incrementsCompleted { get; set; } - public float progress => GetProgress(); - public DateTime lastUpdate { get; private set; } - public DateTime executionStarted { get; private set; } - public TimeSpan timeRemaining => GetTimeRemaining(); - - public enum State { Running, Complete, Standby, Cancelled, Waiting } - public State state { get; private set; } - - public ProgressToken(int increments) - { - this.cancellationRequested = false; - this.increments = increments; - this.incrementsCompleted = 0; - this.state = State.Waiting; - this.executionStarted = DateTime.UnixEpoch; - this.lastUpdate = DateTime.UnixEpoch; - } - - private float GetProgress() - { - if(increments > 0 && incrementsCompleted > 0) - return incrementsCompleted / (float)increments; - return 0; - } - - private TimeSpan GetTimeRemaining() - { - if (increments > 0 && incrementsCompleted > 0) - return DateTime.Now.Subtract(this.executionStarted).Divide(incrementsCompleted).Multiply(increments - incrementsCompleted); - return TimeSpan.MaxValue; - } - - public void Increment() - { - this.lastUpdate = DateTime.Now; - this.incrementsCompleted++; - if (incrementsCompleted > increments) - state = State.Complete; - } - - public void Standby() - { - this.lastUpdate = DateTime.Now; - state = State.Standby; - } - - public void Start() - { - this.lastUpdate = DateTime.Now; - state = State.Running; - this.executionStarted = DateTime.Now; - } - - public void Complete() - { - this.lastUpdate = DateTime.Now; - state = State.Complete; - } - - public void Cancel() - { - this.lastUpdate = DateTime.Now; - state = State.Cancelled; - } - - public void Waiting() - { - this.lastUpdate = DateTime.Now; - state = State.Waiting; - } -} \ No newline at end of file diff --git a/Tranga/Jobs/UpdateMetadata.cs b/Tranga/Jobs/UpdateMetadata.cs index e9107ce..1533fb4 100644 --- a/Tranga/Jobs/UpdateMetadata.cs +++ b/Tranga/Jobs/UpdateMetadata.cs @@ -1,4 +1,7 @@ -using Tranga.MangaConnectors; +using System.Runtime.CompilerServices; +using JobQueue; +using Microsoft.Extensions.Logging; +using Tranga.MangaConnectors; namespace Tranga.Jobs; @@ -6,22 +9,17 @@ public class UpdateMetadata : Job { public Manga manga { get; set; } - public UpdateMetadata(GlobalBase clone, MangaConnector connector, Manga manga, string? parentJobId = null) : base(clone, JobType.UpdateMetaDataJob, connector, parentJobId: parentJobId) + public UpdateMetadata(GlobalBase clone, JobQueue queue, MangaConnector connector, Manga manga, string? jobId = null, string? parentJobId = null, ILogger? logger = null) : base (clone, queue, connector, JobType.UpdateMetaDataJob, TimeSpan.Zero, TimeSpan.FromSeconds(clone.settings.jobTimeout), 1, jobId, parentJobId, logger) { this.manga = manga; - } - - protected override string GetId() - { - return $"{GetType()}-{manga.internalId}"; + if (jobId is null) + { + const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + this.JobId = $"{this.GetType().Name}-{connector.name}-{manga.sortName}-{new string(Enumerable.Repeat(chars, 4).Select(s => s[Random.Shared.Next(s.Length)]).ToArray())}"; + } } - public override string ToString() - { - return $"{id} Manga: {manga}"; - } - - protected override IEnumerable ExecuteReturnSubTasksInternal(JobBoss jobBoss) + protected override void Execute(CancellationToken cancellationToken) { //Retrieve new Metadata Manga? possibleUpdatedManga = mangaConnector.GetMangaFromId(manga.publicationId); @@ -29,30 +27,19 @@ public class UpdateMetadata : Job { if (updatedManga.Equals(this.manga)) //Check if anything changed { - this.progressToken.Complete(); - return Array.Empty(); + this.ProgressToken.MarkFinished(); } this.manga.UpdateMetadata(updatedManga); - this.manga.SaveSeriesInfoJson(settings.downloadLocation, true); - this.progressToken.Complete(); + this.manga.SaveSeriesInfoJson(this.GlobalBase.settings.downloadLocation, true); + this.ProgressToken.MarkFinished(); } else { - Log($"Could not find Manga {manga}"); - this.progressToken.Cancel(); - return Array.Empty(); + this.GlobalBase.Log($"Could not find Manga {manga}"); + this.ProgressToken.MarkFailed(); } - this.progressToken.Cancel(); - return Array.Empty(); - } - - public override bool Equals(object? obj) - { - - if (obj is not UpdateMetadata otherJob) - return false; - return otherJob.mangaConnector == this.mangaConnector && - otherJob.manga.Equals(this.manga); + this.ProgressToken.Cancel(); + return ; } } \ No newline at end of file diff --git a/Tranga/LibraryConnectors/LibraryConnector.cs b/Tranga/LibraryConnectors/LibraryConnector.cs index 5bd7097..9634827 100644 --- a/Tranga/LibraryConnectors/LibraryConnector.cs +++ b/Tranga/LibraryConnectors/LibraryConnector.cs @@ -1,6 +1,6 @@ using System.Net; using System.Net.Http.Headers; -using Logging; +using Microsoft.Extensions.Logging; namespace Tranga.LibraryConnectors; @@ -31,7 +31,7 @@ public abstract class LibraryConnector : GlobalBase protected static class NetClient { - public static Stream MakeRequest(string url, string authScheme, string auth, Logger? logger) + public static Stream MakeRequest(string url, string authScheme, string auth, ILogger? logger) { HttpClient client = new(); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(authScheme, auth); @@ -45,8 +45,7 @@ public abstract class LibraryConnector : GlobalBase { HttpResponseMessage response = client.Send(requestMessage); - logger?.WriteLine("LibraryManager.NetClient", - $"GET {url} -> {(int)response.StatusCode}: {response.ReasonPhrase}"); + logger?.LogInformation($"LibraryManager.NetClient | GET {url} -> {(int)response.StatusCode}: {response.ReasonPhrase}"); if (response.StatusCode is HttpStatusCode.Unauthorized && response.RequestMessage!.RequestUri!.AbsoluteUri != url) @@ -61,7 +60,7 @@ public abstract class LibraryConnector : GlobalBase switch (e) { case HttpRequestException: - logger?.WriteLine("LibraryManager.NetClient", $"Failed to make Request:\n\r{e}\n\rContinuing."); + logger?.LogInformation($"LibraryManager.NetClient | Failed to make Request:\n\r{e}\n\rContinuing."); break; default: throw; @@ -70,7 +69,7 @@ public abstract class LibraryConnector : GlobalBase } } - public static bool MakePost(string url, string authScheme, string auth, Logger? logger) + public static bool MakePost(string url, string authScheme, string auth, ILogger? logger) { HttpClient client = new() { @@ -86,7 +85,7 @@ public abstract class LibraryConnector : GlobalBase RequestUri = new Uri(url) }; HttpResponseMessage response = client.Send(requestMessage); - logger?.WriteLine("LibraryManager.NetClient", $"POST {url} -> {(int)response.StatusCode}: {response.ReasonPhrase}"); + logger?.LogInformation($"LibraryManager.NetClient | POST {url} -> {(int)response.StatusCode}: {response.ReasonPhrase}"); if(response.StatusCode is HttpStatusCode.Unauthorized && response.RequestMessage!.RequestUri!.AbsoluteUri != url) return MakePost(response.RequestMessage!.RequestUri!.AbsoluteUri, authScheme, auth, logger); diff --git a/Tranga/MangaConnectors/Bato.cs b/Tranga/MangaConnectors/Bato.cs index 96d97c0..1b49583 100644 --- a/Tranga/MangaConnectors/Bato.cs +++ b/Tranga/MangaConnectors/Bato.cs @@ -1,6 +1,7 @@ using System.Net; using System.Text.RegularExpressions; using HtmlAgilityPack; +using JobQueue; using Tranga.Jobs; namespace Tranga.MangaConnectors; @@ -170,9 +171,9 @@ public class Bato : MangaConnector public override HttpStatusCode DownloadChapter(Chapter chapter, ProgressToken? progressToken = null) { - if (progressToken?.cancellationRequested ?? false) + if (progressToken?.CancellationTokenSource.IsCancellationRequested ?? false) { - progressToken.Cancel(); + progressToken.Value.Cancel(); return HttpStatusCode.RequestTimeout; } diff --git a/Tranga/MangaConnectors/MangaConnector.cs b/Tranga/MangaConnectors/MangaConnector.cs index bd80aed..7c9e3d4 100644 --- a/Tranga/MangaConnectors/MangaConnector.cs +++ b/Tranga/MangaConnectors/MangaConnector.cs @@ -2,6 +2,7 @@ using System.Net; using System.Runtime.InteropServices; using System.Text.RegularExpressions; +using JobQueue; using Tranga.Jobs; using static System.IO.UnixFileMode; @@ -219,11 +220,11 @@ public abstract class MangaConnector : GlobalBase protected HttpStatusCode DownloadChapterImages(string[] imageUrls, string saveArchiveFilePath, RequestType requestType, string? comicInfoPath = null, string? referrer = null, ProgressToken? progressToken = null) { - if (progressToken?.cancellationRequested ?? false) + if (progressToken?.CancellationTokenSource.IsCancellationRequested ?? false) return HttpStatusCode.RequestTimeout; Log($"Downloading Images for {saveArchiveFilePath}"); if(progressToken is not null) - progressToken.increments = imageUrls.Length; + progressToken.Value.SetSteps(imageUrls.Length); //Check if Publication Directory already exists string directoryPath = Path.GetDirectoryName(saveArchiveFilePath)!; if (!Directory.Exists(directoryPath)) @@ -249,15 +250,15 @@ public abstract class MangaConnector : GlobalBase Log($"{saveArchiveFilePath} {chapter + 1:000}/{imageUrls.Length:000} {status}"); if ((int)status < 200 || (int)status >= 300) { - progressToken?.Complete(); + progressToken?.MarkFinished(); return status; } - if (progressToken?.cancellationRequested ?? false) + if (progressToken?.CancellationTokenSource.IsCancellationRequested ?? false) { - progressToken.Complete(); + progressToken.Value.MarkFinished(); return HttpStatusCode.RequestTimeout; } - progressToken?.Increment(); + progressToken?.UpdateProgress(1); } if(comicInfoPath is not null) @@ -270,7 +271,7 @@ public abstract class MangaConnector : GlobalBase File.SetUnixFileMode(saveArchiveFilePath, UserRead | UserWrite | UserExecute | GroupRead | GroupWrite | GroupExecute); Directory.Delete(tempFolder, true); //Cleanup - progressToken?.Complete(); + progressToken?.MarkFinished(); return HttpStatusCode.OK; } diff --git a/Tranga/MangaConnectors/MangaDex.cs b/Tranga/MangaConnectors/MangaDex.cs index 08c4bac..b44570e 100644 --- a/Tranga/MangaConnectors/MangaDex.cs +++ b/Tranga/MangaConnectors/MangaDex.cs @@ -1,7 +1,7 @@ using System.Net; using System.Text.Json.Nodes; using System.Text.RegularExpressions; -using Tranga.Jobs; +using JobQueue; using JsonSerializer = System.Text.Json.JsonSerializer; namespace Tranga.MangaConnectors; @@ -242,9 +242,9 @@ public class MangaDex : MangaConnector public override HttpStatusCode DownloadChapter(Chapter chapter, ProgressToken? progressToken = null) { - if (progressToken?.cancellationRequested ?? false) + if (progressToken?.CancellationTokenSource.IsCancellationRequested ?? false) { - progressToken.Cancel(); + progressToken.Value.Cancel(); return HttpStatusCode.RequestTimeout; } diff --git a/Tranga/MangaConnectors/MangaKatana.cs b/Tranga/MangaConnectors/MangaKatana.cs index a62315b..471265c 100644 --- a/Tranga/MangaConnectors/MangaKatana.cs +++ b/Tranga/MangaConnectors/MangaKatana.cs @@ -1,6 +1,7 @@ using System.Net; using System.Text.RegularExpressions; using HtmlAgilityPack; +using JobQueue; using Tranga.Jobs; namespace Tranga.MangaConnectors; @@ -196,9 +197,9 @@ public class MangaKatana : MangaConnector public override HttpStatusCode DownloadChapter(Chapter chapter, ProgressToken? progressToken = null) { - if (progressToken?.cancellationRequested ?? false) + if (progressToken?.CancellationTokenSource.IsCancellationRequested ?? false) { - progressToken.Cancel(); + progressToken.Value.Cancel(); return HttpStatusCode.RequestTimeout; } diff --git a/Tranga/MangaConnectors/MangaLife.cs b/Tranga/MangaConnectors/MangaLife.cs index fd92377..3442234 100644 --- a/Tranga/MangaConnectors/MangaLife.cs +++ b/Tranga/MangaConnectors/MangaLife.cs @@ -1,6 +1,7 @@ using System.Net; using System.Text.RegularExpressions; using HtmlAgilityPack; +using JobQueue; using Tranga.Jobs; namespace Tranga.MangaConnectors; @@ -161,16 +162,16 @@ public class MangaLife : MangaConnector public override HttpStatusCode DownloadChapter(Chapter chapter, ProgressToken? progressToken = null) { - if (progressToken?.cancellationRequested ?? false) + if (progressToken?.CancellationTokenSource.IsCancellationRequested ?? false) { - progressToken.Cancel(); + progressToken.Value.Cancel(); return HttpStatusCode.RequestTimeout; } Manga chapterParentManga = chapter.parentManga; - if (progressToken?.cancellationRequested ?? false) + if (progressToken?.CancellationTokenSource.IsCancellationRequested ?? false) { - progressToken.Cancel(); + progressToken.Value.Cancel(); return HttpStatusCode.RequestTimeout; } diff --git a/Tranga/MangaConnectors/Manganato.cs b/Tranga/MangaConnectors/Manganato.cs index d7bbd79..8e884b7 100644 --- a/Tranga/MangaConnectors/Manganato.cs +++ b/Tranga/MangaConnectors/Manganato.cs @@ -1,6 +1,7 @@ using System.Net; using System.Text.RegularExpressions; using HtmlAgilityPack; +using JobQueue; using Tranga.Jobs; namespace Tranga.MangaConnectors; @@ -179,9 +180,9 @@ public class Manganato : MangaConnector public override HttpStatusCode DownloadChapter(Chapter chapter, ProgressToken? progressToken = null) { - if (progressToken?.cancellationRequested ?? false) + if (progressToken?.CancellationTokenSource.IsCancellationRequested ?? false) { - progressToken.Cancel(); + progressToken.Value.Cancel(); return HttpStatusCode.RequestTimeout; } diff --git a/Tranga/MangaConnectors/Mangasee.cs b/Tranga/MangaConnectors/Mangasee.cs index 3a0efde..29fca73 100644 --- a/Tranga/MangaConnectors/Mangasee.cs +++ b/Tranga/MangaConnectors/Mangasee.cs @@ -3,6 +3,7 @@ using System.Net; using System.Text.RegularExpressions; using System.Xml.Linq; using HtmlAgilityPack; +using JobQueue; using Newtonsoft.Json; using Tranga.Jobs; @@ -216,16 +217,16 @@ public class Mangasee : MangaConnector public override HttpStatusCode DownloadChapter(Chapter chapter, ProgressToken? progressToken = null) { - if (progressToken?.cancellationRequested ?? false) + if (progressToken?.CancellationTokenSource.IsCancellationRequested ?? false) { - progressToken.Cancel(); + progressToken.Value.Cancel(); return HttpStatusCode.RequestTimeout; } Manga chapterParentManga = chapter.parentManga; - if (progressToken?.cancellationRequested ?? false) + if (progressToken?.CancellationTokenSource.IsCancellationRequested ?? false) { - progressToken.Cancel(); + progressToken.Value.Cancel(); return HttpStatusCode.RequestTimeout; } diff --git a/Tranga/MangaConnectors/Mangaworld.cs b/Tranga/MangaConnectors/Mangaworld.cs index a457999..1ba3285 100644 --- a/Tranga/MangaConnectors/Mangaworld.cs +++ b/Tranga/MangaConnectors/Mangaworld.cs @@ -1,6 +1,7 @@ using System.Net; using System.Text.RegularExpressions; using HtmlAgilityPack; +using JobQueue; using Tranga.Jobs; namespace Tranga.MangaConnectors; @@ -178,9 +179,9 @@ public class Mangaworld: MangaConnector public override HttpStatusCode DownloadChapter(Chapter chapter, ProgressToken? progressToken = null) { - if (progressToken?.cancellationRequested ?? false) + if (progressToken?.CancellationTokenSource.IsCancellationRequested ?? false) { - progressToken.Cancel(); + progressToken.Value.Cancel(); return HttpStatusCode.RequestTimeout; } diff --git a/Tranga/Server.cs b/Tranga/Server.cs index c12f1fa..75b1eb2 100644 --- a/Tranga/Server.cs +++ b/Tranga/Server.cs @@ -2,6 +2,8 @@ using System.Runtime.InteropServices; using System.Text; using System.Text.RegularExpressions; +using JobQueue; +using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Tranga.Jobs; using Tranga.LibraryConnectors; @@ -167,35 +169,47 @@ public class Server : GlobalBase SendResponse(HttpStatusCode.OK, response, connector!.GetChapters((Manga)manga!, translatedLanguage??"en")); break; case "Jobs": - if (!requestVariables.TryGetValue("jobId", out jobId)) + if (requestVariables.TryGetValue("jobId", out jobId)) { - if(!_parent.jobBoss.jobs.Any(jjob => jjob.id == jobId)) - SendResponse(HttpStatusCode.BadRequest, response); + Job? job = _parent.JobQueue.JobWithId(jobId); + if (job is null) + { + SendResponse(HttpStatusCode.NotFound, response); + break; + } else - SendResponse(HttpStatusCode.OK, response, _parent.jobBoss.jobs.First(jjob => jjob.id == jobId)); - break; + { + SendResponse(HttpStatusCode.OK, response, job); + break; + } } - SendResponse(HttpStatusCode.OK, response, _parent.jobBoss.jobs); + SendResponse(HttpStatusCode.OK, response, _parent.JobQueue.AllJobs()); break; case "Jobs/Progress": if (requestVariables.TryGetValue("jobId", out jobId)) { - if(!_parent.jobBoss.jobs.Any(jjob => jjob.id == jobId)) - SendResponse(HttpStatusCode.BadRequest, response); + Job? job = _parent.JobQueue.JobWithId(jobId); + if (job is null) + { + SendResponse(HttpStatusCode.NotFound, response); + break; + } else - SendResponse(HttpStatusCode.OK, response, _parent.jobBoss.jobs.First(jjob => jjob.id == jobId).progressToken); - break; + { + SendResponse(HttpStatusCode.OK, response, job.ProgressToken); + break; + } } - SendResponse(HttpStatusCode.OK, response, _parent.jobBoss.jobs.Select(jjob => jjob.progressToken)); + SendResponse(HttpStatusCode.OK, response, _parent.JobQueue.AllJobs().Select(jjob => jjob.ProgressToken)); break; case "Jobs/Running": - SendResponse(HttpStatusCode.OK, response, _parent.jobBoss.jobs.Where(jjob => jjob.progressToken.state is ProgressToken.State.Running)); + SendResponse(HttpStatusCode.OK, response, _parent.JobQueue.RunningJobs()); break; 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.JobQueue.WaitingJobs().Order()); break; 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.JobQueue.AllJobs().Cast().Where(job => job.jobType is Job.JobType.DownloadNewChaptersJob)); break; case "Settings": SendResponse(HttpStatusCode.OK, response, settings); @@ -233,7 +247,7 @@ public class Server : GlobalBase MangaConnector? connector; Manga? tmpManga; Manga manga; - Job? job; + Job? job; string path = Regex.Match(request.Url!.LocalPath, @"[A-z0-9]+(\/[A-z0-9]+)*").Value; switch (path) { @@ -275,7 +289,8 @@ public class Server : GlobalBase manga.MovePublicationFolder(settings.downloadLocation, customFolderName); requestVariables.TryGetValue("translatedLanguage", out translatedLanguage); - _parent.jobBoss.AddJob(new DownloadNewChapters(this, connector!, manga, true, interval, translatedLanguage: translatedLanguage??"en")); + _parent.JobQueue.AddJob(connector!, new DownloadNewChapter(this, _parent.JobQueue, connector!, manga, interval, 1, null, null, this.logger, translatedLanguage??"en")); + SendResponse(HttpStatusCode.Accepted, response); break; case "Jobs/DownloadNewChapters": @@ -304,32 +319,33 @@ public class Server : GlobalBase manga.MovePublicationFolder(settings.downloadLocation, customFolderName); requestVariables.TryGetValue("translatedLanguage", out translatedLanguage); - _parent.jobBoss.AddJob(new DownloadNewChapters(this, connector!, manga, false, translatedLanguage: translatedLanguage??"en")); + _parent.JobQueue.AddJob(connector!, new DownloadNewChapter(this, _parent.JobQueue, connector!, manga, TimeSpan.FromHours(1), 1, null, null, this.logger, translatedLanguage??"en")); + SendResponse(HttpStatusCode.Accepted, response); break; case "Jobs/UpdateMetadata": if (!requestVariables.TryGetValue("internalId", out internalId)) { - foreach (Job pJob in _parent.jobBoss.jobs.Where(possibleDncJob => - possibleDncJob.jobType is Job.JobType.DownloadNewChaptersJob).ToArray())//ToArray to avoid modyifying while adding new jobs + foreach (DownloadNewChapter dncJob in _parent.JobQueue.AllJobs().Cast().Where(j => j.jobType is Job.JobType.DownloadNewChaptersJob).Cast().ToArray())//ToArray to avoid modyifying while adding new jobs { - DownloadNewChapters dncJob = pJob as DownloadNewChapters ?? - throw new Exception("Has to be DownloadNewChapters Job"); - _parent.jobBoss.AddJob(new UpdateMetadata(this, dncJob.mangaConnector, dncJob.manga)); + _parent.JobQueue.AddJob(dncJob.mangaConnector, new UpdateMetadata(this, _parent.JobQueue, dncJob.mangaConnector, dncJob.manga, null, dncJob.JobId, logger)); } SendResponse(HttpStatusCode.Accepted, response); } else { - Job[] possibleDncJobs = _parent.jobBoss.GetJobsLike(internalId: internalId).ToArray(); + Job[] possibleDncJobs = _parent.JobQueue.AllJobs().Cast() + .Where(j => j.jobType is Job.JobType.DownloadNewChaptersJob).Cast() + .ToArray(); switch (possibleDncJobs.Length) { case <1: SendResponse(HttpStatusCode.BadRequest, response, "Could not find matching release"); break; case >1: SendResponse(HttpStatusCode.BadRequest, response, "Multiple releases??"); break; default: - DownloadNewChapters dncJob = possibleDncJobs[0] as DownloadNewChapters ?? + DownloadNewChapter dncJob = possibleDncJobs[0] as DownloadNewChapter ?? throw new Exception("Has to be DownloadNewChapters Job"); - _parent.jobBoss.AddJob(new UpdateMetadata(this, dncJob.mangaConnector, dncJob.manga)); + + _parent.JobQueue.AddJob(dncJob.mangaConnector, new UpdateMetadata(this, _parent.JobQueue, dncJob.mangaConnector, dncJob.manga, null, dncJob.JobId, logger)); SendResponse(HttpStatusCode.Accepted, response); break; } @@ -337,17 +353,17 @@ public class Server : GlobalBase break; case "Jobs/StartNow": if (!requestVariables.TryGetValue("jobId", out jobId) || - !_parent.jobBoss.TryGetJobById(jobId, out job)) + !_parent.JobQueue.TryJobWithId(jobId, out job)) { SendResponse(HttpStatusCode.BadRequest, response); break; } - _parent.jobBoss.AddJobToQueue(job!); + job!.Start(); SendResponse(HttpStatusCode.Accepted, response); break; case "Jobs/Cancel": if (!requestVariables.TryGetValue("jobId", out jobId) || - !_parent.jobBoss.TryGetJobById(jobId, out job)) + !_parent.JobQueue.TryJobWithId(jobId, out job)) { SendResponse(HttpStatusCode.BadRequest, response); break; @@ -491,37 +507,16 @@ public class Server : GlobalBase SendResponse(HttpStatusCode.BadRequest, response); } break; - case "LogMessages": - if (logger is null || !File.Exists(logger?.logFilePath)) - { - SendResponse(HttpStatusCode.NotFound, response); - break; - } - - if (requestVariables.TryGetValue("count", out string? count)) - { - try - { - uint messageCount = uint.Parse(count); - SendResponse(HttpStatusCode.OK, response, logger.Tail(messageCount)); - } - catch (FormatException f) - { - SendResponse(HttpStatusCode.InternalServerError, response, f); - } - }else - SendResponse(HttpStatusCode.OK, response, logger.GetLog()); - break; case "LogFile": - if (logger is null || !File.Exists(logger?.logFilePath)) + if (logger is null || !File.Exists(logger.LogFilePath)) { SendResponse(HttpStatusCode.NotFound, response); break; } - string logDir = new FileInfo(logger.logFilePath).DirectoryName!; + string logDir = new FileInfo(logger.LogFilePath).DirectoryName!; string tmpFilePath = Path.Join(logDir, "Tranga.log"); - File.Copy(logger.logFilePath, tmpFilePath); + File.Copy(logger.LogFilePath, tmpFilePath); SendResponse(HttpStatusCode.OK, response, new FileStream(tmpFilePath, FileMode.Open)); File.Delete(tmpFilePath); break; @@ -542,26 +537,12 @@ public class Server : GlobalBase { case "Jobs": if (!requestVariables.TryGetValue("jobId", out string? jobId) || - !_parent.jobBoss.TryGetJobById(jobId, out Job? job)) + !_parent.JobQueue.TryJobWithId(jobId, out Job? job)) { SendResponse(HttpStatusCode.BadRequest, response); break; } - _parent.jobBoss.RemoveJob(job!); - SendResponse(HttpStatusCode.Accepted, response); - break; - case "Jobs/DownloadNewChapters": - 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)); + _parent.JobQueue.RemoveJob(job!); SendResponse(HttpStatusCode.Accepted, response); break; case "NotificationConnectors": diff --git a/Tranga/Tranga.cs b/Tranga/Tranga.cs index e146f6f..6867520 100644 --- a/Tranga/Tranga.cs +++ b/Tranga/Tranga.cs @@ -1,13 +1,14 @@ -using Logging; -using Tranga.Jobs; +using GlaxLogger; using Tranga.MangaConnectors; +using JobQueue; +using Microsoft.Extensions.Logging; namespace Tranga; public partial class Tranga : GlobalBase { public bool keepRunning; - public JobBoss jobBoss; + public JobQueue JobQueue; private Server _server; private HashSet _connectors; @@ -26,8 +27,7 @@ public partial class Tranga : GlobalBase new Bato(this), new MangaLife(this) }; - jobBoss = new(this, this._connectors); - StartJobBoss(); + this.JobQueue = new JobQueue(100, logger); 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)]); @@ -64,17 +64,4 @@ public partial class Tranga : GlobalBase manga = GetPublicationById(internalId); return manga is not null; } - - private void StartJobBoss() - { - Thread t = new (() => - { - while (keepRunning) - { - jobBoss.CheckJobs(); - Thread.Sleep(100); - } - }); - t.Start(); - } } \ No newline at end of file diff --git a/Tranga/Tranga.csproj b/Tranga/Tranga.csproj index 2e4242b..1fe6077 100644 --- a/Tranga/Tranga.csproj +++ b/Tranga/Tranga.csproj @@ -8,15 +8,12 @@ + - - - - .dockerignore @@ -24,4 +21,10 @@ + + + ..\..\TaskQueue\JobQueue\bin\Debug\net7.0\JobQueue.dll + + + diff --git a/Tranga/TrangaArgs.cs b/Tranga/TrangaArgs.cs index 86d9028..0ec2a80 100644 --- a/Tranga/TrangaArgs.cs +++ b/Tranga/TrangaArgs.cs @@ -1,4 +1,5 @@ -using Logging; +using GlaxLogger; +using Microsoft.Extensions.Logging; namespace Tranga; @@ -14,17 +15,11 @@ public partial class Tranga : GlobalBase return; } - string[]? consoleLogger = GetArg(args, ArgEnum.ConsoleLogger); string[]? fileLogger = GetArg(args, ArgEnum.FileLogger); string? filePath = fileLogger?[0];//TODO validate path + string[]? logLevel = GetArg(args, ArgEnum.LogLevel); + LogLevel? level = logLevel is null || logLevel.Length < 1 ? null : Enum.Parse(logLevel[0]); - List enabledLoggers = new(); - if(consoleLogger is not null) - enabledLoggers.Add(Logger.LoggerType.ConsoleLogger); - if (fileLogger is not null) - enabledLoggers.Add(Logger.LoggerType.FileLogger); - Logger logger = new(enabledLoggers.ToArray(), Console.Out, Console.OutputEncoding, filePath); - TrangaSettings? settings = null; string[]? downloadLocationPath = GetArg(args, ArgEnum.DownloadLocation); string[]? workingDirectory = GetArg(args, ArgEnum.WorkingDirectory); @@ -53,7 +48,7 @@ public partial class Tranga : GlobalBase Directory.CreateDirectory(settings.downloadLocation);//TODO validate path Directory.CreateDirectory(settings.workingDirectory);//TODO validate path - Tranga _ = new (logger, settings); + Tranga _ = new (new Logger(outputFolderPath: filePath, consoleOut: Console.Out), settings); } private static void PrintHelp() @@ -104,18 +99,17 @@ public partial class Tranga : GlobalBase { { ArgEnum.DownloadLocation, new(new []{"-d", "--downloadLocation"}, 1, "Directory to which downloaded Manga are saved") }, { ArgEnum.WorkingDirectory, new(new []{"-w", "--workingDirectory"}, 1, "Directory in which application-data is saved") }, - { ArgEnum.ConsoleLogger, new(new []{"-c", "--consoleLogger"}, 0, "Enables the consoleLogger") }, - { ArgEnum.FileLogger, new(new []{"-f", "--fileLogger"}, 1, "Enables the fileLogger, Directory where logfiles are saved") }, + { ArgEnum.FileLogger, new(new []{"-f", "--fileLogger"}, 1, "Directory where logfiles are saved") }, + { ArgEnum.LogLevel, new(new []{"-l", "--loglevel"}, 1, "Log-Level") }, { ArgEnum.Help, new(new []{"-h", "--help"}, 0, "Print this") } //{ ArgEnum., new(new []{""}, 1, "") } }; internal enum ArgEnum { - TrangaSettings, + LogLevel, DownloadLocation, WorkingDirectory, - ConsoleLogger, FileLogger, Help } diff --git a/Tranga/TrangaSettings.cs b/Tranga/TrangaSettings.cs index 4058188..4cca386 100644 --- a/Tranga/TrangaSettings.cs +++ b/Tranga/TrangaSettings.cs @@ -13,6 +13,7 @@ public class TrangaSettings public string downloadLocation { get; private set; } public string workingDirectory { get; private set; } public int apiPortNumber { get; init; } + public int jobTimeout { get; init; } = 180; public string styleSheet { get; private set; } public string userAgent { get; set; } = DefaultUserAgent; [JsonIgnore] public string settingsFilePath => Path.Join(workingDirectory, "settings.json");