diff --git a/LightlessSync/FileCache/FileCompactor.cs b/LightlessSync/FileCache/FileCompactor.cs index b9c2118..4722b1f 100644 --- a/LightlessSync/FileCache/FileCompactor.cs +++ b/LightlessSync/FileCache/FileCompactor.cs @@ -4,7 +4,6 @@ using Microsoft.Extensions.Logging; using Microsoft.Win32.SafeHandles; using System.Collections.Concurrent; using System.Diagnostics; -using System.IO; using System.Runtime.InteropServices; using System.Threading.Channels; using static LightlessSync.Utils.FileSystemHelper; @@ -140,42 +139,82 @@ public sealed class FileCompactor : IDisposable if (fsType == FilesystemType.NTFS && !_dalamudUtilService.IsWine) { - var blockSize = GetBlockSizeForPath(fileInfo.FullName, _logger, _dalamudUtilService.IsWine); - var losize = GetCompressedFileSizeW(fileInfo.FullName, out uint hosize); - var size = (long)hosize << 32 | losize; - return ((size + blockSize - 1) / blockSize) * blockSize; + (bool flowControl, long value) = GetFileSizeNTFS(fileInfo); + if (!flowControl) + { + return value; + } } if (fsType == FilesystemType.Btrfs) { - try + (bool flowControl, long value) = GetFileSizeBtrfs(fileInfo); + if (!flowControl) { - bool isWine = _dalamudUtilService?.IsWine ?? false; - string realPath = isWine ? ToLinuxPathIfWine(fileInfo.FullName, isWine) : fileInfo.FullName; - - var fileName = "stat"; - var arguments = $"-c %b \"{realPath}\""; - - (bool processControl, bool success) = StartProcessInfo(realPath, fileName, arguments, out Process? proc, out string stdout); - - if (!processControl && !success) - throw new InvalidOperationException($"stat failed: {proc}"); - - if (!long.TryParse(stdout.Trim(), out var blocks)) - throw new InvalidOperationException($"invalid stat output: {stdout}"); - - // st_blocks are always 512-byte on Linux enviroment. - return blocks * 512L; - } - catch (Exception ex) - { - _logger.LogDebug(ex, "Failed stat size for {file}, fallback to Length", fileInfo.FullName); + return value; } } return fileInfo.Length; } + /// + /// Get File Size in an Btrfs file system (Linux/Wine). + /// + /// File that you want the size from. + /// Succesful check and value of the filesize. + /// Fails on the Process in StartProcessInfo + private (bool flowControl, long value) GetFileSizeBtrfs(FileInfo fileInfo) + { + try + { + bool isWine = _dalamudUtilService?.IsWine ?? false; + string realPath = isWine ? ToLinuxPathIfWine(fileInfo.FullName, isWine) : fileInfo.FullName; + + var fileName = "stat"; + var arguments = $"-c %b \"{realPath}\""; + + (bool processControl, bool success) = StartProcessInfo(realPath, fileName, arguments, out Process? proc, out string stdout); + + if (!processControl && !success) + throw new InvalidOperationException($"stat failed: {proc}"); + + if (!long.TryParse(stdout.Trim(), out var blocks)) + throw new InvalidOperationException($"invalid stat output: {stdout}"); + + // st_blocks are always 512-byte on Linux enviroment. + return (flowControl: false, value: blocks * 512L); + } + catch (Exception ex) + { + _logger.LogDebug(ex, "Failed stat size for {file}, fallback to Length", fileInfo.FullName); + } + + return (flowControl: true, value: default); + } + + /// + /// Get File Size in an NTFS file system (Windows). + /// + /// File that you want the size from. + /// Succesful check and value of the filesize. + private (bool flowControl, long value) GetFileSizeNTFS(FileInfo fileInfo) + { + try + { + var blockSize = GetBlockSizeForPath(fileInfo.FullName, _logger, _dalamudUtilService.IsWine); + var losize = GetCompressedFileSizeW(fileInfo.FullName, out uint hosize); + var size = (long)hosize << 32 | losize; + return (flowControl: false, value: ((size + blockSize - 1) / blockSize) * blockSize); + } + catch (Exception ex) + { + _logger.LogDebug(ex, "Failed stat size for {file}, fallback to Length", fileInfo.FullName); + } + + return (flowControl: true, value: default); + } + /// /// Compressing the given path with BTRFS or NTFS file system. /// @@ -293,7 +332,7 @@ public sealed class FileCompactor : IDisposable /// Decompress an BTRFS File /// /// Path of the compressed file - /// Decompessing state + /// Decompressing state private bool DecompressBtrfsFile(string path) { var fs = new FileStream(path, FileMode.Open, FileAccess.ReadWrite, FileShare.Read); @@ -353,7 +392,7 @@ public sealed class FileCompactor : IDisposable /// Decompress an NTFS File /// /// Path of the compressed file - /// Decompessing state + /// Decompressing state private bool DecompressWOFFile(string path, out FileStream fs) { fs = new FileStream(path, FileMode.Open, FileAccess.ReadWrite, FileShare.Read); @@ -571,14 +610,6 @@ public sealed class FileCompactor : IDisposable return false; } - //Skipping small files to make compression a bit faster, its not that effective on small files. - int blockSize = GetBlockSizeForPath(realPath, _logger, isWine); - if (fi.Length < Math.Max(blockSize * 2, 128 * 1024)) - { - _logger.LogTrace("Skipping Btrfs compression for small file {file} ({size} bytes)", realPath, fi.Length); - return true; - } - if (IsBtrfsCompressedFile(realPath)) { _logger.LogTrace("File {file} already compressed (Btrfs), skipping file", realPath); @@ -695,21 +726,52 @@ public sealed class FileCompactor : IDisposable private void EnqueueCompaction(string filePath) { + // Safe-checks + if (string.IsNullOrWhiteSpace(filePath)) + return; + + if (!_lightlessConfigService.Current.UseCompactor) + return; + + if (!File.Exists(filePath)) + return; + if (!_pendingCompactions.TryAdd(filePath, 0)) return; - var fsType = GetFilesystemType(filePath, _dalamudUtilService.IsWine); - if (fsType != FilesystemType.NTFS && fsType != FilesystemType.Btrfs) + bool enqueued = false; + try { - _logger.LogTrace("Skip enqueue (unsupported fs) {fs} {file}", fsType, filePath); - _pendingCompactions.TryRemove(filePath, out _); - return; - } + bool isWine = _dalamudUtilService?.IsWine ?? false; + var fsType = GetFilesystemType(filePath, isWine); - if (!_compactionQueue.Writer.TryWrite(filePath)) + // If under Wine, we should skip NTFS because its not Windows but might return NTFS. + if (fsType == FilesystemType.NTFS && isWine) + { + _logger.LogTrace("Skip enqueue (NTFS under Wine) {file}", filePath); + return; + } + + // Unknown file system should be skipped. + if (fsType != FilesystemType.NTFS && fsType != FilesystemType.Btrfs) + { + _logger.LogTrace("Skip enqueue (unsupported fs) {fs} {file}", fsType, filePath); + return; + } + + if (!_compactionQueue.Writer.TryWrite(filePath)) + { + _logger.LogTrace("Skip enqueue: compaction channel is closed {file}", filePath); + return; + } + + enqueued = true; + _logger.LogTrace("Queued compaction for {file} (fs={fs})", filePath, fsType); + } + finally { - _pendingCompactions.TryRemove(filePath, out _); - _logger.LogDebug("Failed to enqueue compaction {file}", filePath); + if (!enqueued) + _pendingCompactions.TryRemove(filePath, out _); } }