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/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/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..76576ca 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! Notifications: {string.Join(", ", uniqueTitles)}"); + foreach (string ut in uniqueTitles) + { + string[] texts = _notifications.Where(n => n.Key == ut).Select(n => n.Value).ToArray(); + SendNotificationInternal($"{ut} ({texts.Length})", 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/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) diff --git a/Tranga/TrangaSettings.cs b/Tranga/TrangaSettings.cs index b945fe5..ccfa997 100644 --- a/Tranga/TrangaSettings.cs +++ b/Tranga/TrangaSettings.cs @@ -15,6 +15,8 @@ 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; + 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"); @@ -46,15 +48,17 @@ 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, bool? pBufferNotifications = 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; + bufferNotifications = pBufferNotifications ?? bufferNotifications; Directory.CreateDirectory(downloadLocation); Directory.CreateDirectory(workingDirectory); ExportSettings(); @@ -90,7 +94,7 @@ public static class TrangaSettings public static void UpdateAprilFoolsMode(bool enabled) { - TrangaSettings.aprilFoolsMode = enabled; + aprilFoolsMode = enabled; ExportSettings(); } @@ -102,10 +106,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 +120,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 +158,15 @@ 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)); + jobj.Add("bufferNotifications", JToken.FromObject(bufferNotifications)); return jobj; } @@ -170,16 +176,20 @@ 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()!; + if (jobj.TryGetValue("bufferNotifications", out JToken? bn)) + bufferNotifications = bn.Value()!; } } \ No newline at end of file