diff --git a/API/Controllers/Requests/PatchLibraryRefreshRecord.cs b/API/Controllers/Requests/PatchLibraryRefreshRecord.cs new file mode 100644 index 0000000..6e2182f --- /dev/null +++ b/API/Controllers/Requests/PatchLibraryRefreshRecord.cs @@ -0,0 +1,21 @@ +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 + /// + [Description("When WhileDownloadingis selected, update the time between refreshes")] + public 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/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/TrangaSettings.cs b/API/TrangaSettings.cs index bcbbe44..7b205e4 100644 --- a/API/TrangaSettings.cs +++ b/API/TrangaSettings.cs @@ -1,6 +1,8 @@ using System.Runtime.InteropServices; using API.MangaDownloadClients; +using API.Workers; using Newtonsoft.Json; +using Newtonsoft.Json.Converters; namespace API; @@ -54,16 +56,20 @@ 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)) 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) @@ -125,4 +131,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/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/MangaDownloadWorkers/DownloadChapterFromMangaconnectorWorker.cs b/API/Workers/MangaDownloadWorkers/DownloadChapterFromMangaconnectorWorker.cs index c03a6b5..f80214f 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,22 @@ public class DownloadChapterFromMangaconnectorWorker(MangaConnectorId c Log.Debug($"Downloaded chapter {chapter}."); - return []; + 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 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 => 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) { @@ -232,5 +247,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..0167afb --- /dev/null +++ b/API/Workers/RefreshLibrariesWorker.cs @@ -0,0 +1,44 @@ +using API.Schema.LibraryContext; +using API.Schema.LibraryContext.LibraryConnectors; +using Microsoft.EntityFrameworkCore; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +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 []; + } +} + +[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 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