Skip to content

Commit 31c3675

Browse files
authored
Merge pull request #549 from IridiumIO/Version-4.0-Beta6
Version 4.0 beta6
2 parents c9e214c + 5bfa885 commit 31c3675

File tree

68 files changed

+3038
-1724
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+3038
-1724
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,4 +365,5 @@ FodyWeavers.xsd
365365
/CompactGUI/My Project/launchSettings.json
366366

367367
# IntelliJ
368-
.idea/
368+
.idea/
369+
/CompactGUI.WatcherCS

CompactGUI.Core/Analyser.cs

Lines changed: 62 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,29 +6,66 @@
66

77
namespace CompactGUI.Core;
88

9-
public class Analyser
9+
public sealed class Analyser : IDisposable
1010
{
1111

1212
public string FolderName { get; set; }
13-
public long UncompressedBytes { get; set; }
14-
public long CompressedBytes { get; set; }
15-
public bool ContainsCompressedFiles { get; set; }
16-
public List<AnalysedFileDetails> FileCompressionDetailsList { get; set; } = new List<AnalysedFileDetails>();
17-
1813
private ILogger<Analyser> _logger;
14+
private readonly FolderChangeMonitor _folderMonitor;
15+
public bool HasFolderChanged => _folderMonitor.HasChanged;
16+
public DateTime LastFolderChanged => _folderMonitor.LastChanged;
17+
1918

2019
public Analyser(string folder, ILogger<Analyser> logger)
2120
{
2221
FolderName = folder;
23-
UncompressedBytes = 0;
24-
CompressedBytes = 0;
25-
ContainsCompressedFiles = false;
2622
_logger = logger;
23+
24+
_folderMonitor = new FolderChangeMonitor(folder);
25+
_folderMonitor.Changed += (s, e) =>
26+
_logger.LogInformation("Folder change detected by FolderChangeMonitor for {FolderName}", FolderName);
27+
}
28+
29+
30+
public long CompressedBytes;
31+
public long UncompressedBytes;
32+
public bool ContainsCompressedFiles;
33+
34+
private static long GetTotalCompressedBytes(List<AnalysedFileDetails> fileCompressionDetailsList)
35+
{
36+
return fileCompressionDetailsList.Sum(f => f.CompressedSize);
37+
}
38+
private static long GetTotalUncompressedBytes(List<AnalysedFileDetails> fileCompressionDetailsList)
39+
{
40+
return fileCompressionDetailsList.Sum(f => f.UncompressedSize);
41+
}
42+
private static bool GetContainsCompressedFiles(List<AnalysedFileDetails> fileCompressionDetailsList)
43+
{
44+
return fileCompressionDetailsList.Any(f => f.CompressionMode != WOFCompressionAlgorithm.NO_COMPRESSION);
45+
}
46+
47+
48+
private List<AnalysedFileDetails>? _analysedFileDetails;
49+
50+
public async ValueTask<List<AnalysedFileDetails>?> GetAnalysedFilesAsync(CancellationToken token)
51+
{
52+
if (_analysedFileDetails != null && !HasFolderChanged)
53+
{
54+
_logger.LogInformation("Returning cached analysed files for folder {FolderName}", FolderName);
55+
return _analysedFileDetails;
56+
}
57+
58+
_logger.LogInformation("Analysing folder {FolderName} for the first time or after a change", FolderName);
59+
_analysedFileDetails = await AnalyseFolder(token).ConfigureAwait(false);
60+
return _analysedFileDetails;
2761
}
2862

2963

30-
public async Task<Boolean?> AnalyseFolder(CancellationToken cancellationToken)
64+
private async Task<List<AnalysedFileDetails>?> AnalyseFolder(CancellationToken cancellationToken)
3165
{
66+
67+
List<AnalysedFileDetails>? AnalysedFileDetails;
68+
3269
AnalyserLog.StartingAnalysis(_logger, FolderName);
3370
Stopwatch sw = Stopwatch.StartNew();
3471
try
@@ -41,11 +78,7 @@ public Analyser(string folder, ILogger<Analyser> logger)
4178
.OfType<AnalysedFileDetails>()
4279
.ToList();
4380

44-
CompressedBytes = fileDetails.Sum(f => f.CompressedSize);
45-
UncompressedBytes = fileDetails.Sum(f => f.UncompressedSize);
46-
ContainsCompressedFiles = fileDetails.Any(f => f.CompressionMode != WOFCompressionAlgorithm.NO_COMPRESSION);
47-
48-
FileCompressionDetailsList = fileDetails;
81+
AnalysedFileDetails = fileDetails;
4982
}
5083
catch (Exception ex)
5184
{
@@ -54,9 +87,14 @@ public Analyser(string folder, ILogger<Analyser> logger)
5487
}
5588
finally { sw.Stop(); }
5689

90+
_folderMonitor.Reset();
91+
92+
CompressedBytes = GetTotalCompressedBytes(AnalysedFileDetails);
93+
UncompressedBytes = GetTotalUncompressedBytes(AnalysedFileDetails);
94+
ContainsCompressedFiles = GetContainsCompressedFiles(AnalysedFileDetails);
5795
AnalyserLog.AnalysisCompleted(_logger, FolderName, Math.Round(sw.Elapsed.TotalSeconds, 3), CompressedBytes, UncompressedBytes, ContainsCompressedFiles);
5896

59-
return ContainsCompressedFiles;
97+
return AnalysedFileDetails;
6098

6199

62100
}
@@ -88,9 +126,9 @@ public Analyser(string folder, ILogger<Analyser> logger)
88126
public List<ExtensionResult> GetPoorlyCompressedExtensions()
89127
{
90128
// Only use PLINQ if the list is large enough to benefit from parallel processing
91-
IEnumerable<AnalysedFileDetails> query = FileCompressionDetailsList.Count <= 10000
92-
? FileCompressionDetailsList
93-
: FileCompressionDetailsList.AsParallel();
129+
IEnumerable<AnalysedFileDetails> query = _analysedFileDetails?.Count <= 10000
130+
? _analysedFileDetails
131+
: _analysedFileDetails.AsParallel();
94132

95133
return query
96134
.Where(fl => fl.UncompressedSize > 0)
@@ -107,6 +145,11 @@ public List<ExtensionResult> GetPoorlyCompressedExtensions()
107145

108146
}
109147

