Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions .github/workflows/build-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ jobs:

steps:
- name: Check out repository
uses: actions/checkout@v4
uses: actions/checkout@v7

- name: Set up .NET
uses: actions/setup-dotnet@v4
uses: actions/setup-dotnet@v6
with:
dotnet-version: 9.0.x

Expand All @@ -62,7 +62,8 @@ jobs:
-p:PublishSingleFile=true `
-p:IncludeNativeLibrariesForSelfExtract=true `
-p:DebugType=None `
-p:DebugSymbols=false
-p:DebugSymbols=false `
-p:TreatWarningsAsErrors=true

if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
Expand Down Expand Up @@ -105,7 +106,7 @@ jobs:
} | Set-Content (Join-Path $artifactDir "SHA256SUMS.txt") -Encoding ascii

- name: Upload build artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: CompactGUI-win-x64
path: ${{ runner.temp }}/compactgui-artifacts/*
Expand All @@ -122,12 +123,12 @@ jobs:

steps:
- name: Check out repository
uses: actions/checkout@v4
uses: actions/checkout@v7
with:
fetch-depth: 0

- name: Download release assets
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: CompactGUI-win-x64
path: release-assets
Expand Down Expand Up @@ -188,4 +189,4 @@ jobs:
release_args+=(--prerelease)
fi

gh "${release_args[@]}"
gh "${release_args[@]}"
12 changes: 6 additions & 6 deletions CompactGUI.Core/Analyser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,12 @@ private static bool GetContainsCompressedFiles(List<AnalysedFileDetails> fileCom

public List<ExtensionResult> GetPoorlyCompressedExtensions()
{
List<AnalysedFileDetails> analysedFileDetails = _analysedFileDetails ?? [];

// Only use PLINQ if the list is large enough to benefit from parallel processing
IEnumerable<AnalysedFileDetails> query = _analysedFileDetails?.Count <= 10000
? _analysedFileDetails
: _analysedFileDetails.AsParallel();
IEnumerable<AnalysedFileDetails> query = analysedFileDetails.Count <= 10000
? analysedFileDetails
: analysedFileDetails.AsParallel();

return query
.Where(fl => fl.UncompressedSize > 0)
Expand All @@ -150,6 +152,4 @@ public void Dispose()
_folderMonitor.Dispose();
_analysedFileDetails?.Clear();
}
}


}
15 changes: 7 additions & 8 deletions CompactGUI.Core/Compactor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ private void InitializeCompressionInfoPointer()

}

public async Task<bool> RunAsync(List<string> filesList, IProgress<CompressionProgress> progressMonitor = null, int maxParallelism = 1)

public async Task<bool> RunAsync(List<string> filesList, IProgress<CompressionProgress>? progressMonitor = null, int maxParallelism = 1)
{
if(cancellationTokenSource.IsCancellationRequested) { return false; }

Expand Down Expand Up @@ -92,7 +93,7 @@ await Parallel.ForEachAsync(workingFiles, parallelOptions,
return true;
}

private async Task PauseAndProcessFile(FileDetails file, long totalFilesSize, CancellationToken token, IProgress<CompressionProgress> progressMonitor)
private async Task PauseAndProcessFile(FileDetails file, long totalFilesSize, CancellationToken token, IProgress<CompressionProgress>? progressMonitor)
{
CompactorLog.ProcessingFile(_logger, file.FileName, file.UncompressedSize);

Expand Down Expand Up @@ -126,23 +127,22 @@ public async Task<IEnumerable<FileDetails>> BuildWorkingFilesList()
uint clusterSize = SharedMethods.GetClusterSize(workingDirectory);


var analysedFiles = await _analyser.GetAnalysedFilesAsync(cancellationTokenSource.Token);
var analysedFiles = await _analyser.GetAnalysedFilesAsync(cancellationTokenSource.Token) ?? [];

var filesList = analysedFiles?
return analysedFiles
.Where(fl =>
fl.CompressionMode != wofCompressionAlgorithm
&& fl.UncompressedSize > clusterSize
&& ((fl.FileInfo != null && !excludedFileExtensions.Contains(fl.FileInfo.Extension)) || excludedFileExtensions.Contains(fl.FileName))
)
.Select(fl => new FileDetails(fl.FileName, fl.UncompressedSize))
.ToList();

return filesList;
}





public void Pause()
{
CompactorLog.CompressionPaused(_logger);
Expand Down Expand Up @@ -178,5 +178,4 @@ public void Dispose()

public readonly record struct FileDetails(string FileName, long UncompressedSize);


}
}
2 changes: 1 addition & 1 deletion CompactGUI.Core/ICompressor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
public interface ICompressor : IDisposable
{
Task<bool> RunAsync(List<String> filesList,
IProgress<CompressionProgress> progressMonitor = null,
IProgress<CompressionProgress>? progressMonitor = null,
int maxParallelism = 1);

void Pause();
Expand Down
12 changes: 6 additions & 6 deletions CompactGUI.Core/Settings/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,14 @@ public partial class Settings : ObservableRecipient

private void UpdateNextScheduledBackgroundRun()
{
DateTime nextRun = scheduledBackgroundLastRan.Date
.AddDays(scheduledBackgroundInterval)
.AddHours(scheduledBackgroundHour)
.AddMinutes(scheduledBackgroundMinute);
DateTime nextRun = ScheduledBackgroundLastRan.Date
.AddDays(ScheduledBackgroundInterval)
.AddHours(ScheduledBackgroundHour)
.AddMinutes(ScheduledBackgroundMinute);
DateTime now = DateTime.Now;
while (nextRun <= now)
{
nextRun = nextRun.AddDays(scheduledBackgroundInterval == 0 ? 1 : scheduledBackgroundInterval);
nextRun = nextRun.AddDays(ScheduledBackgroundInterval == 0 ? 1 : ScheduledBackgroundInterval);
}

NextScheduledBackgroundRun = nextRun;
Expand All @@ -89,4 +89,4 @@ public enum BackgroundMode
IdleOnly,
Scheduled,
ScheduledAndIdle
}
}
16 changes: 9 additions & 7 deletions CompactGUI.Core/SharedMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -183,22 +183,24 @@ public static bool HasDirectoryWritePermission(string folderName)
DirectoryInfo dirInfo = new DirectoryInfo(folderName);
DirectorySecurity dirSecurity = dirInfo.GetAccessControl();

var user = WindowsIdentity.GetCurrent();
var userSID = user.User;
var userGroupSIDs = user.Groups;
using WindowsIdentity user = WindowsIdentity.GetCurrent();
SecurityIdentifier? userSID = user.User;
if (userSID is null) return false;

IdentityReferenceCollection? userGroupSIDs = user.Groups;
var rules = dirSecurity.GetAccessRules(true, true, typeof(SecurityIdentifier));

bool writeAllowed = false;
bool writeDenied = false;

foreach (FileSystemAccessRule rule in rules)
{
var fileSystemRights = rule.FileSystemRights;

if (!rule.FileSystemRights.HasFlag(FileSystemRights.Write)) continue;
if (rule.IdentityReference is not SecurityIdentifier ruleSID) continue;
if (!ruleSID.Equals(userSID) && !userGroupSIDs.Contains(ruleSID)) continue;

bool belongsToUser = ruleSID.Equals(userSID);
bool belongsToGroup = userGroupSIDs?.Contains(ruleSID) == true;
if (!belongsToUser && !belongsToGroup) continue;

if (rule.AccessControlType == AccessControlType.Deny)
{
Expand Down Expand Up @@ -243,4 +245,4 @@ public static bool IsDirectStorageGameFolder(string folderPath)
}


}
}
117 changes: 99 additions & 18 deletions CompactGUI.Watcher/WatchedFolder.vb
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ Public Class WatchedFolder
<AttachAttribute(GetType(JsonIgnoreAttribute))>
<ObservableProperty> Private _IsEditing As Boolean = False

<AttachAttribute(GetType(JsonIgnoreAttribute))>
<ObservableProperty> Private _IsAvailable As Boolean = True

' --- Monitoring State ---
<AttachAttribute(GetType(JsonIgnoreAttribute))>
<ObservableProperty> Private _HasTargetChanged As Boolean = False
Expand All @@ -39,6 +42,7 @@ Public Class WatchedFolder
Private WithEvents FSWatcher As FileSystemWatcher

Private debounceTimer As Timer
Private isMonitoringPaused As Boolean

Private disposedValue As Boolean

Expand All @@ -52,40 +56,96 @@ Public Class WatchedFolder
Public Sub New()
End Sub

Public Sub InitializeMonitoring()
If FSWatcher Is Nothing Then
FSWatcher = New FileSystemWatcher(Folder) With {
.NotifyFilter = NotifyFilters.Size Or NotifyFilters.CreationTime Or NotifyFilters.LastWrite Or NotifyFilters.FileName,
.IncludeSubdirectories = True,
.Filter = "",
.EnableRaisingEvents = True
}
debounceTimer = New Timer(AddressOf DebounceTimerCallback, Nothing, Timeout.Infinite, Timeout.Infinite)
Public Function InitializeMonitoring() As Boolean
If disposedValue Then Return False

If String.IsNullOrWhiteSpace(Folder) OrElse Not Directory.Exists(Folder) Then
MarkUnavailable()
Return False
End If
End Sub

Try
If FSWatcher Is Nothing Then
FSWatcher = New FileSystemWatcher(Folder) With {
.NotifyFilter = NotifyFilters.Size Or NotifyFilters.CreationTime Or NotifyFilters.LastWrite Or NotifyFilters.FileName,
.IncludeSubdirectories = True,
.Filter = "",
.EnableRaisingEvents = Not isMonitoringPaused
}
debounceTimer = New Timer(AddressOf DebounceTimerCallback, Nothing, Timeout.Infinite, Timeout.Infinite)
Else
FSWatcher.EnableRaisingEvents = Not isMonitoringPaused
End If

IsAvailable = True
Return True
Catch ex As Exception When TypeOf ex Is IOException OrElse TypeOf ex Is UnauthorizedAccessException OrElse TypeOf ex Is ArgumentException
Debug.WriteLine($"Unable to monitor folder {Folder}: {ex.Message}")
MarkUnavailable()
Return False
End Try
End Function

Public Function RefreshAvailability() As Boolean
Dim wasAvailable = IsAvailable

If Not InitializeMonitoring() Then Return False

If Not wasAvailable Then
HasTargetChanged = True
LastChangedDate = DateTime.Now
End If

Return True
End Function

Public Sub PauseMonitoring()
If FSWatcher IsNot Nothing Then
FSWatcher.EnableRaisingEvents = False
isMonitoringPaused = True

If Not IsAvailable OrElse FSWatcher Is Nothing Then Return

If Not Directory.Exists(Folder) Then
MarkUnavailable()
Return
End If

Try
FSWatcher.EnableRaisingEvents = False
Catch ex As Exception When TypeOf ex Is IOException OrElse TypeOf ex Is ObjectDisposedException
MarkUnavailable()
End Try
End Sub

Public Sub ResumeMonitoring()
If FSWatcher IsNot Nothing Then
isMonitoringPaused = False
If Not RefreshAvailability() Then Return

Try
FSWatcher.EnableRaisingEvents = True
End If
Catch ex As Exception When TypeOf ex Is IOException OrElse TypeOf ex Is ObjectDisposedException
MarkUnavailable()
End Try
End Sub

' --- Monitoring Events ---
Private Sub WatcherErrorEvent(sender As Object, e As ErrorEventArgs) Handles FSWatcher.Error
Debug.WriteLine(e.GetException.Message)

If Not Directory.Exists(Folder) Then
MarkUnavailable()
End If
End Sub

Private Sub WatcherModifiedEvent(sender As Object, e As FileSystemEventArgs) Handles FSWatcher.Created, FSWatcher.Changed, FSWatcher.Renamed, FSWatcher.Deleted
debounceTimer.Change(1000, Timeout.Infinite)
debounceTimer?.Change(1000, Timeout.Infinite)
End Sub

Private Sub DebounceTimerCallback(state As Object)
If Not Directory.Exists(Folder) Then
MarkUnavailable()
Return
End If

HasTargetChanged = True
LastChangedDate = DateTime.Now
OnPropertyChanged(NameOf(HasTargetChanged))
Expand All @@ -94,6 +154,28 @@ Public Class WatchedFolder
Debug.WriteLine("Folder " & Folder & " has changed!")
End Sub

Private Sub MarkUnavailable()
IsAvailable = False
IsWorking = False
StopMonitoring()
End Sub

Private Sub StopMonitoring()
Try
FSWatcher?.Dispose()
Catch ex As ObjectDisposedException
Finally
FSWatcher = Nothing
End Try

Try
debounceTimer?.Dispose()
Catch ex As ObjectDisposedException
Finally
debounceTimer = Nothing
End Try
End Sub

' --- Calculated Properties ---
Public ReadOnly Property DecayPercentage As Decimal
Get
Expand All @@ -119,8 +201,7 @@ Public Class WatchedFolder
Protected Overridable Sub Dispose(disposing As Boolean)
If Not disposedValue Then
If disposing Then
FSWatcher?.Dispose()
debounceTimer?.Dispose()
StopMonitoring()
End If
disposedValue = True
End If
Expand All @@ -130,4 +211,4 @@ Public Class WatchedFolder
Dispose(disposing:=True)
GC.SuppressFinalize(Me)
End Sub
End Class
End Class
Loading