From 69323d6d6053dad5bb86b114046171a44714398a Mon Sep 17 00:00:00 2001 From: Glax Date: Sat, 21 Sep 2024 21:02:55 +0200 Subject: [PATCH 1/4] Add LibraryBuffer, so Libraries are not spammed with scans on every download. --- Tranga/LibraryConnectors/Kavita.cs | 2 +- Tranga/LibraryConnectors/Komga.cs | 2 +- Tranga/LibraryConnectors/LibraryConnector.cs | 44 +++++++++++++- Tranga/TrangaSettings.cs | 61 +++++++++++--------- 4 files changed, 78 insertions(+), 31 deletions(-) diff --git a/Tranga/LibraryConnectors/Kavita.cs b/Tranga/LibraryConnectors/Kavita.cs index a9d2fa9..62150a0 100644 --- a/Tranga/LibraryConnectors/Kavita.cs +++ b/Tranga/LibraryConnectors/Kavita.cs @@ -61,7 +61,7 @@ public class Kavita : LibraryConnector return ""; } - public override void UpdateLibrary() + protected override void UpdateLibraryInternal() { Log("Updating libraries."); foreach (KavitaLibrary lib in GetLibraries()) diff --git a/Tranga/LibraryConnectors/Komga.cs b/Tranga/LibraryConnectors/Komga.cs index 557d934..2d6b2a1 100644 --- a/Tranga/LibraryConnectors/Komga.cs +++ b/Tranga/LibraryConnectors/Komga.cs @@ -25,7 +25,7 @@ public class Komga : LibraryConnector return $"Komga {baseUrl}"; } - public override void UpdateLibrary() + protected override void UpdateLibraryInternal() { Log("Updating libraries."); foreach (KomgaLibrary lib in GetLibraries()) diff --git a/Tranga/LibraryConnectors/LibraryConnector.cs b/Tranga/LibraryConnectors/LibraryConnector.cs index 80dd5b2..99320b0 100644 --- a/Tranga/LibraryConnectors/LibraryConnector.cs +++ b/Tranga/LibraryConnectors/LibraryConnector.cs @@ -17,6 +17,9 @@ public abstract class LibraryConnector : GlobalBase public string baseUrl { get; } // ReSharper disable once MemberCanBeProtected.Global public string auth { get; } //Base64 encoded, if you use your password everywhere, you have problems + private DateTime? _updateLibraryRequested = null; + private readonly Thread? _libraryBufferThread = null; + private const int NoChangeTimeout = 2, BiggestInterval = 20; protected LibraryConnector(GlobalBase clone, string baseUrl, string auth, LibraryType libraryType) : base(clone) { @@ -28,8 +31,47 @@ public abstract class LibraryConnector : GlobalBase this.baseUrl = baseUrlRex.Match(baseUrl).Value; this.auth = auth; this.libraryType = libraryType; + + if (TrangaSettings.bufferLibraryUpdates) + { + _libraryBufferThread = new(CheckLibraryBuffer); + _libraryBufferThread.Start(); + } } - public abstract void UpdateLibrary(); + + private void CheckLibraryBuffer() + { + while (true) + { + if (_updateLibraryRequested is not null && DateTime.Now.Subtract((DateTime)_updateLibraryRequested) > TimeSpan.FromMinutes(NoChangeTimeout)) //If no updates have been requested for NoChangeTimeout minutes, update library + { + UpdateLibraryInternal(); + _updateLibraryRequested = null; + } + Thread.Sleep(100); + } + } + + public void UpdateLibrary() + { + _updateLibraryRequested ??= DateTime.Now; + if (!TrangaSettings.bufferLibraryUpdates) + { + UpdateLibraryInternal(); + return; + }else if (_updateLibraryRequested is not null && + DateTime.Now.Subtract((DateTime)_updateLibraryRequested) > TimeSpan.FromMinutes(BiggestInterval)) //If the last update has been more than BiggestInterval minutes ago, update library + { + UpdateLibraryInternal(); + _updateLibraryRequested = null; + } + else if(_updateLibraryRequested is not null) + { + Log($"Buffering Library Updates (Updates in latest {((DateTime)_updateLibraryRequested).Add(TimeSpan.FromMinutes(BiggestInterval)).Subtract(DateTime.Now)} or {((DateTime)_updateLibraryRequested).Add(TimeSpan.FromMinutes(NoChangeTimeout)).Subtract(DateTime.Now)})"); + } + } + + protected abstract void UpdateLibraryInternal(); internal abstract bool Test(); protected static class NetClient diff --git a/Tranga/TrangaSettings.cs b/Tranga/TrangaSettings.cs index b945fe5..3615d63 100644 --- a/Tranga/TrangaSettings.cs +++ b/Tranga/TrangaSettings.cs @@ -15,6 +15,7 @@ public static class TrangaSettings public static string workingDirectory { get; private set; } = Path.Join(RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "/usr/share" : Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "tranga-api"); public static int apiPortNumber { get; private set; } = 6531; public static string userAgent { get; private set; } = DefaultUserAgent; + public static bool bufferLibraryUpdates { get; private set; } = false; [JsonIgnore] public static string settingsFilePath => Path.Join(workingDirectory, "settings.json"); [JsonIgnore] public static string libraryConnectorsFilePath => Path.Join(workingDirectory, "libraryConnectors.json"); [JsonIgnore] public static string notificationConnectorsFilePath => Path.Join(workingDirectory, "notificationConnectors.json"); @@ -46,15 +47,16 @@ public static class TrangaSettings ExportSettings(); } - public static void CreateOrUpdate(string? downloadDirectory = null, string? pWorkingDirectory = null, int? pApiPortNumber = null, string? pUserAgent = null, bool? pAprilFoolsMode = null) + public static void CreateOrUpdate(string? downloadDirectory = null, string? pWorkingDirectory = null, int? pApiPortNumber = null, string? pUserAgent = null, bool? pAprilFoolsMode = null, bool? pBufferLibraryUpdates = null) { if(pWorkingDirectory is null && File.Exists(settingsFilePath)) LoadFromWorkingDirectory(workingDirectory); - TrangaSettings.downloadLocation = downloadDirectory ?? TrangaSettings.downloadLocation; - TrangaSettings.workingDirectory = pWorkingDirectory ?? TrangaSettings.workingDirectory; - TrangaSettings.apiPortNumber = pApiPortNumber ?? TrangaSettings.apiPortNumber; - TrangaSettings.userAgent = pUserAgent ?? TrangaSettings.userAgent; - TrangaSettings.aprilFoolsMode = pAprilFoolsMode ?? TrangaSettings.aprilFoolsMode; + downloadLocation = downloadDirectory ?? downloadLocation; + workingDirectory = pWorkingDirectory ?? workingDirectory; + apiPortNumber = pApiPortNumber ?? apiPortNumber; + userAgent = pUserAgent ?? userAgent; + aprilFoolsMode = pAprilFoolsMode ?? aprilFoolsMode; + bufferLibraryUpdates = pBufferLibraryUpdates ?? bufferLibraryUpdates; Directory.CreateDirectory(downloadLocation); Directory.CreateDirectory(workingDirectory); ExportSettings(); @@ -90,7 +92,7 @@ public static class TrangaSettings public static void UpdateAprilFoolsMode(bool enabled) { - TrangaSettings.aprilFoolsMode = enabled; + aprilFoolsMode = enabled; ExportSettings(); } @@ -102,10 +104,10 @@ public static class TrangaSettings else Directory.CreateDirectory(newPath); - if (moveFiles && Directory.Exists(TrangaSettings.downloadLocation)) - Directory.Move(TrangaSettings.downloadLocation, newPath); + if (moveFiles && Directory.Exists(downloadLocation)) + Directory.Move(downloadLocation, newPath); - TrangaSettings.downloadLocation = newPath; + downloadLocation = newPath; ExportSettings(); } @@ -116,26 +118,26 @@ public static class TrangaSettings GroupRead | GroupWrite | None | OtherRead | OtherWrite | UserRead | UserWrite); else Directory.CreateDirectory(newPath); - Directory.Move(TrangaSettings.workingDirectory, newPath); - TrangaSettings.workingDirectory = newPath; + Directory.Move(workingDirectory, newPath); + workingDirectory = newPath; ExportSettings(); } public static void UpdateUserAgent(string? customUserAgent) { - TrangaSettings.userAgent = customUserAgent ?? DefaultUserAgent; + userAgent = customUserAgent ?? DefaultUserAgent; ExportSettings(); } public static void UpdateRateLimit(RequestType requestType, int newLimit) { - TrangaSettings.requestLimits[requestType] = newLimit; + requestLimits[requestType] = newLimit; ExportSettings(); } public static void ResetRateLimits() { - TrangaSettings.requestLimits = DefaultRequestLimits; + requestLimits = DefaultRequestLimits; ExportSettings(); } @@ -154,13 +156,14 @@ public static class TrangaSettings public static JObject AsJObject() { JObject jobj = new JObject(); - jobj.Add("downloadLocation", JToken.FromObject(TrangaSettings.downloadLocation)); - jobj.Add("workingDirectory", JToken.FromObject(TrangaSettings.workingDirectory)); - jobj.Add("apiPortNumber", JToken.FromObject(TrangaSettings.apiPortNumber)); - jobj.Add("userAgent", JToken.FromObject(TrangaSettings.userAgent)); - jobj.Add("aprilFoolsMode", JToken.FromObject(TrangaSettings.aprilFoolsMode)); - jobj.Add("version", JToken.FromObject(TrangaSettings.version)); - jobj.Add("requestLimits", JToken.FromObject(TrangaSettings.requestLimits)); + jobj.Add("downloadLocation", JToken.FromObject(downloadLocation)); + jobj.Add("workingDirectory", JToken.FromObject(workingDirectory)); + jobj.Add("apiPortNumber", JToken.FromObject(apiPortNumber)); + jobj.Add("userAgent", JToken.FromObject(userAgent)); + jobj.Add("aprilFoolsMode", JToken.FromObject(aprilFoolsMode)); + jobj.Add("version", JToken.FromObject(version)); + jobj.Add("requestLimits", JToken.FromObject(requestLimits)); + jobj.Add("bufferLibraryUpdates", JToken.FromObject(bufferLibraryUpdates)); return jobj; } @@ -170,16 +173,18 @@ public static class TrangaSettings { JObject jobj = JObject.Parse(serialized); if (jobj.TryGetValue("downloadLocation", out JToken? dl)) - TrangaSettings.downloadLocation = dl.Value()!; + downloadLocation = dl.Value()!; if (jobj.TryGetValue("workingDirectory", out JToken? wd)) - TrangaSettings.workingDirectory = wd.Value()!; + workingDirectory = wd.Value()!; if (jobj.TryGetValue("apiPortNumber", out JToken? apn)) - TrangaSettings.apiPortNumber = apn.Value(); + apiPortNumber = apn.Value(); if (jobj.TryGetValue("userAgent", out JToken? ua)) - TrangaSettings.userAgent = ua.Value()!; + userAgent = ua.Value()!; if (jobj.TryGetValue("aprilFoolsMode", out JToken? afm)) - TrangaSettings.aprilFoolsMode = afm.Value()!; + aprilFoolsMode = afm.Value()!; if (jobj.TryGetValue("requestLimits", out JToken? rl)) - TrangaSettings.requestLimits = rl.ToObject>()!; + requestLimits = rl.ToObject>()!; + if (jobj.TryGetValue("bufferLibraryUpdates", out JToken? blu)) + bufferLibraryUpdates = blu.Value()!; } } \ No newline at end of file From d9228421866ee358a463e126c14bd8639013ae76 Mon Sep 17 00:00:00 2001 From: Glax Date: Sun, 22 Sep 2024 00:02:43 +0200 Subject: [PATCH 2/4] Add NotificationBuffer, so Notification are not spammed on every chapter. --- Tranga/GlobalBase.cs | 4 +- Tranga/Jobs/DownloadChapter.cs | 2 +- Tranga/NotificationConnectors/Gotify.cs | 2 +- Tranga/NotificationConnectors/LunaSea.cs | 2 +- .../NotificationConnector.cs | 58 ++++++++++++++++++- Tranga/NotificationConnectors/Ntfy.cs | 2 +- Tranga/TrangaSettings.cs | 7 ++- 7 files changed, 69 insertions(+), 8 deletions(-) diff --git a/Tranga/GlobalBase.cs b/Tranga/GlobalBase.cs index 49a0d79..5466cd1 100644 --- a/Tranga/GlobalBase.cs +++ b/Tranga/GlobalBase.cs @@ -66,10 +66,10 @@ public abstract class GlobalBase Log(string.Format(fStr, replace)); } - protected void SendNotifications(string title, string text) + protected void SendNotifications(string title, string text, bool buffer = false) { foreach (NotificationConnector nc in notificationConnectors) - nc.SendNotification(title, text); + nc.SendNotification(title, text, buffer); } protected void AddNotificationConnector(NotificationConnector notificationConnector) diff --git a/Tranga/Jobs/DownloadChapter.cs b/Tranga/Jobs/DownloadChapter.cs index 434736e..4e8456b 100644 --- a/Tranga/Jobs/DownloadChapter.cs +++ b/Tranga/Jobs/DownloadChapter.cs @@ -37,7 +37,7 @@ public class DownloadChapter : Job if (success == HttpStatusCode.OK) { UpdateLibraries(); - SendNotifications("Chapter downloaded", $"{chapter.parentManga.sortName} - {chapter.chapterNumber}"); + SendNotifications("Chapter downloaded", $"{chapter.parentManga.sortName} - {chapter.chapterNumber}", true); } }); downloadTask.Start(); diff --git a/Tranga/NotificationConnectors/Gotify.cs b/Tranga/NotificationConnectors/Gotify.cs index cbe3d9c..3dc7f28 100644 --- a/Tranga/NotificationConnectors/Gotify.cs +++ b/Tranga/NotificationConnectors/Gotify.cs @@ -24,7 +24,7 @@ public class Gotify : NotificationConnector return $"Gotify {endpoint}"; } - public override void SendNotification(string title, string notificationText) + protected override void SendNotificationInternal(string title, string notificationText) { Log($"Sending notification: {title} - {notificationText}"); MessageData message = new(title, notificationText); diff --git a/Tranga/NotificationConnectors/LunaSea.cs b/Tranga/NotificationConnectors/LunaSea.cs index 41c220a..e124192 100644 --- a/Tranga/NotificationConnectors/LunaSea.cs +++ b/Tranga/NotificationConnectors/LunaSea.cs @@ -20,7 +20,7 @@ public class LunaSea : NotificationConnector return $"LunaSea {id}"; } - public override void SendNotification(string title, string notificationText) + protected override void SendNotificationInternal(string title, string notificationText) { Log($"Sending notification: {title} - {notificationText}"); MessageData message = new(title, notificationText); diff --git a/Tranga/NotificationConnectors/NotificationConnector.cs b/Tranga/NotificationConnectors/NotificationConnector.cs index 51b9469..b75813d 100644 --- a/Tranga/NotificationConnectors/NotificationConnector.cs +++ b/Tranga/NotificationConnectors/NotificationConnector.cs @@ -3,14 +3,70 @@ public abstract class NotificationConnector : GlobalBase { public readonly NotificationConnectorType notificationConnectorType; + private DateTime? _notificationRequested = null; + private readonly Thread? _notificationBufferThread = null; + private const int NoChangeTimeout = 3, BiggestInterval = 30; + private List> _notifications = new(); protected NotificationConnector(GlobalBase clone, NotificationConnectorType notificationConnectorType) : base(clone) { Log($"Creating notificationConnector {Enum.GetName(notificationConnectorType)}"); this.notificationConnectorType = notificationConnectorType; + + + if (TrangaSettings.bufferLibraryUpdates) + { + _notificationBufferThread = new(CheckNotificationBuffer); + _notificationBufferThread.Start(); + } + } + + private void CheckNotificationBuffer() + { + while (true) + { + if (_notificationRequested is not null && DateTime.Now.Subtract((DateTime)_notificationRequested) > TimeSpan.FromMinutes(NoChangeTimeout)) //If no updates have been requested for NoChangeTimeout minutes, update library + { + string[] uniqueTitles = _notifications.DistinctBy(n => n.Key).Select(n => n.Key).ToArray(); + Log($"Notification Buffer sending! {string.Join(", ", uniqueTitles)}"); + foreach (string ut in uniqueTitles) + { + string[] texts = _notifications.Where(n => n.Key == ut).Select(n => n.Value).ToArray(); + SendNotificationInternal(ut, string.Join('\n', texts)); + } + _notificationRequested = null; + } + Thread.Sleep(100); + } } public enum NotificationConnectorType : byte { Gotify = 0, LunaSea = 1, Ntfy = 2 } + + public void SendNotification(string title, string notificationText, bool buffer = false) + { + _notificationRequested ??= DateTime.Now; + if (!TrangaSettings.bufferLibraryUpdates || !buffer) + { + SendNotificationInternal(title, notificationText); + return; + } + _notifications.Add(new(title, notificationText)); + if (_notificationRequested is not null && + DateTime.Now.Subtract((DateTime)_notificationRequested) > TimeSpan.FromMinutes(BiggestInterval)) //If the last update has been more than BiggestInterval minutes ago, update library + { + string[] uniqueTitles = _notifications.DistinctBy(n => n.Key).Select(n => n.Key).ToArray(); + foreach (string ut in uniqueTitles) + { + string[] texts = _notifications.Where(n => n.Key == ut).Select(n => n.Value).ToArray(); + SendNotificationInternal(ut, string.Join('\n', texts)); + } + _notificationRequested = null; + } + else if(_notificationRequested is not null) + { + Log($"Buffering Notifications (Updates in latest {((DateTime)_notificationRequested).Add(TimeSpan.FromMinutes(BiggestInterval)).Subtract(DateTime.Now)} or {((DateTime)_notificationRequested).Add(TimeSpan.FromMinutes(NoChangeTimeout)).Subtract(DateTime.Now)})"); + } + } - public abstract void SendNotification(string title, string notificationText); + protected abstract void SendNotificationInternal(string title, string notificationText); } \ No newline at end of file diff --git a/Tranga/NotificationConnectors/Ntfy.cs b/Tranga/NotificationConnectors/Ntfy.cs index f19fe34..afad9a6 100644 --- a/Tranga/NotificationConnectors/Ntfy.cs +++ b/Tranga/NotificationConnectors/Ntfy.cs @@ -54,7 +54,7 @@ public class Ntfy : NotificationConnector return $"Ntfy {endpoint} {topic}"; } - public override void SendNotification(string title, string notificationText) + protected override void SendNotificationInternal(string title, string notificationText) { Log($"Sending notification: {title} - {notificationText}"); MessageData message = new(title, topic, notificationText); diff --git a/Tranga/TrangaSettings.cs b/Tranga/TrangaSettings.cs index 3615d63..ccfa997 100644 --- a/Tranga/TrangaSettings.cs +++ b/Tranga/TrangaSettings.cs @@ -16,6 +16,7 @@ public static class TrangaSettings public static int apiPortNumber { get; private set; } = 6531; public static string userAgent { get; private set; } = DefaultUserAgent; public static bool bufferLibraryUpdates { get; private set; } = false; + public static bool bufferNotifications { get; private set; } = false; [JsonIgnore] public static string settingsFilePath => Path.Join(workingDirectory, "settings.json"); [JsonIgnore] public static string libraryConnectorsFilePath => Path.Join(workingDirectory, "libraryConnectors.json"); [JsonIgnore] public static string notificationConnectorsFilePath => Path.Join(workingDirectory, "notificationConnectors.json"); @@ -47,7 +48,7 @@ public static class TrangaSettings ExportSettings(); } - public static void CreateOrUpdate(string? downloadDirectory = null, string? pWorkingDirectory = null, int? pApiPortNumber = null, string? pUserAgent = null, bool? pAprilFoolsMode = null, bool? pBufferLibraryUpdates = null) + public static void CreateOrUpdate(string? downloadDirectory = null, string? pWorkingDirectory = null, int? pApiPortNumber = null, string? pUserAgent = null, bool? pAprilFoolsMode = null, bool? pBufferLibraryUpdates = null, bool? pBufferNotifications = null) { if(pWorkingDirectory is null && File.Exists(settingsFilePath)) LoadFromWorkingDirectory(workingDirectory); @@ -57,6 +58,7 @@ public static class TrangaSettings userAgent = pUserAgent ?? userAgent; aprilFoolsMode = pAprilFoolsMode ?? aprilFoolsMode; bufferLibraryUpdates = pBufferLibraryUpdates ?? bufferLibraryUpdates; + bufferNotifications = pBufferNotifications ?? bufferNotifications; Directory.CreateDirectory(downloadLocation); Directory.CreateDirectory(workingDirectory); ExportSettings(); @@ -164,6 +166,7 @@ public static class TrangaSettings jobj.Add("version", JToken.FromObject(version)); jobj.Add("requestLimits", JToken.FromObject(requestLimits)); jobj.Add("bufferLibraryUpdates", JToken.FromObject(bufferLibraryUpdates)); + jobj.Add("bufferNotifications", JToken.FromObject(bufferNotifications)); return jobj; } @@ -186,5 +189,7 @@ public static class TrangaSettings requestLimits = rl.ToObject>()!; if (jobj.TryGetValue("bufferLibraryUpdates", out JToken? blu)) bufferLibraryUpdates = blu.Value()!; + if (jobj.TryGetValue("bufferNotifications", out JToken? bn)) + bufferNotifications = bn.Value()!; } } \ No newline at end of file From eb6c37cc53903aebfbf061001ae35e19eee95565 Mon Sep 17 00:00:00 2001 From: Glax Date: Sun, 22 Sep 2024 00:05:09 +0200 Subject: [PATCH 3/4] Output settings.json on startup --- Tranga/Tranga.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Tranga/Tranga.cs b/Tranga/Tranga.cs index 3c9a209..2762a94 100644 --- a/Tranga/Tranga.cs +++ b/Tranga/Tranga.cs @@ -34,6 +34,7 @@ public partial class Tranga : GlobalBase 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)]); + Log(TrangaSettings.AsJObject().ToString()); } public MangaConnector? GetConnector(string name) From fcdbd32872a9c75010218a9e9c6cf694f25208ed Mon Sep 17 00:00:00 2001 From: Glax Date: Sun, 22 Sep 2024 00:09:45 +0200 Subject: [PATCH 4/4] Include amount of notifications of type in title --- Tranga/NotificationConnectors/NotificationConnector.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tranga/NotificationConnectors/NotificationConnector.cs b/Tranga/NotificationConnectors/NotificationConnector.cs index b75813d..76576ca 100644 --- a/Tranga/NotificationConnectors/NotificationConnector.cs +++ b/Tranga/NotificationConnectors/NotificationConnector.cs @@ -28,11 +28,11 @@ public abstract class NotificationConnector : GlobalBase if (_notificationRequested is not null && DateTime.Now.Subtract((DateTime)_notificationRequested) > TimeSpan.FromMinutes(NoChangeTimeout)) //If no updates have been requested for NoChangeTimeout minutes, update library { string[] uniqueTitles = _notifications.DistinctBy(n => n.Key).Select(n => n.Key).ToArray(); - Log($"Notification Buffer sending! {string.Join(", ", uniqueTitles)}"); + Log($"Notification Buffer sending! Notifications: {string.Join(", ", uniqueTitles)}"); foreach (string ut in uniqueTitles) { string[] texts = _notifications.Where(n => n.Key == ut).Select(n => n.Value).ToArray(); - SendNotificationInternal(ut, string.Join('\n', texts)); + SendNotificationInternal($"{ut} ({texts.Length})", string.Join('\n', texts)); } _notificationRequested = null; }