Skip to content

Commit

Permalink
refactor: overhaul FileSystemCompression class
Browse files Browse the repository at this point in the history
Added extension methods: DirectoryInfo.Compress()
FileInfo.Compress()

Reworked methods:
SetCompression() (formerly EnableCompression())
  • Loading branch information
BinToss committed Mar 15, 2022
1 parent c53a407 commit 3fbeba7
Showing 1 changed file with 141 additions and 21 deletions.
162 changes: 141 additions & 21 deletions src/Common/FileSystemCompression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,35 +4,155 @@
/// - Goz https://stackoverflow.com/users/131140/goz

using System;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
using PInvoke;

namespace HXE.Common
{
internal static class FileSystemCompression
{
private const int FSCTL_SET_COMPRESSION = 0x9C040;
private const short COMPRESSION_FORMAT_DEFAULT = 1;

[DllImport("kernel32.dll", SetLastError = true)]
private static extern int DeviceIoControl(
SafeFileHandle hDevice,
int dwIoControlCode,
ref short lpInBuffer,
int nInBufferSize,
IntPtr lpOutBuffer,
int nOutBufferSize,
ref int lpBytesReturned,
IntPtr lpOverlapped);

public static bool EnableCompression(SafeFileHandle handle)
private static class Constants
{
int lpBytesReturned = 0;
short lpInBuffer = COMPRESSION_FORMAT_DEFAULT;
public const int FSCTL_SET_COMPRESSION = 0x9C040;
public const short COMPRESSION_FORMAT_DEFAULT = 1;
}

// TODO: Duplicate as non-extension "CompressDirectory()"
/// <summary>
/// Compress the directory represented by the DirectoryInfo object.
/// </summary>
/// <param name="directoryInfo">The directory to enable compression for.</param>
/// <param name="compressFiles"></param> // TODO
/// <param name="recurse"></param> // TODO
/// <param name="progress"></param> // TODO
/// <exception cref="DirectoryNotFoundException">
/// The path encapsulated in the System.IO.DirectoryInfo object is invalid, such<br/>
/// as being on an unmapped drive.
/// </exception>
/// <exception cref="System.Security.SecurityException">The caller does not have the required permission.</exception>
/// <exception cref="FileNotFoundException">The file is not found.</exception>
/// <exception cref="UnauthorizedAccessException">file path is read-only.</exception>
/// <exception cref="Win32Exception">DeviceIoControl operation failed. See <see cref="Win32Exception.NativeErrorCode"/> for exception data.</exception>
public static void Compress(this DirectoryInfo directoryInfo, bool compressFiles = true, bool recurse = false, IProgress<int> progress = null)
{
/* Progress */
bool withProgress = progress != null;
int itemsCompleted = 0;
int itemsTotal = 1;

void UpdateProgress(int n)
{
if (withProgress) return; // not necessary, but it's here just in case.
itemsCompleted += n;
progress.Report(itemsCompleted * 100 / itemsTotal);
}

/* Get files, subdirectories */
SearchOption searchOption = recurse ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
DirectoryInfo[] directories = recurse ? directoryInfo.GetDirectories(
searchPattern: "*",
searchOption: searchOption
) : null;
FileInfo[] files = compressFiles ? directoryInfo.GetFiles(
searchPattern: "*",
searchOption: searchOption
) : null;

/* Add files, directories count to itemsTotal; Update progress */
if (withProgress)
{
if (files != null)
{
itemsTotal += files.Length;
}
if (directories != null)
{
itemsTotal += directories.Length;
}

UpdateProgress(0);
}

/* Compress root directory */
Kernel32.SafeObjectHandle hDirectory = Kernel32.CreateFile(
filename: directoryInfo.FullName,
access: Kernel32.ACCESS_MASK.GenericRight.GENERIC_READ | Kernel32.ACCESS_MASK.GenericRight.GENERIC_WRITE,
share: Kernel32.FileShare.None,
securityAttributes: Kernel32.SECURITY_ATTRIBUTES.Create(),
creationDisposition: Kernel32.CreationDisposition.OPEN_EXISTING,
flagsAndAttributes: Kernel32.CreateFileFlags.FILE_FLAG_BACKUP_SEMANTICS,
templateFile: null
);

SetCompression(hDirectory);
hDirectory.Close();
if (withProgress)
UpdateProgress(itemsCompleted++);

/* Compress sub-directories */
if (recurse)
{
foreach (DirectoryInfo directory in directories)
{
directory.Compress();
if (withProgress)
UpdateProgress(itemsCompleted++);
}
}

/* Compress files*/
if (compressFiles)
{
foreach (FileInfo file in files)
{
file.Compress();
if (withProgress)
UpdateProgress(itemsCompleted++);
}
}
}

// TODO: Duplicate as non-extension "CompressFile()"
/// <summary>
/// Set filesystem compression for the file
/// </summary>
/// <param name="fileInfo">A FileInfo object indicating the file to compress.</param>
/// <exception cref="System.Security.SecurityException">The caller does not have the required permission.</exception>
/// <exception cref="FileNotFoundException">The file is not found.</exception>
/// <exception cref="UnauthorizedAccessException">path is read-only or is a directory.</exception>
/// <exception cref="DirectoryNotFoundException">The specified path is invalid, such as being on an unmapped drive.</exception>
/// <exception cref="Win32Exception">DeviceIoControl operation failed. See <see cref="Win32Exception.NativeErrorCode"/> for exception data.</exception>
public static void Compress(this FileInfo fileInfo)
{
FileStream fileStream = fileInfo.Open(mode: FileMode.Open, access: FileAccess.ReadWrite, share: FileShare.None);
SetCompression((Kernel32.SafeObjectHandle)(SafeHandle)fileStream.SafeFileHandle);
fileStream.Dispose();
}

/// <summary>
/// P/Invoke DeviceIoControl with the FSCTL_SET_COMPRESSION
/// </summary>
/// <param name="handle"></param> //TODO
/// <exception cref="Win32Exception">DeviceIoControl operation failed. See <see cref="Win32Exception.NativeErrorCode"/> for exception data.</exception>
public static unsafe void SetCompression(Kernel32.SafeObjectHandle handle)
{
short lpInBuffer = Constants.COMPRESSION_FORMAT_DEFAULT;
bool success = Kernel32.DeviceIoControl(
hDevice: handle,
dwIoControlCode: Constants.FSCTL_SET_COMPRESSION,
inBuffer: &lpInBuffer,
nInBufferSize: sizeof(short), // sizeof(typeof(lpInBuffer.GetType()))
outBuffer: (void*)IntPtr.Zero,
nOutBufferSize: 0,
pBytesReturned: out _,
lpOverlapped: (Kernel32.OVERLAPPED*)IntPtr.Zero
);

return DeviceIoControl(handle, FSCTL_SET_COMPRESSION,
ref lpInBuffer, sizeof(short), IntPtr.Zero, 0,
ref lpBytesReturned, IntPtr.Zero) != 0;
if (!success)
{
throw new Win32Exception(Kernel32.GetLastError());
}
}
}
}

0 comments on commit 3fbeba7

Please sign in to comment.