From 1806b02039089dd2796f774d7ab1500f207a9824 Mon Sep 17 00:00:00 2001
From: Lorenz Stechauner <lorenz.stechauner@necronda.net>
Date: Tue, 19 Mar 2024 13:17:06 +0100
Subject: [PATCH] [#43] App: Use FileSystemWatcher to renew contexts on demand

---
 Elwig/App.xaml.cs                           | 10 +++++
 Elwig/Helpers/ClientParameters.cs           |  1 -
 Elwig/Windows/AdministrationWindow.cs       |  2 +-
 Elwig/Windows/AreaComAdminWindow.xaml.cs    |  5 +--
 Elwig/Windows/BaseDataWindow.xaml.cs        |  5 +--
 Elwig/Windows/ChartWindow.xaml.cs           |  7 ++--
 Elwig/Windows/ContextWindow.cs              | 25 +++---------
 Elwig/Windows/DeliveryAdminWindow.xaml.cs   |  5 +--
 Elwig/Windows/MailWindow.xaml.cs            |  3 +-
 Elwig/Windows/MemberAdminWindow.xaml.cs     |  5 +--
 Elwig/Windows/OriginHierarchyWindow.xaml.cs | 44 ++++++++++-----------
 Elwig/Windows/PaymentVariantsWindow.xaml.cs |  3 +-
 Elwig/Windows/SeasonFinishWindow.xaml.cs    |  2 +-
 13 files changed, 51 insertions(+), 66 deletions(-)

diff --git a/Elwig/App.xaml.cs b/Elwig/App.xaml.cs
index 1e5ffbc..69dcd55 100644
--- a/Elwig/App.xaml.cs
+++ b/Elwig/App.xaml.cs
@@ -60,6 +60,10 @@ namespace Elwig {
         public static ClientParameters Client { get; set; }
 
         public static Dispatcher MainDispatcher { get; private set; }
+        private readonly FileSystemWatcher Watcher = new(Path.GetDirectoryName(Config.DatabaseFile) ?? "C:\\") {
+            NotifyFilter = NotifyFilters.LastWrite,
+            Filter = "*.sqlite3",
+        };
 
         public App() : base() {
             System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);
@@ -69,6 +73,12 @@ namespace Elwig {
             Scales = [];
             CurrentApp = this;
             OverrideCulture();
+            Watcher.Changed += new FileSystemEventHandler(OnChanged);
+            Watcher.EnableRaisingEvents = true;
+        }
+
+        private void OnChanged(object source, FileSystemEventArgs e) {
+            MainDispatcher.BeginInvoke(async () => await HintContextChange());
         }
 
         private static void OverrideCulture() {
diff --git a/Elwig/Helpers/ClientParameters.cs b/Elwig/Helpers/ClientParameters.cs
index b6b3dc0..fe86ae3 100644
--- a/Elwig/Helpers/ClientParameters.cs
+++ b/Elwig/Helpers/ClientParameters.cs
@@ -171,7 +171,6 @@ namespace Elwig.Helpers {
             }
 
             await cmd.ExecuteNonQueryAsync();
-            await App.HintContextChange();
         }
     }
 }
