Added safe checks on enqueue.

This commit is contained in:
cake
2025-11-03 19:27:47 +01:00
parent d4dca455ba
commit cfc9f60176

View File

@@ -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;
}
/// <summary>
/// Get File Size in an Btrfs file system (Linux/Wine).
/// </summary>
/// <param name="fileInfo">File that you want the size from.</param>
/// <returns>Succesful check and value of the filesize.</returns>
/// <exception cref="InvalidOperationException">Fails on the Process in StartProcessInfo</exception>
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);
}
/// <summary>
/// Get File Size in an NTFS file system (Windows).
/// </summary>
/// <param name="fileInfo">File that you want the size from.</param>
/// <returns>Succesful check and value of the filesize.</returns>
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);
}
/// <summary>
/// Compressing the given path with BTRFS or NTFS file system.
/// </summary>
@@ -293,7 +332,7 @@ public sealed class FileCompactor : IDisposable
/// Decompress an BTRFS File
/// </summary>
/// <param name="path">Path of the compressed file</param>
/// <returns>Decompessing state</returns>
/// <returns>Decompressing state</returns>
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
/// </summary>
/// <param name="path">Path of the compressed file</param>
/// <returns>Decompessing state</returns>
/// <returns>Decompressing state</returns>
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 _);
}
}