Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Added 'compress' attribute option to the properties window #16319

Merged
merged 20 commits into from
Dec 4, 2024
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
11 changes: 11 additions & 0 deletions src/Files.App.CsWin32/NativeMethods.txt
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,12 @@ PSGetPropertyKeyFromName
ShellExecuteEx
CoTaskMemFree
QueryDosDevice
DeviceIoControl
GetLastError
CreateFile
GetVolumeInformation
COMPRESSION_FORMAT
FILE_ACCESS_RIGHTS
FindFirstFileEx
FindNextFile
CreateFile
Expand All @@ -160,5 +166,10 @@ IApplicationDocumentLists
ApplicationDocumentLists
IApplicationActivationManager
MENU_ITEM_TYPE
COMPRESSION_FORMAT
FSCTL_SET_COMPRESSION
FSCTL_DISMOUNT_VOLUME
FSCTL_LOCK_VOLUME
FILE_FILE_COMPRESSION
WM_WINDOWPOSCHANGING
WINDOWPOS
11 changes: 5 additions & 6 deletions src/Files.App/Data/Models/RemovableDevice.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
// Copyright (c) 2024 Files Community
// Licensed under the MIT License. See the LICENSE.

using System;
using System.Diagnostics;
using System.Threading.Tasks;
using Windows.Win32;
using Windows.Win32.Storage.FileSystem;
using static Files.App.Helpers.Win32PInvoke;

namespace Files.App.Data.Models
Expand All @@ -20,7 +19,7 @@ public RemovableDevice(string letter)
string filename = @"\\.\" + driveLetter + ":";

handle = CreateFileFromAppW(filename,
GENERIC_READ | GENERIC_WRITE,
(uint)(FILE_ACCESS_RIGHTS.FILE_GENERIC_READ | FILE_ACCESS_RIGHTS.FILE_GENERIC_WRITE),
FILE_SHARE_READ | FILE_SHARE_WRITE,
nint.Zero, OPEN_EXISTING, 0, nint.Zero);
}
Expand Down Expand Up @@ -52,7 +51,7 @@ private async Task<bool> LockVolumeAsync()