148+
public void Dispose()
149+
{
150+
_folderMonitor.Dispose();
151+
_analysedFileDetails?.Clear();
152+
}
110153
}
111154

112155

CompactGUI.Core/CompactGUI.Core.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@
1212

1313

1414
<ItemGroup>
15+
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
1516
<PackageReference Include="K4os.Compression.LZ4.Streams" Version="1.3.8" />
1617
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.183">
1718
<PrivateAssets>all</PrivateAssets>
1819
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
1920
</PackageReference>
20-
<PackageReference Include="ZLinq" Version="1.4.10" />
2121
</ItemGroup>
2222

2323

CompactGUI.Core/Compactor.cs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
namespace CompactGUI.Core;
1313

14-
public class Compactor : ICompressor, IDisposable
14+
public sealed class Compactor : ICompressor, IDisposable
1515
{
1616

1717
private readonly string workingDirectory;
@@ -28,12 +28,15 @@ public class Compactor : ICompressor, IDisposable
2828

2929
private ILogger<Compactor> _logger;
3030

31-
public Compactor(string folderPath, WOFCompressionAlgorithm compressionLevel, string[] excludedFileTypes, ILogger<Compactor>? logger = null)
31+
private Analyser _analyser;
32+
33+
public Compactor(string folderPath, WOFCompressionAlgorithm compressionLevel, string[] excludedFileTypes, Analyser analyser, ILogger<Compactor>? logger = null)
3234
{
3335
workingDirectory = folderPath;
3436
excludedFileExtensions = new HashSet<string>(excludedFileTypes);
3537
wofCompressionAlgorithm = compressionLevel;
3638
_logger = logger ?? NullLogger<Compactor>.Instance;
39+
_analyser = analyser;
3740
InitializeCompressionInfoPointer();
3841
}
3942

@@ -122,10 +125,10 @@ public async Task<IEnumerable<FileDetails>> BuildWorkingFilesList()
122125
{
123126
uint clusterSize = SharedMethods.GetClusterSize(workingDirectory);
124127

125-
var analyser = new Analyser(workingDirectory, NullLogger<Analyser>.Instance);
126-
var ret = await analyser.AnalyseFolder(cancellationTokenSource.Token);
128+
129+
var analysedFiles = await _analyser.GetAnalysedFilesAsync(cancellationTokenSource.Token);
127130

128-
var filesList = analyser.FileCompressionDetailsList
131+
var filesList = analysedFiles?
129132
.Where(fl =>
130133
fl.CompressionMode != wofCompressionAlgorithm
131134
&& fl.UncompressedSize > clusterSize
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
using System;
2+
using System.IO;
3+
using System.Threading;
4+
5+
namespace CompactGUI.Core;
6+
7+
/// <summary>
8+
/// Monitors a folder (and subfolders) for changes, with debouncing and proper disposal.
9+
/// </summary>
10+
public sealed class FolderChangeMonitor : IDisposable
11+
{
12+
private readonly FileSystemWatcher _watcher;
13+
private readonly Timer _debounceTimer;
14+
private readonly int _debounceMilliseconds;
15+
private bool _hasChanged;
16+
private DateTime _lastChanged;
17+
private bool _disposed;
18+
19+
/// <summary>
20+
/// Raised when the folder has changed (debounced).
21+
/// </summary>
22+
public event EventHandler? Changed;
23+
24+
/// <summary>
25+
/// True if a change has been detected since the last reset.
26+
/// </summary>
27+
public bool HasChanged => _hasChanged;
28+
29+
/// <summary>
30+
/// The last time a change was detected.
31+
/// </summary>
32+
public DateTime LastChanged => _lastChanged;
33+
34+
/// <summary>
35+
/// Create a new FolderChangeMonitor.
36+
/// </summary>
37+
/// <param name="folderPath">The folder to monitor.</param>
38+
/// <param name="debounceMilliseconds">Debounce interval in ms (default: 1000).</param>
39+
public FolderChangeMonitor(string folderPath, int debounceMilliseconds = 1000)
40+
{
41+
_debounceMilliseconds = debounceMilliseconds;
42+
_watcher = new FileSystemWatcher(folderPath)
43+
{
44+
NotifyFilter = NotifyFilters.Size | NotifyFilters.CreationTime | NotifyFilters.LastWrite | NotifyFilters.FileName,
45+
IncludeSubdirectories = true,
46+
EnableRaisingEvents = true
47+
};
48+
_watcher.Changed += OnChanged;
49+
_watcher.Created += OnChanged;
50+
_watcher.Deleted += OnChanged;
51+
_watcher.Renamed += OnChanged;
52+
_watcher.Error += OnError;
53+
54+
_debounceTimer = new Timer(DebounceCallback, null, Timeout.Infinite, Timeout.Infinite);
55+
}
56+
57+
private void OnChanged(object sender, FileSystemEventArgs e)
58+
{
59+
_debounceTimer.Change(_debounceMilliseconds, Timeout.Infinite);
60+
}
61+
62+
private void DebounceCallback(object? state)
63+
{
64+
_hasChanged = true;
65+
_lastChanged = DateTime.Now;
66+
Changed?.Invoke(this, EventArgs.Empty);
67+
}
68+
69+
private void OnError(object sender, ErrorEventArgs e)
70+
{
71+
// Optionally log or handle errors here
72+
}
73+
74+
/// <summary>
75+
/// Reset the change flag after handling.
76+
/// </summary>
77+
public void Reset()
78+
{
79+
_hasChanged = false;
80+
}
81+
82+
public void Dispose()
83+
{
84+
if (_disposed) return;
85+
_watcher.Dispose();
86+
_debounceTimer.Dispose();
87+
_disposed = true;
88+
GC.SuppressFinalize(this);
89+
}
90+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
7+
namespace CompactGUI.Core.Settings
8+
{
9+
public interface ISettingsService
10+
{
11+
12+
DirectoryInfo DataFolder { get; }
13+
FileInfo SettingsJSONFile { get; }
14+
15+
Settings AppSettings { get; set; }
16+
17+
decimal SettingsVersion { get; }
18+
19+
void LoadSettings();
20+
void SaveSettings();
21+
void ScheduleSettingsSave();
22+
23+
24+
}
25+
26+
27+
}

0 commit comments

Comments
 (0)