From 7de72471bb96f091aba2db5c81440d53a5f72976 Mon Sep 17 00:00:00 2001 From: cake Date: Mon, 10 Nov 2025 06:25:35 +0100 Subject: [PATCH] Refactored --- LightlessSync/FileCache/FileCompactor.cs | 171 ++++++++++++++--------- 1 file changed, 102 insertions(+), 69 deletions(-) diff --git a/LightlessSync/FileCache/FileCompactor.cs b/LightlessSync/FileCache/FileCompactor.cs index e6d67ca..5e0b4a9 100644 --- a/LightlessSync/FileCache/FileCompactor.cs +++ b/LightlessSync/FileCache/FileCompactor.cs @@ -6,6 +6,7 @@ using Microsoft.Win32.SafeHandles; using System.Collections.Concurrent; using System.Diagnostics; using System.Runtime.InteropServices; +using System.Threading; using System.Threading.Channels; using static LightlessSync.Utils.FileSystemHelper; @@ -82,7 +83,7 @@ public sealed class FileCompactor : IDisposable _fragBatch = new BatchFilefragService( useShell: _dalamudUtilService.IsWine, log: _logger, - batchSize: 256, + batchSize: 128, flushMs: 25); _logger.LogInformation("FileCompactor started with {workers} workers", workerCount); @@ -198,13 +199,12 @@ public sealed class FileCompactor : IDisposable var (ok1, out1, err1, code1) = isWindowsProc ? RunProcessShell($"stat -c %b -- {QuoteSingle(linuxPath)}", null, 10000) - : RunProcessDirect("stat", new[] { "-c", "%b", "--", linuxPath }, null, 10000); + : RunProcessDirect("stat", ["-c", "%b", "--", linuxPath], null, 10000); if (ok1 && long.TryParse(out1.Trim(), out long blocks)) - return (false, blocks * 512L); // st_blocks are 512B units + return (false, blocks * 512L); // st_blocks are always 512B units - // Fallback: du -B1 (true on-disk bytes) - var (ok2, out2, err2, code2) = RunProcessShell($"du -B1 -- {QuoteSingle(linuxPath)} | cut -f1", null, 10000); // use shell for the pipe + var (ok2, out2, err2, code2) = RunProcessShell($"du -B1 -- {QuoteSingle(linuxPath)} | cut -f1", workingDir: null, 10000); // use shell for the pipe if (ok2 && long.TryParse(out2.Trim(), out long bytes)) return (false, bytes); @@ -425,6 +425,7 @@ public sealed class FileCompactor : IDisposable /// Decompressing state private bool DecompressWOFFile(string path) { + //Check if its already been compressed if (TryIsWofExternal(path, out bool isExternal, out int algo)) { if (!isExternal) @@ -436,6 +437,7 @@ public sealed class FileCompactor : IDisposable _logger.LogTrace("WOF compression (algo={algo}) detected for {file}", compressString, path); } + //This will attempt to start WOF thread. return WithFileHandleForWOF(path, FileAccess.ReadWrite, h => { if (!DeviceIoControl(h, FSCTL_DELETE_EXTERNAL_BACKING, @@ -524,7 +526,7 @@ public sealed class FileCompactor : IDisposable } catch { - /* ignore and fall through */ + /* ignore and fall through the floor! */ } } @@ -550,7 +552,7 @@ public sealed class FileCompactor : IDisposable { int ret = WofSetFileDataLocation(h, WOF_PROVIDER_FILE, efInfoPtr, length); - // 0x80070158 is the benign "already compressed/unsupported" style return + // 0x80070158 is the being "already compressed/unsupported" style return if (ret != 0 && ret != unchecked((int)0x80070158)) { _logger.LogWarning("Failed to compact {file}: {ret}", path, ret.ToString("X")); @@ -791,38 +793,12 @@ public sealed class FileCompactor : IDisposable using var proc = Process.Start(psi); if (proc is null) return (false, "", "failed to start process", -1); - var outTask = proc.StandardOutput.ReadToEndAsync(_compactionCts.Token); - var errTask = proc.StandardError.ReadToEndAsync(_compactionCts.Token); - var both = Task.WhenAll(outTask, errTask); - - if (_dalamudUtilService.IsWine) + var (success, so2, se2) = CheckProcessResult(proc, timeoutMs, _compactionCts.Token); + if (!success) { - var finished = Task.WhenAny(both, Task.Delay(timeoutMs, _compactionCts.Token)).GetAwaiter().GetResult(); - if (finished != both) - { - try { proc.Kill(entireProcessTree: true); } catch { /* ignore this */ } - try { Task.WaitAll(new[] { outTask, errTask }, 1000, _compactionCts.Token); } catch { /* ignore this */ } - var so = outTask.IsCompleted ? outTask.Result : ""; - var se = errTask.IsCompleted ? errTask.Result : "timeout"; - return (false, so, se, -1); - } - - var stdout = outTask.Result; - var stderr = errTask.Result; - var ok = string.IsNullOrWhiteSpace(stderr); - return (ok, stdout, stderr, ok ? 0 : -1); + return (false, so2, se2, -1); } - if (!proc.WaitForExit(timeoutMs)) - { - try { proc.Kill(entireProcessTree: true); } catch { /* ignore this */ } - try { Task.WaitAll([outTask, errTask], 1000, _compactionCts.Token); } catch { /* ignore this */ } - return (false, outTask.IsCompleted ? outTask.Result : "", "timeout", -1); - } - - Task.WaitAll(outTask, errTask); - var so2 = outTask.Result; - var se2 = errTask.Result; int code; try { code = proc.ExitCode; } catch { code = -1; } return (code == 0, so2, se2, code); @@ -836,7 +812,7 @@ public sealed class FileCompactor : IDisposable /// State of the process, output of the process and error with exit code private (bool ok, string stdout, string stderr, int exitCode) RunProcessShell(string command, string? workingDir = null, int timeoutMs = 60000) { - // Use a LOGIN shell so PATH includes /usr/sbin etc. + var psi = new ProcessStartInfo("/bin/bash") { RedirectStandardOutput = true, @@ -845,7 +821,7 @@ public sealed class FileCompactor : IDisposable CreateNoWindow = true }; if (!string.IsNullOrEmpty(workingDir)) psi.WorkingDirectory = workingDir; - + // Use a Login shell so PATH includes /usr/sbin etc. AKA -lc psi.ArgumentList.Add("-lc"); psi.ArgumentList.Add(QuoteDouble(command)); EnsureUnixPathEnv(psi); @@ -853,43 +829,74 @@ public sealed class FileCompactor : IDisposable using var proc = Process.Start(psi); if (proc is null) return (false, "", "failed to start /bin/bash", -1); - var outTask = proc.StandardOutput.ReadToEndAsync(_compactionCts.Token); - var errTask = proc.StandardError.ReadToEndAsync(_compactionCts.Token); - var both = Task.WhenAll(outTask, errTask); - - if (_dalamudUtilService.IsWine) + var (success, so2, se2) = CheckProcessResult(proc, timeoutMs, _compactionCts.Token); + if (!success) { - var finished = Task.WhenAny(both, Task.Delay(timeoutMs, _compactionCts.Token)).GetAwaiter().GetResult(); - if (finished != both) - { - try { proc.Kill(entireProcessTree: true); } catch { /* ignore this */ } - try { Task.WaitAll([outTask, errTask], 1000, _compactionCts.Token); } catch { /* ignore this */ } - var so = outTask.IsCompleted ? outTask.Result : ""; - var se = errTask.IsCompleted ? errTask.Result : "timeout"; - return (false, so, se, -1); - } - - var stdout = outTask.Result; - var stderr = errTask.Result; - var ok = string.IsNullOrWhiteSpace(stderr); - return (ok, stdout, stderr, ok ? 0 : -1); + return (false, so2, se2, -1); } - if (!proc.WaitForExit(timeoutMs)) - { - try { proc.Kill(entireProcessTree: true); } catch { /* ignore this */ } - try { Task.WaitAll([outTask, errTask], 1000, _compactionCts.Token); } catch { /* ignore this */ } - return (false, outTask.IsCompleted ? outTask.Result : "", "timeout", -1); - } - - Task.WaitAll(outTask, errTask); - var so2 = outTask.Result; - var se2 = errTask.Result; int code; try { code = proc.ExitCode; } catch { code = -1; } return (code == 0, so2, se2, code); } + /// + /// Checking the process result for shell or direct processes + /// + /// Process + /// How long when timeout is gotten + /// Cancellation Token + /// Multiple variables + private (bool success, string testy, string testi) CheckProcessResult(Process proc, int timeoutMs, CancellationToken token) + { + var outTask = proc.StandardOutput.ReadToEndAsync(token); + var errTask = proc.StandardError.ReadToEndAsync(token); + var bothTasks = Task.WhenAll(outTask, errTask); + + //On wine, we dont wanna use waitforexit as it will be always broken and giving an error. + if (_dalamudUtilService.IsWine) + { + var finished = Task.WhenAny(bothTasks, Task.Delay(timeoutMs, token)).GetAwaiter().GetResult(); + if (finished != bothTasks) + { + try + { + proc.Kill(entireProcessTree: true); + Task.WaitAll([outTask, errTask], 1000, token); + } + catch + { + // ignore this + } + var so = outTask.IsCompleted ? outTask.Result : ""; + var se = errTask.IsCompleted ? errTask.Result : "timeout"; + return (false, so, se); + } + + var stderr = errTask.Result; + var ok = string.IsNullOrWhiteSpace(stderr); + return (ok, outTask.Result, stderr); + } + + // On linux, we can use it as we please + if (!proc.WaitForExit(timeoutMs)) + { + try + { + proc.Kill(entireProcessTree: true); + Task.WaitAll([outTask, errTask], 1000, token); + } + catch + { + // ignore this + } + return (false, outTask.IsCompleted ? outTask.Result : "", "timeout"); + } + + Task.WaitAll(outTask, errTask); + return (true, outTask.Result, errTask.Result); + } + /// /// Enqueues the compaction/decompation of an filepath. /// @@ -991,6 +998,11 @@ public sealed class FileCompactor : IDisposable } } + /// + /// Resolves linux path from wine pathing + /// + /// Windows path given from Wine + /// Linux path to be used in Linux private string ResolveLinuxPathForWine(string windowsPath) { var (ok, outp, _, _) = RunProcessShell($"winepath -u {QuoteSingle(windowsPath)}", null, 5000); @@ -998,6 +1010,10 @@ public sealed class FileCompactor : IDisposable return ToLinuxPathIfWine(windowsPath, isWine: true); } + /// + /// Ensures the Unix pathing to be included into the process + /// + /// Process private static void EnsureUnixPathEnv(ProcessStartInfo psi) { if (!psi.Environment.TryGetValue("PATH", out var p) || string.IsNullOrWhiteSpace(p)) @@ -1006,6 +1022,11 @@ public sealed class FileCompactor : IDisposable psi.Environment["PATH"] = "/usr/sbin:/usr/bin:/bin:" + p; } + /// + /// Resolves paths for Btrfs to be used on wine or linux and windows in case + /// + /// Path given t + /// private (string windowsPath, string linuxPath) ResolvePathsForBtrfs(string path) { bool isWindowsProc = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); @@ -1021,13 +1042,19 @@ public sealed class FileCompactor : IDisposable return (path, linux); } - private bool ProbeFileReadableForBtrfs(string windowsPath, string linuxPath) + /// + /// Probes file if its readable to be used + /// + /// Windows path + /// Linux path + /// Succesfully probed or not + private bool ProbeFileReadableForBtrfs(string winePath, string linuxPath) { try { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - using var _ = new FileStream(windowsPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + using var _ = new FileStream(winePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); } else { @@ -1038,6 +1065,12 @@ public sealed class FileCompactor : IDisposable catch { return false; } } + /// + /// Running functions into the Btrfs Gate/Threading. + /// + /// Type of the function that wants to be run inside Btrfs Gate + /// Body of the function + /// Task private T RunWithBtrfsGate(Func body) { bool acquired = false;