for (int i = 0; i < 5; i++)
{
if (DeviceIoControl(handle, FSCTL_LOCK_VOLUME, nint.Zero, 0, nint.Zero, 0, out _, nint.Zero))
if (DeviceIoControl(handle, PInvoke.FSCTL_LOCK_VOLUME, nint.Zero, 0, nint.Zero, 0, out _, nint.Zero))
{
Debug.WriteLine("Lock successful!");
result = true;
Expand All @@ -72,7 +71,7 @@ private async Task<bool> LockVolumeAsync()

private bool DismountVolume()
{
return DeviceIoControl(handle, FSCTL_DISMOUNT_VOLUME, nint.Zero, 0, nint.Zero, 0, out _, nint.Zero);
return DeviceIoControl(handle, PInvoke.FSCTL_DISMOUNT_VOLUME, nint.Zero, 0, nint.Zero, 0, out _, nint.Zero);
}

private bool PreventRemovalOfVolume(bool prevent)
Expand Down
34 changes: 34 additions & 0 deletions src/Files.App/Data/Models/SelectedItemsPropertiesViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,40 @@ public bool? IsHiddenEditedValue
set => SetProperty(ref isHiddenEditedValue, value);
}

private bool? isContentCompressed;
/// <remarks>
/// Applies to NTFS item compression.
/// </remarks>
public bool? IsContentCompressed
yaira2 marked this conversation as resolved.
Show resolved Hide resolved
{
get => isContentCompressed;
set
{
SetProperty(ref isContentCompressed, value);
IsContentCompressedEditedValue = value;
}
}

private bool? isContentCompressedEditedValue;
/// <remarks>
/// Applies to NTFS item compression.
/// </remarks>
public bool? IsContentCompressedEditedValue
{
get => isContentCompressedEditedValue;
set => SetProperty(ref isContentCompressedEditedValue, value);
}

private bool canCompressContent;
/// <remarks>
/// Applies to NTFS item compression.
/// </remarks>
public bool CanCompressContent
{
get => canCompressContent;
set => SetProperty(ref canCompressContent, value);
}

private bool runAsAdmin;
public bool RunAsAdmin
{
Expand Down
77 changes: 69 additions & 8 deletions src/Files.App/Helpers/Win32/Win32Helper.Storage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@
using System.Windows.Forms;
using Vanara.PInvoke;
using Windows.System;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.Storage.FileSystem;
d2dyno1 marked this conversation as resolved.
Show resolved Hide resolved
yaira2 marked this conversation as resolved.
Show resolved Hide resolved
using static Vanara.PInvoke.Kernel32;
using COMPRESSION_FORMAT = Windows.Win32.Storage.FileSystem.COMPRESSION_FORMAT;
using HRESULT = Vanara.PInvoke.HRESULT;
using HWND = Vanara.PInvoke.HWND;

namespace Files.App.Helpers
{
Expand Down Expand Up @@ -324,7 +331,7 @@ public static string ExtractStringFromDLL(string file, int number)
}

if (iconData is not null || iconOptions.HasFlag(IconOptions.ReturnThumbnailOnly))
return iconData;
return iconData;
else
{
var shfi = new Shell32.SHFILEINFO();
Expand All @@ -333,7 +340,7 @@ public static string ExtractStringFromDLL(string file, int number)
// Cannot access file, use file attributes
var useFileAttibutes = iconData is null;

var ret = Shell32.SHGetFileInfo(path, isFolder ? FileAttributes.Directory : 0, ref shfi, Shell32.SHFILEINFO.Size, flags);
var ret = Shell32.SHGetFileInfo(path, isFolder ? FileAttributes.Directory : 0, ref shfi, Shell32.SHFILEINFO.Size, flags);
if (ret == IntPtr.Zero)
return iconData;

Expand Down Expand Up @@ -884,24 +891,24 @@ private static Process CreatePowershellProcess(string command, PowerShellExecuti
public static SafeFileHandle CreateFileForWrite(string filePath, bool overwrite = true)
{
return new SafeFileHandle(Win32PInvoke.CreateFileFromApp(filePath,
Win32PInvoke.GENERIC_WRITE, 0, IntPtr.Zero, overwrite ? Win32PInvoke.CREATE_ALWAYS : Win32PInvoke.OPEN_ALWAYS, (uint)Win32PInvoke.File_Attributes.BackupSemantics, IntPtr.Zero), true);
(uint)FILE_ACCESS_RIGHTS.FILE_GENERIC_WRITE, 0, IntPtr.Zero, overwrite ? Win32PInvoke.CREATE_ALWAYS : Win32PInvoke.OPEN_ALWAYS, (uint)Win32PInvoke.File_Attributes.BackupSemantics, IntPtr.Zero), true);
}

public static SafeFileHandle OpenFileForRead(string filePath, bool readWrite = false, uint flags = 0)
{
return new SafeFileHandle(Win32PInvoke.CreateFileFromApp(filePath,
Win32PInvoke.GENERIC_READ | (readWrite ? Win32PInvoke.GENERIC_WRITE : 0), (uint)(Win32PInvoke.FILE_SHARE_READ | (readWrite ? 0 : Win32PInvoke.FILE_SHARE_WRITE)), IntPtr.Zero, Win32PInvoke.OPEN_EXISTING, (uint)Win32PInvoke.File_Attributes.BackupSemantics | flags, IntPtr.Zero), true);
(uint)FILE_ACCESS_RIGHTS.FILE_GENERIC_READ | (uint)(readWrite ? FILE_ACCESS_RIGHTS.FILE_GENERIC_WRITE : 0u), (uint)(Win32PInvoke.FILE_SHARE_READ | (readWrite ? 0 : Win32PInvoke.FILE_SHARE_WRITE)), IntPtr.Zero, Win32PInvoke.OPEN_EXISTING, (uint)Win32PInvoke.File_Attributes.BackupSemantics | flags, IntPtr.Zero), true);
}

public static bool GetFileDateModified(string filePath, out FILETIME dateModified)
{
using var hFile = new SafeFileHandle(Win32PInvoke.CreateFileFromApp(filePath, Win32PInvoke.GENERIC_READ, Win32PInvoke.FILE_SHARE_READ, IntPtr.Zero, Win32PInvoke.OPEN_EXISTING, (uint)Win32PInvoke.File_Attributes.BackupSemantics, IntPtr.Zero), true);
using var hFile = new SafeFileHandle(Win32PInvoke.CreateFileFromApp(filePath, (uint)FILE_ACCESS_RIGHTS.FILE_GENERIC_READ, Win32PInvoke.FILE_SHARE_READ, IntPtr.Zero, Win32PInvoke.OPEN_EXISTING, (uint)Win32PInvoke.File_Attributes.BackupSemantics, IntPtr.Zero), true);
return Win32PInvoke.GetFileTime(hFile.DangerousGetHandle(), out _, out _, out dateModified);
}

