From 6ccbec890ad11d4e3644fb0cd8ed5456e00350f9 Mon Sep 17 00:00:00 2001 From: MaddoScientisto Date: Sat, 14 Feb 2026 19:36:58 +0100 Subject: [PATCH] Improve speed stats, UI usability, and settings persistence Enhanced speed counter with smoothing and elapsed time display. Added folder open buttons to UI and repositioned speed label. Added UpdateSubdirectories to settings for persistence. --- imagecatalog/DataModel.cs | 85 +++++++++++++++++++++++++++--- imagecatalog/MainForm.Designer.cs | 42 +++++++-------- imagecatalog/Models/SettingsDto.cs | 4 ++ 3 files changed, 103 insertions(+), 28 deletions(-) diff --git a/imagecatalog/DataModel.cs b/imagecatalog/DataModel.cs index 0c7d826..080ef24 100644 --- a/imagecatalog/DataModel.cs +++ b/imagecatalog/DataModel.cs @@ -849,6 +849,11 @@ namespace ImageCatalog_2 // Atomic counter for processed images — avoids expensive ConcurrentBag.Count enumerations private int _processedAtomic = 0; private System.Threading.Timer? _speedTimer; + // Stopwatch used to compute run-wide averages + private Stopwatch? _speedWatch; + // Recent diffs queue to smooth short-term fluctuations + private readonly Queue _recentDiffs = new(); + private int _recentWindowSize = 5; // average over last 5 samples (~5s) private void Test(object parameter) { @@ -907,6 +912,8 @@ namespace ImageCatalog_2 _processedAtomic = 0; // Start speed timer (sample every second using lightweight atomic reads) + _speedWatch = Stopwatch.StartNew(); + _recentDiffs.Clear(); _speedTimer = new System.Threading.Timer(UpdateSpeedCounter, null, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1)); var time = await _imageCreationService.CreaCatalogoParallel( @@ -915,9 +922,28 @@ namespace ImageCatalog_2 OnImageProcessed, token); - SpeedCounter = time; + // Compute final averages and show only averages (do not show raw seconds) + var finalProcessed = System.Threading.Volatile.Read(ref _processedAtomic); + double overallAvg = 0.0; + double overallPerMin = 0.0; + if (_speedWatch is not null && _speedWatch.Elapsed.TotalSeconds > 0.0) + { + overallAvg = finalProcessed / _speedWatch.Elapsed.TotalSeconds; + overallPerMin = overallAvg * 60.0; + } + + // Compute elapsed time as h m s and show final averages (no raw seconds parentheses) + var finalElapsed = _speedWatch?.Elapsed ?? TimeSpan.Zero; + int fh = (int)finalElapsed.TotalHours; + int fm = finalElapsed.Minutes; + int fs = finalElapsed.Seconds; + + SpeedCounter = $"{fh}h {fm}m {fs}s{Environment.NewLine}media: {overallAvg:0.00} f/s{Environment.NewLine}media: {overallPerMin:0.00} f/m"; + _speedTimer?.Dispose(); _speedTimer = null; + _speedWatch?.Stop(); + _speedWatch = null; } catch (OperationCanceledException) { @@ -942,12 +968,57 @@ namespace ImageCatalog_2 private void UpdateSpeedCounter(object? state) { - _previousAmount = _currentAmount; - // Read the atomic counter without enumerating the ConcurrentBag - _currentAmount = System.Threading.Volatile.Read(ref _processedAtomic); - int diff = _currentAmount - _previousAmount; - // Report files per second (timer runs every 1s) - SpeedCounter = $"{diff} f/s"; + try + { + _previousAmount = _currentAmount; + // Read the atomic counter without enumerating the ConcurrentBag + _currentAmount = System.Threading.Volatile.Read(ref _processedAtomic); + int diff = _currentAmount - _previousAmount; + + // Protect against negative or spurious diffs + if (diff < 0) diff = 0; + + // Maintain a small sliding window of recent diffs to smooth the display + lock (_recentDiffs) + { + _recentDiffs.Enqueue(diff); + if (_recentDiffs.Count > _recentWindowSize) + _recentDiffs.Dequeue(); + } + + double avgRecent; + lock (_recentDiffs) + { + avgRecent = _recentDiffs.Count == 0 ? 0.0 : _recentDiffs.Average(); + } + + // Compute overall average (since start) if we have a stopwatch + double overall = 0.0; + if (_speedWatch is not null && _speedWatch.Elapsed.TotalSeconds >= 1) + { + var elapsedSeconds = _speedWatch.Elapsed.TotalSeconds; + var total = System.Threading.Volatile.Read(ref _processedAtomic); + overall = elapsedSeconds > 0 ? total / elapsedSeconds : 0.0; + } + + // Recent per-minute estimate + var recentPerMin = avgRecent * 60.0; + var overallPerMin = overall * 60.0; + + // Build a two-line display plus elapsed time: first line shows f/s with overall media and elapsed time, + // second line shows recent photos per minute (media) + var elapsed = _speedWatch?.Elapsed ?? TimeSpan.Zero; + int hours = (int)elapsed.TotalHours; + int minutes = elapsed.Minutes; + int seconds = elapsed.Seconds; + var elapsedStr = $"{hours}h {minutes}m {seconds}s"; + + SpeedCounter = $"{avgRecent:0.00} f/s (media: {overall:0.00} f/s) - {elapsedStr}{Environment.NewLine}media: {recentPerMin:0.00} f/m"; + } + catch + { + // Swallow unlikely errors from timing/queue operations but keep UI responsive + } } private void OnImageProcessed(object? sender, Tuple args) diff --git a/imagecatalog/MainForm.Designer.cs b/imagecatalog/MainForm.Designer.cs index 6b1c48c..0ee4e01 100644 --- a/imagecatalog/MainForm.Designer.cs +++ b/imagecatalog/MainForm.Designer.cs @@ -56,6 +56,8 @@ namespace ImageCatalog Label8 = new Label(); Label7 = new Label(); GroupBox3 = new GroupBox(); + btnOpenDestFolder = new Button(); + btnOpenSourceFolder = new Button(); chkAggiornaSottodirectory = new CheckBox(); _Button3 = new Button(); _Button2 = new Button(); @@ -181,8 +183,6 @@ namespace ImageCatalog _btnCreaCatalogoAsync = new Button(); timer1 = new System.Windows.Forms.Timer(components); dataModelBindingSource1 = new BindingSource(components); - btnOpenSourceFolder = new Button(); - btnOpenDestFolder = new Button(); ((System.ComponentModel.ISupportInitialize)bindingSource1).BeginInit(); ((System.ComponentModel.ISupportInitialize)dataModelBindingSource).BeginInit(); TabControl1.SuspendLayout(); @@ -245,7 +245,7 @@ namespace ImageCatalog // Label43.AutoSize = true; Label43.DataBindings.Add(new Binding("Text", bindingSource1, "SpeedCounter", true)); - Label43.Location = new Point(1073, 789); + Label43.Location = new Point(1074, 725); Label43.Margin = new Padding(6, 0, 6, 0); Label43.Name = "Label43"; Label43.Size = new Size(46, 30); @@ -418,6 +418,24 @@ namespace ImageCatalog GroupBox3.TabStop = false; GroupBox3.Text = "Directory"; // + // btnOpenDestFolder + // + btnOpenDestFolder.Location = new Point(939, 97); + btnOpenDestFolder.Margin = new Padding(6, 8, 6, 8); + btnOpenDestFolder.Name = "btnOpenDestFolder"; + btnOpenDestFolder.Size = new Size(48, 35); + btnOpenDestFolder.TabIndex = 27; + btnOpenDestFolder.Text = "Apri"; + // + // btnOpenSourceFolder + // + btnOpenSourceFolder.Location = new Point(939, 38); + btnOpenSourceFolder.Margin = new Padding(6, 8, 6, 8); + btnOpenSourceFolder.Name = "btnOpenSourceFolder"; + btnOpenSourceFolder.Size = new Size(48, 35); + btnOpenSourceFolder.TabIndex = 26; + btnOpenSourceFolder.Text = "Apri"; + // // chkAggiornaSottodirectory // chkAggiornaSottodirectory.DataBindings.Add(new Binding("Checked", bindingSource1, "UpdateSubdirectories", true, DataSourceUpdateMode.OnPropertyChanged)); @@ -1844,24 +1862,6 @@ namespace ImageCatalog // dataModelBindingSource1.DataSource = typeof(ImageCatalog_2.DataModel); // - // btnOpenSourceFolder - // - btnOpenSourceFolder.Location = new Point(939, 38); - btnOpenSourceFolder.Margin = new Padding(6, 8, 6, 8); - btnOpenSourceFolder.Name = "btnOpenSourceFolder"; - btnOpenSourceFolder.Size = new Size(48, 35); - btnOpenSourceFolder.TabIndex = 26; - btnOpenSourceFolder.Text = "Apri"; - // - // btnOpenDestFolder - // - btnOpenDestFolder.Location = new Point(939, 97); - btnOpenDestFolder.Margin = new Padding(6, 8, 6, 8); - btnOpenDestFolder.Name = "btnOpenDestFolder"; - btnOpenDestFolder.Size = new Size(48, 35); - btnOpenDestFolder.TabIndex = 27; - btnOpenDestFolder.Text = "Apri"; - // // MainForm // AutoScaleDimensions = new SizeF(12F, 30F); diff --git a/imagecatalog/Models/SettingsDto.cs b/imagecatalog/Models/SettingsDto.cs index d5a01b2..70896e0 100644 --- a/imagecatalog/Models/SettingsDto.cs +++ b/imagecatalog/Models/SettingsDto.cs @@ -177,6 +177,10 @@ namespace ImageCatalog_2.Models [XmlElement("GeneraleRotazioneAutomatica")] public bool AutomaticRotation { get; set; } + [JsonPropertyName("UpdateSubdirectories")] + [XmlElement("DirSottoDirectory")] + public bool UpdateSubdirectories { get; set; } + [JsonPropertyName("AddRaceTime")] [XmlElement("TempoGara")] public bool AddRaceTime { get; set; }