From e3bb8d2c5506f0d3152c1782e9d1f10f0b7dadd6 Mon Sep 17 00:00:00 2001 From: mpagani Date: Wed, 16 Jul 2025 12:22:20 +0200 Subject: [PATCH 1/3] feat: Add UserActivityTracker to monitor user activity Introduce a new `UserActivityTracker` class in the `SourceGit.Models` namespace to track user activity and determine when to perform auto-fetch operations. Update the `App.axaml.cs` file to initialize the tracker at startup and modify `Repository.cs` to utilize this new functionality, ensuring auto-fetching occurs only during user inactivity. --- src/App.axaml.cs | 1 + src/Models/UserActivityTracker.cs | 80 +++++++++++++++++++++++++++++++ src/ViewModels/Repository.cs | 4 +- 3 files changed, 82 insertions(+), 3 deletions(-) create mode 100644 src/Models/UserActivityTracker.cs diff --git a/src/App.axaml.cs b/src/App.axaml.cs index f5c0559a1..a106ca0e0 100644 --- a/src/App.axaml.cs +++ b/src/App.axaml.cs @@ -575,6 +575,7 @@ private void TryLaunchAsNormal(IClassicDesktopStyleApplicationLifetime desktop) _launcher = new ViewModels.Launcher(startupRepo); desktop.MainWindow = new Views.Launcher() { DataContext = _launcher }; + Models.UserActivityTracker.Instance.Initialize(); desktop.ShutdownMode = ShutdownMode.OnMainWindowClose; #if !DISABLE_UPDATE_DETECTION diff --git a/src/Models/UserActivityTracker.cs b/src/Models/UserActivityTracker.cs new file mode 100644 index 000000000..16280f0cb --- /dev/null +++ b/src/Models/UserActivityTracker.cs @@ -0,0 +1,80 @@ +using System; +using System.Threading; + +namespace SourceGit.Models +{ + public class UserActivityTracker + { + private static readonly Lazy _instance = new(() => new UserActivityTracker()); + private bool _isWindowActive = false; + private DateTime _lastActivity = DateTime.MinValue; + private readonly Lock _lockObject = new(); + private readonly int _minIdleSecondsBeforeAutoFetch = 15; + + private void OnUserActivity(object sender, EventArgs e) => UpdateLastActivity(); + + private void OnWindowActivated(object sender, EventArgs e) + { + lock (_lockObject) + { + _isWindowActive = true; + _lastActivity = DateTime.Now; + } + } + + private void OnWindowDeactivated(object sender, EventArgs e) + { + lock (_lockObject) + _isWindowActive = false; + } + + public void Initialize() + { + lock (_lockObject) + { + _lastActivity = DateTime.Now; + _isWindowActive = true; + } + + if (App.Current?.ApplicationLifetime is Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime desktop) + if (desktop.MainWindow != null) + { + desktop.MainWindow.Activated += OnWindowActivated; + desktop.MainWindow.Deactivated += OnWindowDeactivated; + desktop.MainWindow.KeyDown += OnUserActivity; + desktop.MainWindow.PointerPressed += OnUserActivity; + desktop.MainWindow.PointerMoved += OnUserActivity; + desktop.MainWindow.PointerWheelChanged += OnUserActivity; + } + } + + public bool ShouldPerformAutoFetch(DateTime lastFetchTime, int intervalMinutes) + { + var now = DateTime.Now; + + if (now < lastFetchTime.AddMinutes(intervalMinutes)) + return false; + + lock (_lockObject) + { + if (!_isWindowActive) + return true; + + var timeSinceLastActivity = now - _lastActivity; + + if (timeSinceLastActivity.TotalSeconds >= _minIdleSecondsBeforeAutoFetch) + return true; + + return false; + } + } + + public void UpdateLastActivity() + { + lock (_lockObject) + _lastActivity = DateTime.Now; + } + + public static UserActivityTracker Instance => _instance.Value; + } +} diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs index e51347457..5b29d19c6 100644 --- a/src/ViewModels/Repository.cs +++ b/src/ViewModels/Repository.cs @@ -3074,9 +3074,7 @@ private async void AutoFetchImpl(object sender) if (File.Exists(lockFile)) return; - var now = DateTime.Now; - var desire = _lastFetchTime.AddMinutes(_settings.AutoFetchInterval); - if (desire > now) + if (!Models.UserActivityTracker.Instance.ShouldPerformAutoFetch(_lastFetchTime, _settings.AutoFetchInterval)) return; var remotes = new List(); From 564cad1c1e1e820095e7fc220feb3093c0dd37a0 Mon Sep 17 00:00:00 2001 From: mpagani Date: Wed, 16 Jul 2025 12:25:28 +0200 Subject: [PATCH 2/3] fix: Rename UserActivityTracker instance variable Updated the private static readonly instance of the `UserActivityTracker` class from `_instance` to `s_instance`. This change enhances code readability by using the `s_` prefix to indicate that the variable is static. --- src/Models/UserActivityTracker.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Models/UserActivityTracker.cs b/src/Models/UserActivityTracker.cs index 16280f0cb..838e3e164 100644 --- a/src/Models/UserActivityTracker.cs +++ b/src/Models/UserActivityTracker.cs @@ -5,7 +5,7 @@ namespace SourceGit.Models { public class UserActivityTracker { - private static readonly Lazy _instance = new(() => new UserActivityTracker()); + private static readonly Lazy s_instance = new(() => new UserActivityTracker()); private bool _isWindowActive = false; private DateTime _lastActivity = DateTime.MinValue; private readonly Lock _lockObject = new(); @@ -75,6 +75,6 @@ public void UpdateLastActivity() _lastActivity = DateTime.Now; } - public static UserActivityTracker Instance => _instance.Value; + public static UserActivityTracker Instance => s_instance.Value; } } From bd7c2fbdfd771406320bd953244705e0ad205023 Mon Sep 17 00:00:00 2001 From: mpagani Date: Wed, 16 Jul 2025 13:30:25 +0200 Subject: [PATCH 3/3] refactor: Copilot review --- src/Models/UserActivityTracker.cs | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/src/Models/UserActivityTracker.cs b/src/Models/UserActivityTracker.cs index 838e3e164..78e28eb23 100644 --- a/src/Models/UserActivityTracker.cs +++ b/src/Models/UserActivityTracker.cs @@ -5,11 +5,13 @@ namespace SourceGit.Models { public class UserActivityTracker { + private const int DefaultMinIdleSecondsBeforeAutoFetch = 15; + private static readonly Lazy s_instance = new(() => new UserActivityTracker()); private bool _isWindowActive = false; private DateTime _lastActivity = DateTime.MinValue; private readonly Lock _lockObject = new(); - private readonly int _minIdleSecondsBeforeAutoFetch = 15; + private readonly int _minIdleSecondsBeforeAutoFetch = DefaultMinIdleSecondsBeforeAutoFetch; private void OnUserActivity(object sender, EventArgs e) => UpdateLastActivity(); @@ -36,16 +38,18 @@ public void Initialize() _isWindowActive = true; } - if (App.Current?.ApplicationLifetime is Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime desktop) - if (desktop.MainWindow != null) - { - desktop.MainWindow.Activated += OnWindowActivated; - desktop.MainWindow.Deactivated += OnWindowDeactivated; - desktop.MainWindow.KeyDown += OnUserActivity; - desktop.MainWindow.PointerPressed += OnUserActivity; - desktop.MainWindow.PointerMoved += OnUserActivity; - desktop.MainWindow.PointerWheelChanged += OnUserActivity; - } + if (App.Current?.ApplicationLifetime is not Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime desktop) + return; + + if (desktop.MainWindow == null) + return; + + desktop.MainWindow.Activated += OnWindowActivated; + desktop.MainWindow.Deactivated += OnWindowDeactivated; + desktop.MainWindow.KeyDown += OnUserActivity; + desktop.MainWindow.PointerPressed += OnUserActivity; + desktop.MainWindow.PointerMoved += OnUserActivity; + desktop.MainWindow.PointerWheelChanged += OnUserActivity; } public bool ShouldPerformAutoFetch(DateTime lastFetchTime, int intervalMinutes) @@ -64,9 +68,9 @@ public bool ShouldPerformAutoFetch(DateTime lastFetchTime, int intervalMinutes) if (timeSinceLastActivity.TotalSeconds >= _minIdleSecondsBeforeAutoFetch) return true; - - return false; } + + return false; } public void UpdateLastActivity()