public static bool SetFileDateModified(string filePath, FILETIME dateModified)
{
using var hFile = new SafeFileHandle(Win32PInvoke.CreateFileFromApp(filePath, Win32PInvoke.FILE_WRITE_ATTRIBUTES, 0, IntPtr.Zero, Win32PInvoke.OPEN_EXISTING, (uint)Win32PInvoke.File_Attributes.BackupSemantics, IntPtr.Zero), true);
using var hFile = new SafeFileHandle(Win32PInvoke.CreateFileFromApp(filePath, (uint)FILE_ACCESS_RIGHTS.FILE_WRITE_ATTRIBUTES, 0, IntPtr.Zero, Win32PInvoke.OPEN_EXISTING, (uint)Win32PInvoke.File_Attributes.BackupSemantics, IntPtr.Zero), true);
return Win32PInvoke.SetFileTime(hFile.DangerousGetHandle(), new(), new(), dateModified);
}

Expand Down Expand Up @@ -935,10 +942,64 @@ public static bool UnsetFileAttribute(string lpFileName, FileAttributes dwAttrs)
return Win32PInvoke.SetFileAttributesFromApp(lpFileName, lpFileInfo.dwFileAttributes & ~dwAttrs);
}

public static unsafe bool CanCompressContent(string path)
{
path = Path.GetPathRoot(path) ?? string.Empty;
uint dwFileSystemFlags = 0;

var success = PInvoke.GetVolumeInformation(
path,
null,
0u,
null,
null,
&dwFileSystemFlags,
null,
0u);

if (!success)
return false;

return (dwFileSystemFlags & PInvoke.FILE_FILE_COMPRESSION) != 0;
}

public static unsafe bool SetCompressionAttributeIoctl(string lpFileName, bool isCompressed)
{
// GENERIC_READ | GENERIC_WRITE flags are needed here
// FILE_FLAG_BACKUP_SEMANTICS is used to open directories
using var hFile = PInvoke.CreateFile(
lpFileName,
(uint)(FILE_ACCESS_RIGHTS.FILE_GENERIC_READ | FILE_ACCESS_RIGHTS.FILE_GENERIC_WRITE | FILE_ACCESS_RIGHTS.FILE_WRITE_ATTRIBUTES),
FILE_SHARE_MODE.FILE_SHARE_READ | FILE_SHARE_MODE.FILE_SHARE_WRITE,
lpSecurityAttributes: null,
FILE_CREATION_DISPOSITION.OPEN_EXISTING,
FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_NORMAL | FILE_FLAGS_AND_ATTRIBUTES.FILE_FLAG_BACKUP_SEMANTICS,
hTemplateFile: null);

if (hFile.IsInvalid)
return false;

var bytesReturned = 0u;
d2dyno1 marked this conversation as resolved.
Show resolved Hide resolved
var compressionFormat = isCompressed
? COMPRESSION_FORMAT.COMPRESSION_FORMAT_DEFAULT
: COMPRESSION_FORMAT.COMPRESSION_FORMAT_NONE;

var result = PInvoke.DeviceIoControl(
new(hFile.DangerousGetHandle()),
PInvoke.FSCTL_SET_COMPRESSION,
&compressionFormat,
sizeof(ushort),
null,
0u,
&bytesReturned);

return result;
}

