diff --git a/src/Core/AutoOptimizationPolicy.cs b/src/Core/AutoOptimizationPolicy.cs new file mode 100644 index 00000000..121ba395 --- /dev/null +++ b/src/Core/AutoOptimizationPolicy.cs @@ -0,0 +1,31 @@ +using System; + +namespace WinMemoryCleaner +{ + internal static class AutoOptimizationPolicy + { + internal static Enums.Memory.Optimization.Reason? GetReason( + DateTimeOffset now, + DateTimeOffset lastAutoOptimizationByInterval, + DateTimeOffset lastAutoOptimizationByMemoryUsage, + int autoOptimizationInterval, + int autoOptimizationMemoryUsage, + int freePhysicalMemoryPercentage) + { + if (autoOptimizationInterval > 0 && + now.Subtract(lastAutoOptimizationByInterval).TotalHours >= autoOptimizationInterval) + { + return Enums.Memory.Optimization.Reason.Schedule; + } + + if (autoOptimizationMemoryUsage > 0 && + freePhysicalMemoryPercentage < autoOptimizationMemoryUsage && + now.Subtract(lastAutoOptimizationByMemoryUsage).TotalMinutes >= Constants.App.AutoOptimizationMemoryUsageInterval) + { + return Enums.Memory.Optimization.Reason.LowMemory; + } + + return null; + } + } +} diff --git a/src/Test/AutoOptimizationPolicyTests.cs b/src/Test/AutoOptimizationPolicyTests.cs new file mode 100644 index 00000000..09a7f301 --- /dev/null +++ b/src/Test/AutoOptimizationPolicyTests.cs @@ -0,0 +1,103 @@ +using NUnit.Framework; +using System; + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace WinMemoryCleaner.AutomationTests +{ + [TestFixture] + public class AutoOptimizationPolicyTests + { + [Test] + public void DisabledBoth_ReturnsNull() + { + var now = new DateTimeOffset(2024, 1, 1, 12, 0, 0, TimeSpan.Zero); + + var result = AutoOptimizationPolicy.GetReason(now, now.AddHours(-24), now.AddMinutes(-30), 0, 0, 0); + + Assert.IsNull(result); + } + + [Test] + public void Schedule_WhenJustBeforeDue_ReturnsNull() + { + var now = new DateTimeOffset(2024, 1, 1, 12, 0, 0, TimeSpan.Zero); + + var result = AutoOptimizationPolicy.GetReason(now, now.AddHours(-1).AddTicks(1), now, 1, 0, 0); + + Assert.IsNull(result); + } + + [Test] + public void Schedule_WhenExactlyDue_ReturnsSchedule() + { + var now = new DateTimeOffset(2024, 1, 1, 12, 0, 0, TimeSpan.Zero); + + var result = AutoOptimizationPolicy.GetReason(now, now.AddHours(-1), now, 1, 0, 0); + + Assert.AreEqual(Enums.Memory.Optimization.Reason.Schedule, result); + } + + [Test] + public void LowMemory_WhenFreePercentageIsBelowThreshold_ReturnsLowMemory() + { + var now = new DateTimeOffset(2024, 1, 1, 12, 0, 0, TimeSpan.Zero); + + var result = AutoOptimizationPolicy.GetReason(now, now, now.AddMinutes(-Constants.App.AutoOptimizationMemoryUsageInterval), 0, 40, 39); + + Assert.AreEqual(Enums.Memory.Optimization.Reason.LowMemory, result); + } + + [Test] + public void LowMemory_WhenFreePercentageEqualsThreshold_ReturnsNull() + { + var now = new DateTimeOffset(2024, 1, 1, 12, 0, 0, TimeSpan.Zero); + + var result = AutoOptimizationPolicy.GetReason(now, now, now.AddMinutes(-Constants.App.AutoOptimizationMemoryUsageInterval), 0, 40, 40); + + Assert.IsNull(result); + } + + [Test] + public void LowMemory_WhenCooldownIsJustBeforeDue_ReturnsNull() + { + var now = new DateTimeOffset(2024, 1, 1, 12, 0, 0, TimeSpan.Zero); + + var result = AutoOptimizationPolicy.GetReason(now, now, now.AddMinutes(-Constants.App.AutoOptimizationMemoryUsageInterval).AddTicks(1), 0, 40, 39); + + Assert.IsNull(result); + } + + [Test] + public void LowMemory_WhenCooldownIsExactlyDue_ReturnsLowMemory() + { + var now = new DateTimeOffset(2024, 1, 1, 12, 0, 0, TimeSpan.Zero); + + var result = AutoOptimizationPolicy.GetReason(now, now, now.AddMinutes(-Constants.App.AutoOptimizationMemoryUsageInterval), 0, 40, 39); + + Assert.AreEqual(Enums.Memory.Optimization.Reason.LowMemory, result); + } + + [Test] + public void Schedule_WhenBothTriggersAreDue_ReturnsSchedule() + { + var now = new DateTimeOffset(2024, 1, 1, 12, 0, 0, TimeSpan.Zero); + + var result = AutoOptimizationPolicy.GetReason(now, now.AddHours(-1), now.AddMinutes(-Constants.App.AutoOptimizationMemoryUsageInterval), 1, 40, 39); + + Assert.AreEqual(Enums.Memory.Optimization.Reason.Schedule, result); + } + + [Test] + public void Schedule_WhenNonZeroIntervalCrossesDateBoundary_ReturnsSchedule() + { + var now = new DateTimeOffset(2024, 1, 2, 0, 0, 0, TimeSpan.Zero); + + var result = AutoOptimizationPolicy.GetReason(now, now.AddHours(-7), now, 7, 35, 100); + + Assert.AreEqual(Enums.Memory.Optimization.Reason.Schedule, result); + } + } +} + +#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member diff --git a/src/ViewModel/MainViewModel.cs b/src/ViewModel/MainViewModel.cs index 84feb147..bd65bcdd 100644 --- a/src/ViewModel/MainViewModel.cs +++ b/src/ViewModel/MainViewModel.cs @@ -1636,24 +1636,23 @@ private void MonitorApp() { if (CanOptimize) { - // Interval - if (Settings.AutoOptimizationInterval > 0 && - DateTimeOffset.Now.Subtract(_lastAutoOptimizationByInterval).TotalHours >= Settings.AutoOptimizationInterval) + var reason = AutoOptimizationPolicy.GetReason( + DateTimeOffset.Now, + _lastAutoOptimizationByInterval, + _lastAutoOptimizationByMemoryUsage, + Settings.AutoOptimizationInterval, + Settings.AutoOptimizationMemoryUsage, + Computer.Memory.Physical.Free.Percentage); + + if (reason.HasValue) { - OptimizeAsync(Enums.Memory.Optimization.Reason.Schedule); + OptimizeAsync(reason.Value); - _lastAutoOptimizationByInterval = DateTimeOffset.Now; - continue; - } - - // Memory usage - if (Settings.AutoOptimizationMemoryUsage > 0 && - Computer.Memory.Physical.Free.Percentage < Settings.AutoOptimizationMemoryUsage && - DateTimeOffset.Now.Subtract(_lastAutoOptimizationByMemoryUsage).TotalMinutes >= Constants.App.AutoOptimizationMemoryUsageInterval) - { - OptimizeAsync(Enums.Memory.Optimization.Reason.LowMemory); + if (reason.Value == Enums.Memory.Optimization.Reason.Schedule) + _lastAutoOptimizationByInterval = DateTimeOffset.Now; + else + _lastAutoOptimizationByMemoryUsage = DateTimeOffset.Now; - _lastAutoOptimizationByMemoryUsage = DateTimeOffset.Now; continue; } } diff --git a/src/WinMemoryCleaner.csproj b/src/WinMemoryCleaner.csproj index 6bd06c24..5bb722d2 100644 --- a/src/WinMemoryCleaner.csproj +++ b/src/WinMemoryCleaner.csproj @@ -118,6 +118,7 @@ + @@ -158,6 +159,7 @@ + diff --git a/src/WindowsService/WinService.cs b/src/WindowsService/WinService.cs index a77aee8a..11c0b2e1 100644 --- a/src/WindowsService/WinService.cs +++ b/src/WindowsService/WinService.cs @@ -116,24 +116,23 @@ private void OnTimer(object sender, ElapsedEventArgs e) // Update memory info _computer.Memory = _computerService.Memory; - // Interval - if (Settings.AutoOptimizationInterval > 0 && - DateTimeOffset.Now.Subtract(_lastAutoOptimizationByInterval).TotalHours >= Settings.AutoOptimizationInterval) + var reason = AutoOptimizationPolicy.GetReason( + DateTimeOffset.Now, + _lastAutoOptimizationByInterval, + _lastAutoOptimizationByMemoryUsage, + Settings.AutoOptimizationInterval, + Settings.AutoOptimizationMemoryUsage, + _computer.Memory.Physical.Free.Percentage); + + if (reason.HasValue) { - DependencyInjection.Container.Resolve().Optimize(Enums.Memory.Optimization.Reason.Schedule, Settings.MemoryAreas); + DependencyInjection.Container.Resolve().Optimize(reason.Value, Settings.MemoryAreas); - _lastAutoOptimizationByInterval = DateTimeOffset.Now; - return; - } - - // Memory usage - if (Settings.AutoOptimizationMemoryUsage > 0 && - _computer.Memory.Physical.Free.Percentage < Settings.AutoOptimizationMemoryUsage && - DateTimeOffset.Now.Subtract(_lastAutoOptimizationByMemoryUsage).TotalMinutes >= Constants.App.AutoOptimizationMemoryUsageInterval) - { - DependencyInjection.Container.Resolve().Optimize(Enums.Memory.Optimization.Reason.LowMemory, Settings.MemoryAreas); + if (reason.Value == Enums.Memory.Optimization.Reason.Schedule) + _lastAutoOptimizationByInterval = DateTimeOffset.Now; + else + _lastAutoOptimizationByMemoryUsage = DateTimeOffset.Now; - _lastAutoOptimizationByMemoryUsage = DateTimeOffset.Now; return; } }