diff --git a/Elwig/Windows/AdministrationWindow.cs b/Elwig/Windows/AdministrationWindow.cs
index 0478174..19a2409 100644
--- a/Elwig/Windows/AdministrationWindow.cs
+++ b/Elwig/Windows/AdministrationWindow.cs
@@ -103,7 +103,7 @@ namespace Elwig.Windows {
 
         abstract protected void UpdateButtons();
 
-        protected override async Task OnRenewContext() {
+        protected override async Task OnRenewContext(AppDbContext ctx) {
             for (int i = 0; i < PlzInputs.Length; i++)
                 await UpdatePlz(PlzInputs[i], PlzOrtInputs[i]);
         }
diff --git a/Elwig/Windows/AreaComAdminWindow.xaml.cs b/Elwig/Windows/AreaComAdminWindow.xaml.cs
index 3910981..a6d32f9 100644
--- a/Elwig/Windows/AreaComAdminWindow.xaml.cs
+++ b/Elwig/Windows/AreaComAdminWindow.xaml.cs
@@ -189,9 +189,8 @@ namespace Elwig.Windows {
             ValidateRequiredInputs();
         }
 
-        protected override async Task OnRenewContext() {
-            await base.OnRenewContext();
-            using var ctx = new AppDbContext();
+        protected override async Task OnRenewContext(AppDbContext ctx) {
+            await base.OnRenewContext(ctx);
             ControlUtils.RenewItemsSource(KgInput, await ctx.WbKgs
                 .Include(k => k.AtKg.WbKg!.Rds)
                 .Select(k => k.AtKg)
diff --git a/Elwig/Windows/BaseDataWindow.xaml.cs b/Elwig/Windows/BaseDataWindow.xaml.cs
index ab8d084..56c898d 100644
--- a/Elwig/Windows/BaseDataWindow.xaml.cs
+++ b/Elwig/Windows/BaseDataWindow.xaml.cs
@@ -131,9 +131,8 @@ namespace Elwig.Windows {
             LockInputs();
         }
 
-        protected override async Task OnRenewContext() {
-            await base.OnRenewContext();
-            using var ctx = new AppDbContext();
+        protected override async Task OnRenewContext(AppDbContext ctx) {
+            await base.OnRenewContext(ctx);
             FillInputs(App.Client);
             ControlUtils.RenewItemsSource(SeasonList, await ctx.Seasons
                 .OrderByDescending(s => s.Year)
diff --git a/Elwig/Windows/ChartWindow.xaml.cs b/Elwig/Windows/ChartWindow.xaml.cs
index 1935b53..e2e15f6 100644
--- a/Elwig/Windows/ChartWindow.xaml.cs
+++ b/Elwig/Windows/ChartWindow.xaml.cs
@@ -100,8 +100,7 @@ namespace Elwig.Windows {
             SaveButton.IsEnabled = hasChanged;
         }
 
-        private async Task RefreshGraphList() {
-            using var ctx = new AppDbContext();
+        private async Task RefreshGraphList(AppDbContext ctx) {
             PaymentVar = await ctx.PaymentVariants.FindAsync(Year, AvNr) ?? throw new ArgumentException("PaymentVar not found");
             Season = await ctx.Seasons.FindAsync(Year) ?? throw new ArgumentException("Season not found");
 
@@ -201,8 +200,8 @@ namespace Elwig.Windows {
             FillingInputs = false;
         }
 
-        protected override async Task OnRenewContext() {
-            await RefreshGraphList();
+        protected override async Task OnRenewContext(AppDbContext ctx) {
+            await RefreshGraphList(ctx);
         }
 
         private void InitPlot() {
diff --git a/Elwig/Windows/ContextWindow.cs b/Elwig/Windows/ContextWindow.cs
index a2f7e0c..f58ff4a 100644
--- a/Elwig/Windows/ContextWindow.cs
+++ b/Elwig/Windows/ContextWindow.cs
@@ -2,25 +2,14 @@ using Elwig.Helpers;
 using System;
 using System.Threading.Tasks;
 using System.Windows;
-using System.Windows.Threading;
 
 namespace Elwig.Windows {
     public abstract class ContextWindow : Window {
 
-        public static readonly int RenewSec = 10;
-
-        protected AppDbContext Context { get; private set; }
         protected bool LockContext { get; set; } = false;
-
-        private readonly DispatcherTimer _timer;
         private bool _renewPending = false;
 
         public ContextWindow() : base() {
-            _timer = new DispatcherTimer();
-            _timer.Tick += new EventHandler(OnShouldRenewContext);
-            _timer.Interval = new TimeSpan(0, 0, RenewSec);
-            _timer.Start();
-            Context = new();
             Loaded += OnLoaded;
         }
 
@@ -30,22 +19,18 @@ namespace Elwig.Windows {
             await RenewContext();
         }
 
-        private async void OnShouldRenewContext(object? sender, EventArgs? evt) {
-            if (!Context.HasBackendChanged) return;
-            await HintContextChange();
-        }
-
         protected async void OnLoaded(object? sender, RoutedEventArgs? evt) {
-            await OnRenewContext();
+            using var ctx = new AppDbContext();
+            await OnRenewContext(ctx);
         }
 
         protected async Task RenewContext() {
             if (!_renewPending) return;
-            Context = new();
-            await OnRenewContext();
+            using var ctx = new AppDbContext();
+            await OnRenewContext(ctx);
             _renewPending = false;
         }
 
-        abstract protected Task OnRenewContext();
+        abstract protected Task OnRenewContext(AppDbContext ctx);
     }
 }
diff --git a/Elwig/Windows/DeliveryAdminWindow.xaml.cs b/Elwig/Windows/DeliveryAdminWindow.xaml.cs
index c548f74..f7da0d7 100644
--- a/Elwig/Windows/DeliveryAdminWindow.xaml.cs
+++ b/Elwig/Windows/DeliveryAdminWindow.xaml.cs
@@ -795,9 +795,8 @@ namespace Elwig.Windows {
             StatusVarieties.ToolTip = StatusVarieties.Text;
         }
 
-        protected override async Task OnRenewContext() {
-            await base.OnRenewContext();
-            using var ctx = new AppDbContext();
+        protected override async Task OnRenewContext(AppDbContext ctx) {
+            await base.OnRenewContext(ctx);
 
             if (Member != null) {
                 if (await GetMemberAsync(Member.MgNr) is not Member m) {
diff --git a/Elwig/Windows/MailWindow.xaml.cs b/Elwig/Windows/MailWindow.xaml.cs
index 370d9f1..ee20297 100644
--- a/Elwig/Windows/MailWindow.xaml.cs
+++ b/Elwig/Windows/MailWindow.xaml.cs
@@ -127,8 +127,7 @@ namespace Elwig.Windows {
             EmailBodyInput.Text = App.Client.TextEmailBody ?? "Sehr geehrtes Mitglied,\n\nim Anhang finden Sie das aktuelle Rundschreiben.\n\nIhre Winzergenossenschaft\n";
         }
 
-        protected override async Task OnRenewContext() {
-            using var ctx = new AppDbContext();
+        protected override async Task OnRenewContext(AppDbContext ctx) {
             var season = await ctx.Seasons.FindAsync(Year);
             var l = new List<string> {
                 MemberDataSheet.Name
diff --git a/Elwig/Windows/MemberAdminWindow.xaml.cs b/Elwig/Windows/MemberAdminWindow.xaml.cs
index d041591..a3dfc59 100644
--- a/Elwig/Windows/MemberAdminWindow.xaml.cs
+++ b/Elwig/Windows/MemberAdminWindow.xaml.cs
@@ -264,9 +264,8 @@ namespace Elwig.Windows {
             ValidateRequiredInputs();
         }
 
-        protected override async Task OnRenewContext() {
-            await base.OnRenewContext();
-            using var ctx = new AppDbContext();
+        protected override async Task OnRenewContext(AppDbContext ctx) {
+            await base.OnRenewContext(ctx);
             ControlUtils.RenewItemsSource(BranchInput, await ctx.Branches.OrderBy(b => b.Name).ToListAsync());
             ControlUtils.RenewItemsSource(DefaultKgInput, await ctx.WbKgs.Select(k => k.AtKg).OrderBy(k => k.Name).ToListAsync());
             await RefreshMemberList();
diff --git a/Elwig/Windows/OriginHierarchyWindow.xaml.cs b/Elwig/Windows/OriginHierarchyWindow.xaml.cs
index 90f7c59..522722c 100644
--- a/Elwig/Windows/OriginHierarchyWindow.xaml.cs
+++ b/Elwig/Windows/OriginHierarchyWindow.xaml.cs
@@ -20,31 +20,29 @@ namespace Elwig.Windows {
             DeactivateKgButton.IsEnabled = false;
         }
 
-        protected override async Task OnRenewContext() {
-            using (var ctx = new AppDbContext()) {
-                var origins = (await ctx.WineOrigins
-                        .Include("Gems.AtGem.Kgs.WbKg.Gl")
-                        .AsSplitQuery()
-                        .ToListAsync())
-                        .OrderByDescending(o => o.SortKey)
-                        .ThenBy(o => o.HkId);
-                ControlUtils.RenewItemsSource(WineOrigins, origins, WineOrigins_SelectionChanged);
-                if (WineOrigins.SelectedItem == null) {
-                    var hkid = await ctx.WbKgs
-                        .GroupBy(k => k.AtKg.Gem.WbGem!.HkId)
-                        .Where(g => g.Count() > 0)
-                        .OrderByDescending(g => g.Count())
-                        .Select(g => g.Key)
-                        .FirstOrDefaultAsync();
-                    ControlUtils.SelectItemWithPk(WineOrigins, hkid);
-                }
-                var gls = await ctx.WbGls
-                    .OrderBy(g => g.GlNr)
-                    .Include("Kgs.Rds")
+        protected override async Task OnRenewContext(AppDbContext ctx) {
+            var origins = (await ctx.WineOrigins
+                    .Include("Gems.AtGem.Kgs.WbKg.Gl")
                     .AsSplitQuery()
-                    .ToListAsync();
-                ControlUtils.RenewItemsSource(WbGls, gls, WbGls_SelectionChanged, ControlUtils.RenewSourceDefault.First);
+                    .ToListAsync())
+                    .OrderByDescending(o => o.SortKey)
+                    .ThenBy(o => o.HkId);
+            ControlUtils.RenewItemsSource(WineOrigins, origins, WineOrigins_SelectionChanged);
+            if (WineOrigins.SelectedItem == null) {
+                var hkid = await ctx.WbKgs
+                    .GroupBy(k => k.AtKg.Gem.WbGem!.HkId)
+                    .Where(g => g.Count() > 0)
+                    .OrderByDescending(g => g.Count())
+                    .Select(g => g.Key)
+                    .FirstOrDefaultAsync();
+                ControlUtils.SelectItemWithPk(WineOrigins, hkid);
             }
+            var gls = await ctx.WbGls
+                .OrderBy(g => g.GlNr)
+                .Include("Kgs.Rds")
+                .AsSplitQuery()
+                .ToListAsync();
+            ControlUtils.RenewItemsSource(WbGls, gls, WbGls_SelectionChanged, ControlUtils.RenewSourceDefault.First);
             UpdateWbGems();
             UpdateWbKgs();
             UpdateWbGlKgs();
diff --git a/Elwig/Windows/PaymentVariantsWindow.xaml.cs b/Elwig/Windows/PaymentVariantsWindow.xaml.cs
index d10c177..47cc0b9 100644
--- a/Elwig/Windows/PaymentVariantsWindow.xaml.cs
+++ b/Elwig/Windows/PaymentVariantsWindow.xaml.cs
@@ -36,8 +36,7 @@ namespace Elwig.Windows {
             }
         }
 
-        protected override async Task OnRenewContext() {
-            using var ctx = new AppDbContext();
+        protected override async Task OnRenewContext(AppDbContext ctx) {
             ControlUtils.RenewItemsSource(PaymentVariantList, await ctx.PaymentVariants
                 .Where(v => v.Year == Year)
                 .OrderBy(v => v.AvNr)
diff --git a/Elwig/Windows/SeasonFinishWindow.xaml.cs b/Elwig/Windows/SeasonFinishWindow.xaml.cs
index 607986b..80ea5d4 100644
--- a/Elwig/Windows/SeasonFinishWindow.xaml.cs
+++ b/Elwig/Windows/SeasonFinishWindow.xaml.cs
@@ -19,7 +19,7 @@ namespace Elwig.Windows {
             SeasonInput.Value = Utils.CurrentLastSeason;
         }
 
-        protected override Task OnRenewContext() {
+        protected override Task OnRenewContext(AppDbContext ctx) {
             SeasonInput_ValueChanged(null, null);
             return Task.CompletedTask;
         }