public static string ReadStringFromFile(string filePath)
{
IntPtr hFile = Win32PInvoke.CreateFileFromApp(filePath,
Win32PInvoke.GENERIC_READ,
(uint)FILE_ACCESS_RIGHTS.FILE_GENERIC_READ,
Win32PInvoke.FILE_SHARE_READ,
IntPtr.Zero,
Win32PInvoke.OPEN_EXISTING,
Expand Down Expand Up @@ -987,7 +1048,7 @@ public static string ReadStringFromFile(string filePath)
public static bool WriteStringToFile(string filePath, string str, Win32PInvoke.File_Attributes flags = 0)
{
IntPtr hStream = Win32PInvoke.CreateFileFromApp(filePath,
Win32PInvoke.GENERIC_WRITE, 0, IntPtr.Zero, Win32PInvoke.CREATE_ALWAYS, (uint)(Win32PInvoke.File_Attributes.BackupSemantics | flags), IntPtr.Zero);
(uint)FILE_ACCESS_RIGHTS.FILE_GENERIC_WRITE, 0, IntPtr.Zero, Win32PInvoke.CREATE_ALWAYS, (uint)(Win32PInvoke.File_Attributes.BackupSemantics | flags), IntPtr.Zero);
if (hStream.ToInt64() == -1)
{
return false;
Expand Down
9 changes: 3 additions & 6 deletions src/Files.App/Helpers/Win32/Win32PInvoke.Consts.cs
yaira2 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,14 @@ public static partial class Win32PInvoke
public const int FILE_NOTIFY_CHANGE_SECURITY = 256;

public const int INVALID_HANDLE_VALUE = -1;
public const uint GENERIC_READ = 0x80000000;
public const uint GENERIC_WRITE = 0x40000000;
public const int FILE_SHARE_READ = 0x00000001;
public const int FILE_SHARE_WRITE = 0x00000002;
public const uint FILE_SHARE_DELETE = 0x00000004;
public const int OPEN_EXISTING = 3;
public const int FSCTL_LOCK_VOLUME = 0x00090018;
public const int FSCTL_DISMOUNT_VOLUME = 0x00090020;
public const int IOCTL_STORAGE_EJECT_MEDIA = 0x2D4808;
public const int IOCTL_STORAGE_MEDIA_REMOVAL = 0x002D4804;

public const uint FILE_APPEND_DATA = 0x0004;
public const uint FILE_WRITE_ATTRIBUTES = 0x100;


public const uint FILE_BEGIN = 0;
Expand All @@ -45,8 +40,10 @@ public static partial class Win32PInvoke
public const uint OPEN_ALWAYS = 4;
public const uint TRUNCATE_EXISTING = 5;

public const int MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 16 * 1024;
// FSCTL
public const int FSCTL_GET_REPARSE_POINT = 0x000900A8;

public const int MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 16 * 1024;
public const uint IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003;
public const uint IO_REPARSE_TAG_SYMLINK = 0xA000000C;

Expand Down
3 changes: 3 additions & 0 deletions src/Files.App/Strings/en-US/Resources.resw
Original file line number Diff line number Diff line change
Expand Up @@ -3977,6 +3977,9 @@
<data name="BulkRename" xml:space="preserve">
<value>Bulk rename</value>
</data>
<data name="CompressContents" xml:space="preserve">
<value>Compress contents</value>
</data>
<data name="ShowCreateAlternateDataStream" xml:space="preserve">
<value>Show option to create alternate data stream</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.IO;
using Windows.Win32;
using Windows.Win32.Storage.FileSystem;
using static Files.App.Helpers.Win32Helper;
using static Files.App.Helpers.Win32PInvoke;

Expand All @@ -16,7 +17,7 @@ public bool CreateFile(string path)
{
PInvoke.CreateDirectoryFromApp(Path.GetDirectoryName(path), null);

var hFile = CreateFileFromApp(path, GENERIC_READ, FILE_SHARE_READ, IntPtr.Zero, OPEN_ALWAYS, (uint)File_Attributes.BackupSemantics, IntPtr.Zero);
var hFile = CreateFileFromApp(path, (uint)FILE_ACCESS_RIGHTS.FILE_GENERIC_READ, FILE_SHARE_READ, IntPtr.Zero, OPEN_ALWAYS, (uint)File_Attributes.BackupSemantics, IntPtr.Zero);
if (hFile.IsHandleInvalid())
{
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,15 @@ private void ViewModel_PropertyChanged(object sender, System.ComponentModel.Prop

}
break;

case "IsContentCompressed":
{
List.ForEach(x =>
{
Win32Helper.SetCompressionAttributeIoctl(x.ItemPath, ViewModel.IsContentCompressed ?? false);
});
}
break;
}
}
}
Expand Down
16 changes: 10 additions & 6 deletions src/Files.App/ViewModels/Properties/Items/FileProperties.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public override void GetBaseProperties()
ViewModel.CustomIconSource = Item.CustomIconSource;
ViewModel.LoadFileIcon = Item.LoadFileIcon;
ViewModel.IsDownloadedFile = Win32Helper.ReadStringFromFile($"{Item.ItemPath}:Zone.Identifier") is not null;
ViewModel.IsEditAlbumCoverVisible =
ViewModel.IsEditAlbumCoverVisible =
FileExtensionHelpers.IsVideoFile(Item.FileExtension) ||
FileExtensionHelpers.IsAudioFile(Item.FileExtension);

Expand Down Expand Up @@ -93,10 +93,10 @@ await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(

public override async Task GetSpecialPropertiesAsync()
{
ViewModel.IsReadOnly = Win32Helper.HasFileAttribute(
Item.ItemPath, System.IO.FileAttributes.ReadOnly);
ViewModel.IsHidden = Win32Helper.HasFileAttribute(
Item.ItemPath, System.IO.FileAttributes.Hidden);
ViewModel.IsReadOnly = Win32Helper.HasFileAttribute(Item.ItemPath, System.IO.FileAttributes.ReadOnly);
ViewModel.IsHidden = Win32Helper.HasFileAttribute(Item.ItemPath, System.IO.FileAttributes.Hidden);
ViewModel.CanCompressContent = Win32Helper.CanCompressContent(Item.ItemPath);
ViewModel.IsContentCompressed = Win32Helper.HasFileAttribute(Item.ItemPath, System.IO.FileAttributes.Compressed);

ViewModel.ItemSizeVisibility = true;
ViewModel.ItemSize = Item.FileSizeBytes.ToLongSizeString();
Expand Down Expand Up @@ -279,13 +279,17 @@ private async void ViewModel_PropertyChanged(object sender, System.ComponentMode
if (ViewModel.IsHidden is not null)
{
if ((bool)ViewModel.IsHidden)
Win32Helper.SetFileAttribute(Item.ItemPath, System.IO.FileAttributes.Hidden);
Win32Helper.SetFileAttribute(Item.ItemPath, System.IO.FileAttributes.Hidden);
else
Win32Helper.UnsetFileAttribute(Item.ItemPath, System.IO.FileAttributes.Hidden);
}

break;

case nameof(ViewModel.IsContentCompressed):
Win32Helper.SetCompressionAttributeIoctl(Item.ItemPath, ViewModel.IsContentCompressed ?? false);
break;

case nameof(ViewModel.RunAsAdmin):
case nameof(ViewModel.ShortcutItemPath):
case nameof(ViewModel.ShortcutItemWorkingDir):
Expand Down
9 changes: 7 additions & 2 deletions src/Files.App/ViewModels/Properties/Items/FolderProperties.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,9 @@ await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(

public async override Task GetSpecialPropertiesAsync()
{
ViewModel.IsHidden = Win32Helper.HasFileAttribute(
Item.ItemPath, System.IO.FileAttributes.Hidden);
ViewModel.IsHidden = Win32Helper.HasFileAttribute(Item.ItemPath, System.IO.FileAttributes.Hidden);
ViewModel.CanCompressContent = Win32Helper.CanCompressContent(Item.ItemPath);
ViewModel.IsContentCompressed = Win32Helper.HasFileAttribute(Item.ItemPath, System.IO.FileAttributes.Compressed);

var result = await FileThumbnailHelper.GetIconAsync(
Item.ItemPath,
Expand Down Expand Up @@ -211,6 +212,10 @@ private async void ViewModel_PropertyChanged(object sender, System.ComponentMode
}
break;

case "IsContentCompressed":
Win32Helper.SetCompressionAttributeIoctl(Item.ItemPath, ViewModel.IsContentCompressed ?? false);
break;

case "ShortcutItemPath":
case "ShortcutItemWorkingDir":
case "ShortcutItemArguments":
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public async override Task GetSpecialPropertiesAsync()
{
ViewModel.IsReadOnly = Win32Helper.HasFileAttribute(Library.ItemPath, System.IO.FileAttributes.ReadOnly);
ViewModel.IsHidden = Win32Helper.HasFileAttribute(Library.ItemPath, System.IO.FileAttributes.Hidden);
ViewModel.CanCompressContent = false;

var result = await FileThumbnailHelper.GetIconAsync(
Library.ItemPath,
Expand Down
Loading