From 4e3968f4b122558e95df66daad972a554947445e Mon Sep 17 00:00:00 2001 From: glax Date: Sun, 21 Sep 2025 16:27:43 +0200 Subject: [PATCH 1/4] Add RefreshLibrariesWorker.cs --- API/TrangaSettings.cs | 17 +++++++++++ ...DownloadChapterFromMangaconnectorWorker.cs | 22 ++++++++++---- .../StartNewChapterDownloadsWorker.cs | 10 ++++--- API/Workers/RefreshLibrariesWorker.cs | 29 +++++++++++++++++++ Tranga.sln.DotSettings | 4 ++- 5 files changed, 72 insertions(+), 10 deletions(-) create mode 100644 API/Workers/RefreshLibrariesWorker.cs diff --git a/API/TrangaSettings.cs b/API/TrangaSettings.cs index bcbbe44..81656f1 100644 --- a/API/TrangaSettings.cs +++ b/API/TrangaSettings.cs @@ -1,5 +1,6 @@ using System.Runtime.InteropServices; using API.MangaDownloadClients; +using API.Workers; using Newtonsoft.Json; namespace API; @@ -54,6 +55,10 @@ public struct TrangaSettings() public int MaxConcurrentWorkers { get; set; } = Math.Max(Environment.ProcessorCount, 4); // Minimum of 4 Tasks, maximum of 1 per Core + public LibraryRefreshSetting LibraryRefreshSetting { get; set; } = LibraryRefreshSetting.AfterMangaFinished; + + public int RefreshLibraryWhileDownloadingEveryMinutes { get; set; } = 10; + public static TrangaSettings Load() { if (!File.Exists(SettingsFilePath)) @@ -125,4 +130,16 @@ public struct TrangaSettings() this.MaxConcurrentWorkers = value; Save(); } + + public void SetLibraryRefreshSetting(LibraryRefreshSetting setting) + { + this.LibraryRefreshSetting = setting; + Save(); + } + + public void SetRefreshLibraryWhileDownloadingEveryMinutes(int value) + { + this.RefreshLibraryWhileDownloadingEveryMinutes = value; + Save(); + } } \ No newline at end of file diff --git a/API/Workers/MangaDownloadWorkers/DownloadChapterFromMangaconnectorWorker.cs b/API/Workers/MangaDownloadWorkers/DownloadChapterFromMangaconnectorWorker.cs index c03a6b5..2ab6ab2 100644 --- a/API/Workers/MangaDownloadWorkers/DownloadChapterFromMangaconnectorWorker.cs +++ b/API/Workers/MangaDownloadWorkers/DownloadChapterFromMangaconnectorWorker.cs @@ -3,6 +3,7 @@ using System.Runtime.InteropServices; using API.MangaConnectors; using API.MangaDownloadClients; using API.Schema.MangaContext; +using API.Workers.PeriodicWorkers; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking; using SixLabors.ImageSharp; @@ -21,16 +22,16 @@ namespace API.Workers.MangaDownloadWorkers; public class DownloadChapterFromMangaconnectorWorker(MangaConnectorId chId, IEnumerable? dependsOn = null) : BaseWorkerWithContext(dependsOn) { - internal readonly string MangaConnectorIdId = chId.Key; + private readonly string _mangaConnectorIdId = chId.Key; protected override async Task DoWorkInternal() { - Log.Debug($"Downloading chapter for MangaConnectorId {MangaConnectorIdId}..."); + Log.Debug($"Downloading chapter for MangaConnectorId {_mangaConnectorIdId}..."); // Getting MangaConnector info if (await DbContext.MangaConnectorToChapter .Include(id => id.Obj) .ThenInclude(c => c.ParentManga) .ThenInclude(m => m.Library) - .FirstOrDefaultAsync(c => c.Key == MangaConnectorIdId, CancellationToken) is not { } mangaConnectorId) + .FirstOrDefaultAsync(c => c.Key == _mangaConnectorIdId, CancellationToken) is not { } mangaConnectorId) { Log.Error("Could not get MangaConnectorId."); return []; //TODO Exception? @@ -137,8 +138,19 @@ public class DownloadChapterFromMangaconnectorWorker(MangaConnectorId c Log.Debug($"Downloaded chapter {chapter}."); - return []; + bool refreshLibrary = await CheckLibraryRefresh(); + + return refreshLibrary? [new RefreshLibrariesWorker()] : []; } + + private async Task CheckLibraryRefresh() => Tranga.Settings.LibraryRefreshSetting switch + { + LibraryRefreshSetting.AfterAllFinished => (await StartNewChapterDownloadsWorker.GetMissingChapters(DbContext, CancellationToken)).Count == 0, + LibraryRefreshSetting.AfterMangaFinished => await DbContext.MangaConnectorToChapter.Include(chId => chId.Obj).Where(chId => chId.UseForDownload).AllAsync(chId => chId.Obj.Downloaded, CancellationToken), + LibraryRefreshSetting.AfterEveryChapter => true, + LibraryRefreshSetting.WhileDownloading => DateTime.UtcNow.Subtract(RefreshLibrariesWorker.LastRefresh).TotalMinutes > Tranga.Settings.RefreshLibraryWhileDownloadingEveryMinutes, + _ => true + }; private void ProcessImage(string imagePath) { @@ -232,5 +244,5 @@ public class DownloadChapterFromMangaconnectorWorker(MangaConnectorId c return true; } - public override string ToString() => $"{base.ToString()} {MangaConnectorIdId}"; + public override string ToString() => $"{base.ToString()} {_mangaConnectorIdId}"; } \ No newline at end of file diff --git a/API/Workers/PeriodicWorkers/StartNewChapterDownloadsWorker.cs b/API/Workers/PeriodicWorkers/StartNewChapterDownloadsWorker.cs index 05d386a..3aef412 100644 --- a/API/Workers/PeriodicWorkers/StartNewChapterDownloadsWorker.cs +++ b/API/Workers/PeriodicWorkers/StartNewChapterDownloadsWorker.cs @@ -18,10 +18,7 @@ public class StartNewChapterDownloadsWorker(TimeSpan? interval = null, IEnumerab Log.Debug("Checking for missing chapters..."); // Get missing chapters - List> missingChapters = await DbContext.MangaConnectorToChapter - .Include(id => id.Obj) - .Where(id => id.Obj.Downloaded == false && id.UseForDownload) - .ToListAsync(CancellationToken); + List> missingChapters = await GetMissingChapters(DbContext, CancellationToken); Log.Debug($"Found {missingChapters.Count} missing downloads."); @@ -37,4 +34,9 @@ public class StartNewChapterDownloadsWorker(TimeSpan? interval = null, IEnumerab return newWorkers.ToArray(); } + + internal static async Task>> GetMissingChapters(MangaContext ctx, CancellationToken cancellationToken) => await ctx.MangaConnectorToChapter + .Include(id => id.Obj) + .Where(id => id.Obj.Downloaded == false && id.UseForDownload) + .ToListAsync(cancellationToken); } \ No newline at end of file diff --git a/API/Workers/RefreshLibrariesWorker.cs b/API/Workers/RefreshLibrariesWorker.cs new file mode 100644 index 0000000..1e67bc9 --- /dev/null +++ b/API/Workers/RefreshLibrariesWorker.cs @@ -0,0 +1,29 @@ +using API.Schema.LibraryContext; +using API.Schema.LibraryContext.LibraryConnectors; +using Microsoft.EntityFrameworkCore; + +namespace API.Workers; + +public class RefreshLibrariesWorker(IEnumerable? dependsOn = null) : BaseWorkerWithContext(dependsOn) +{ + public static DateTime LastRefresh { get; set; } = DateTime.UnixEpoch; + + protected override async Task DoWorkInternal() + { + Log.Debug("Refreshing libraries..."); + LastRefresh = DateTime.UtcNow; + List libraries = await DbContext.LibraryConnectors.ToListAsync(CancellationToken); + foreach (LibraryConnector connector in libraries) + await connector.UpdateLibrary(CancellationToken); + Log.Debug("Libraries Refreshed..."); + return []; + } +} + +public enum LibraryRefreshSetting : byte +{ + AfterAllFinished = 0, + AfterMangaFinished = 1, + AfterEveryChapter = 2, + WhileDownloading = 3 +} \ No newline at end of file diff --git a/Tranga.sln.DotSettings b/Tranga.sln.DotSettings index 718e87a..f95497a 100644 --- a/Tranga.sln.DotSettings +++ b/Tranga.sln.DotSettings @@ -8,6 +8,7 @@ True True True + True True True True @@ -15,4 +16,5 @@ True True True - True \ No newline at end of file + True + True \ No newline at end of file From 55a8b09310af85197f9b90378d958b44b28990e2 Mon Sep 17 00:00:00 2001 From: glax Date: Sun, 21 Sep 2025 16:59:12 +0200 Subject: [PATCH 2/4] Add Controller --- .../Requests/PatchLibraryRefreshRecord.cs | 22 +++++++++++++++++++ API/Controllers/SettingsController.cs | 18 ++++++++++++++- .../LibraryConnectors/LibraryConnector.cs | 10 +++++++++ API/Schema/MangaContext/Manga.cs | 8 +++---- .../NotificationsContext/Notification.cs | 3 +++ API/TrangaSettings.cs | 5 +++-- API/Workers/BaseWorker.cs | 3 +++ API/Workers/RefreshLibrariesWorker.cs | 15 +++++++++++++ 8 files changed, 76 insertions(+), 8 deletions(-) create mode 100644 API/Controllers/Requests/PatchLibraryRefreshRecord.cs diff --git a/API/Controllers/Requests/PatchLibraryRefreshRecord.cs b/API/Controllers/Requests/PatchLibraryRefreshRecord.cs new file mode 100644 index 0000000..d1fe149 --- /dev/null +++ b/API/Controllers/Requests/PatchLibraryRefreshRecord.cs @@ -0,0 +1,22 @@ +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using API.Workers; + +namespace API.Controllers.Requests; + +public record PatchLibraryRefreshRecord +{ + /// + /// When to refresh the Library + /// + [Required] + [Description("When to refresh the Library")] + public required LibraryRefreshSetting Setting { get; init; } + + /// + /// When is selected, update the time between refreshes + /// + [Required] + [Description("When WhileDownloadingis selected, update the time between refreshes")] + public required int? RefreshLibraryWhileDownloadingEveryMinutes { get; init; } +} \ No newline at end of file diff --git a/API/Controllers/SettingsController.cs b/API/Controllers/SettingsController.cs index c7c9d05..fce6d47 100644 --- a/API/Controllers/SettingsController.cs +++ b/API/Controllers/SettingsController.cs @@ -1,4 +1,5 @@ -using API.MangaDownloadClients; +using API.Controllers.Requests; +using API.MangaDownloadClients; using Asp.Versioning; using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.AspNetCore.Mvc; @@ -290,4 +291,19 @@ public class SettingsController() : Controller Tranga.Settings.SetDownloadLanguage(Language); return TypedResults.Ok(); } + + + /// + /// Sets the time when Libraries are refreshed + /// + /// + [HttpPatch("LibraryRefresh")] + [ProducesResponseType(Status200OK)] + public Ok SetLibraryRefresh([FromBody]PatchLibraryRefreshRecord requestData) + { + Tranga.Settings.SetLibraryRefreshSetting(requestData.Setting); + if(requestData.RefreshLibraryWhileDownloadingEveryMinutes is { } value) + Tranga.Settings.SetRefreshLibraryWhileDownloadingEveryMinutes(value); + return TypedResults.Ok(); + } } \ No newline at end of file diff --git a/API/Schema/LibraryContext/LibraryConnectors/LibraryConnector.cs b/API/Schema/LibraryContext/LibraryConnectors/LibraryConnector.cs index f25e551..f332bfa 100644 --- a/API/Schema/LibraryContext/LibraryConnectors/LibraryConnector.cs +++ b/API/Schema/LibraryContext/LibraryConnectors/LibraryConnector.cs @@ -2,6 +2,8 @@ using System.ComponentModel.DataAnnotations.Schema; using log4net; using Microsoft.EntityFrameworkCore; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; namespace API.Schema.LibraryContext.LibraryConnectors; @@ -40,8 +42,16 @@ public abstract class LibraryConnector : Identifiable internal abstract Task Test(CancellationToken ct); } +[JsonConverter(typeof(StringEnumConverter))] public enum LibraryType : byte { + /// + /// + /// Komga = 0, + + /// + /// + /// Kavita = 1 } \ No newline at end of file diff --git a/API/Schema/MangaContext/Manga.cs b/API/Schema/MangaContext/Manga.cs index 8a694de..753e569 100644 --- a/API/Schema/MangaContext/Manga.cs +++ b/API/Schema/MangaContext/Manga.cs @@ -1,13 +1,10 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Runtime.InteropServices; -using System.Text; using API.Workers; using Microsoft.EntityFrameworkCore; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors.Transforms; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; using static System.IO.UnixFileMode; namespace API.Schema.MangaContext; @@ -138,6 +135,7 @@ public class Manga : Identifiable public override string ToString() => $"{base.ToString()} {Name}"; } +[JsonConverter(typeof(StringEnumConverter))] public enum MangaReleaseStatus : byte { Continuing = 0, diff --git a/API/Schema/NotificationsContext/Notification.cs b/API/Schema/NotificationsContext/Notification.cs index 0ea5a75..ae4e84e 100644 --- a/API/Schema/NotificationsContext/Notification.cs +++ b/API/Schema/NotificationsContext/Notification.cs @@ -1,5 +1,7 @@ using System.ComponentModel.DataAnnotations; using Microsoft.EntityFrameworkCore; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; namespace API.Schema.NotificationsContext; @@ -48,6 +50,7 @@ public class Notification : Identifiable public override string ToString() => $"{base.ToString()} {Urgency} {Title} {Message}"; } +[JsonConverter(typeof(StringEnumConverter))] public enum NotificationUrgency : byte { Low = 1, diff --git a/API/TrangaSettings.cs b/API/TrangaSettings.cs index 81656f1..7b205e4 100644 --- a/API/TrangaSettings.cs +++ b/API/TrangaSettings.cs @@ -2,6 +2,7 @@ using API.MangaDownloadClients; using API.Workers; using Newtonsoft.Json; +using Newtonsoft.Json.Converters; namespace API; @@ -63,12 +64,12 @@ public struct TrangaSettings() { if (!File.Exists(SettingsFilePath)) new TrangaSettings().Save(); - return JsonConvert.DeserializeObject(File.ReadAllText(SettingsFilePath)); + return JsonConvert.DeserializeObject(File.ReadAllText(SettingsFilePath), new StringEnumConverter()); } public void Save() { - File.WriteAllText(SettingsFilePath, JsonConvert.SerializeObject(this, Formatting.Indented)); + File.WriteAllText(SettingsFilePath, JsonConvert.SerializeObject(this, Formatting.Indented, new StringEnumConverter())); } public void SetUserAgent(string value) diff --git a/API/Workers/BaseWorker.cs b/API/Workers/BaseWorker.cs index 58a2d5b..1208e84 100644 --- a/API/Workers/BaseWorker.cs +++ b/API/Workers/BaseWorker.cs @@ -1,5 +1,7 @@ using API.Schema; using log4net; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; namespace API.Workers; @@ -116,6 +118,7 @@ public abstract class BaseWorker : Identifiable } } +[JsonConverter(typeof(StringEnumConverter))] public enum WorkerExecutionState { Failed = 0, diff --git a/API/Workers/RefreshLibrariesWorker.cs b/API/Workers/RefreshLibrariesWorker.cs index 1e67bc9..0167afb 100644 --- a/API/Workers/RefreshLibrariesWorker.cs +++ b/API/Workers/RefreshLibrariesWorker.cs @@ -1,6 +1,8 @@ using API.Schema.LibraryContext; using API.Schema.LibraryContext.LibraryConnectors; using Microsoft.EntityFrameworkCore; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; namespace API.Workers; @@ -20,10 +22,23 @@ public class RefreshLibrariesWorker(IEnumerable? dependsOn = null) : } } +[JsonConverter(typeof(StringEnumConverter))] public enum LibraryRefreshSetting : byte { + /// + /// Refresh Libraries after all Manga are downloaded + /// AfterAllFinished = 0, + /// + /// Refresh Libraries after a Manga is downloaded + /// AfterMangaFinished = 1, + /// + /// Refresh Libraries after every download + /// AfterEveryChapter = 2, + /// + /// Refresh Libraries while downloading chapters, every x minutes + /// WhileDownloading = 3 } \ No newline at end of file From 8e2f9ebbe52260c71e265ffea98ab5676d26f127 Mon Sep 17 00:00:00 2001 From: glax Date: Sun, 21 Sep 2025 17:09:38 +0200 Subject: [PATCH 3/4] WhileDownloading also refresh after all downloads are finished --- API/Tranga.cs | 26 ++++++++++--------- ...DownloadChapterFromMangaconnectorWorker.cs | 7 +++-- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/API/Tranga.cs b/API/Tranga.cs index de6563f..f9561c9 100644 --- a/API/Tranga.cs +++ b/API/Tranga.cs @@ -146,25 +146,27 @@ public static class Tranga private static Action DefaultAfterWork(BaseWorker worker, Action? callback = null) => () => { Log.Debug($"DefaultAfterWork {worker}"); - if (RunningWorkers.TryGetValue(worker, out Task? task)) + try { - Log.Debug($"Waiting for Children to exit {worker}"); - task.Wait(); - if (task.IsCompleted) + if (RunningWorkers.TryGetValue(worker, out Task? task)) { - try + Log.Debug($"Waiting for Children to exit {worker}"); + task.Wait(); + if (task.IsCompleted) { + Log.Debug($"Children done {worker}"); BaseWorker[] newWorkers = task.Result; Log.Debug($"{worker} created {newWorkers.Length} new Workers."); AddWorkers(newWorkers); - } - catch (Exception e) - { - Log.Error(e); - } - }else Log.Warn($"Children failed: {worker}"); + }else + Log.Warn($"Children failed: {worker}"); + } + RunningWorkers.Remove(worker, out _); + } + catch (Exception e) + { + Log.Error(e); } - RunningWorkers.Remove(worker, out _); callback?.Invoke(); }; diff --git a/API/Workers/MangaDownloadWorkers/DownloadChapterFromMangaconnectorWorker.cs b/API/Workers/MangaDownloadWorkers/DownloadChapterFromMangaconnectorWorker.cs index 2ab6ab2..f80214f 100644 --- a/API/Workers/MangaDownloadWorkers/DownloadChapterFromMangaconnectorWorker.cs +++ b/API/Workers/MangaDownloadWorkers/DownloadChapterFromMangaconnectorWorker.cs @@ -139,18 +139,21 @@ public class DownloadChapterFromMangaconnectorWorker(MangaConnectorId c Log.Debug($"Downloaded chapter {chapter}."); bool refreshLibrary = await CheckLibraryRefresh(); + if(refreshLibrary) + Log.Info($"Condition {Tranga.Settings.LibraryRefreshSetting} met."); return refreshLibrary? [new RefreshLibrariesWorker()] : []; } private async Task CheckLibraryRefresh() => Tranga.Settings.LibraryRefreshSetting switch { - LibraryRefreshSetting.AfterAllFinished => (await StartNewChapterDownloadsWorker.GetMissingChapters(DbContext, CancellationToken)).Count == 0, + LibraryRefreshSetting.AfterAllFinished => await AllDownloadsFinished(), LibraryRefreshSetting.AfterMangaFinished => await DbContext.MangaConnectorToChapter.Include(chId => chId.Obj).Where(chId => chId.UseForDownload).AllAsync(chId => chId.Obj.Downloaded, CancellationToken), LibraryRefreshSetting.AfterEveryChapter => true, - LibraryRefreshSetting.WhileDownloading => DateTime.UtcNow.Subtract(RefreshLibrariesWorker.LastRefresh).TotalMinutes > Tranga.Settings.RefreshLibraryWhileDownloadingEveryMinutes, + LibraryRefreshSetting.WhileDownloading => await AllDownloadsFinished() || DateTime.UtcNow.Subtract(RefreshLibrariesWorker.LastRefresh).TotalMinutes > Tranga.Settings.RefreshLibraryWhileDownloadingEveryMinutes, _ => true }; + private async Task AllDownloadsFinished() => (await StartNewChapterDownloadsWorker.GetMissingChapters(DbContext, CancellationToken)).Count == 0; private void ProcessImage(string imagePath) { From f905b5cfa005f5f98fda7c36a504d2049a6a56a7 Mon Sep 17 00:00:00 2001 From: glax Date: Sun, 21 Sep 2025 17:11:55 +0200 Subject: [PATCH 4/4] Remove required from PatchLibraryRefreshRecord RefreshLibraryWhileDownloadingEveryMinutes --- API/Controllers/Requests/PatchLibraryRefreshRecord.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/API/Controllers/Requests/PatchLibraryRefreshRecord.cs b/API/Controllers/Requests/PatchLibraryRefreshRecord.cs index d1fe149..6e2182f 100644 --- a/API/Controllers/Requests/PatchLibraryRefreshRecord.cs +++ b/API/Controllers/Requests/PatchLibraryRefreshRecord.cs @@ -16,7 +16,6 @@ public record PatchLibraryRefreshRecord /// /// When is selected, update the time between refreshes /// - [Required] [Description("When WhileDownloadingis selected, update the time between refreshes")] - public required int? RefreshLibraryWhileDownloadingEveryMinutes { get; init; } + public int? RefreshLibraryWhileDownloadingEveryMinutes { get; init; } } \ No newline at end of file