7

I need to read and modify the 'Compressed' attribute of a file on an NTFS partition from Java. I imagined something in the java.nio.file.attribute package would do it -- hell it's a complex enough package, but I can't find this attribute.

The DosFileAttributes class has getters for the classic hidden/system/readonly/archive attributes only.

I tried Files.readAttributes which allows dynamically retrieving all attributes from a particular "attribute view". Under "dos:*" there was only the same attributes that are already available from the public methods of the DosFileAttributes class. I tried "ntfs:*" and "windows:*" but they weren't accepted as valid view names.

I also tried the UserDefinedFileAttributeView, but it gave me an empty list on any file I tried.

I wondered about shelling out to the attrib command (accepting the limitation that it wouldn't work for NTFS partitions mounted under Linux or other OSes) but that doesn't seem to support the attribute either. Help?

4

2 回答 2

8

由于标准 Java API 似乎确实缺少这一点,因此我看了一下自己使用JNA进行的操作。这是我第一次接触 JNA。它不像我想要的那么漂亮,而且 JNA 的代码似乎严重缺乏泛型,但它比使用 JNI 并试图为所需的不同平台设置糟糕的交叉编译器(最低 x86 和 x64即使您只针对一个操作系统)。最初是烦人的编译过程驱使我从 C++ 转向 Java,我希望永远不必再回到它。

无论如何,这似乎有效。希望它对其他人也有用。它提供了四种公共方法:

  • isAvailable()-- 调用其他方法是否应该工作(即,我们在 Windows 上并且 JNA 本地库加载正常)
  • isCompressed(File)
  • setCompressed(File, boolean)
  • volumeSupportsFileCompression(File)-- 询问 Windows 文件所在的分区是否支持 [individual] 文件压缩。例如,在 NTFS 上为真,在 FAT(U 盘等)上为假。

Windows API 中的压缩是通过专用的 I/O 控制操作完成的,而不仅仅是“SetAttributes”调用。如果它更简单(与其他文件属性同构),为了完整起见,我也会将加密属性放在那里,但无论如何。

import java.io.File;
import java.io.IOException;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.ptr.ShortByReference;
import com.sun.jna.win32.StdCallLibrary;
import com.sun.jna.win32.W32APIOptions;
import com.sun.jna.platform.win32.Kernel32;
import com.sun.jna.platform.win32.WinNT.HANDLE;

public class WindowsFileOps {
    private WindowsFileOps() {}


    private static interface Kernel32Extra extends StdCallLibrary {
        int COMPRESSION_FORMAT_NONE = 0x00000000;
        int COMPRESSION_FORMAT_DEFAULT = 0x00000001;
        int FSCTL_SET_COMPRESSION = 0x0009C040;

        Kernel32Extra INSTANCE = (Kernel32Extra)Native.loadLibrary("kernel32",
            Kernel32Extra.class, W32APIOptions.UNICODE_OPTIONS);

        boolean GetVolumeInformation(
            String lpRootPathName,
            Pointer lpVolumeNameBuffer,
            int nVolumeNameSize,
            IntByReference lpVolumeSerialNumber,
            IntByReference lpMaximumComponentLength,
            IntByReference lpFileSystemFlags,
            Pointer lpFileSystemNameBuffer,
            int nFileSystemNameSize
        );
    }


    private static Boolean isAvailable;
    public static boolean isAvailable() {
        if (isAvailable == null) {
            try {
                isAvailable = Kernel32.INSTANCE != null && Kernel32Extra.INSTANCE != null;
            } catch (Throwable t) {
                isAvailable = false;
            }
        }
        return isAvailable;
    }


    private static String pathString(File file) {
        // "\\?\" is a Windows API thing that enables paths longer than 260 chars
        return "\\\\?\\" + file.getAbsolutePath();
    }


    private static int getAttributes(File file) throws IOException {
        int attrib = Kernel32.INSTANCE.GetFileAttributes(pathString(file));
        if (attrib == Kernel32.INVALID_FILE_ATTRIBUTES) {
            throw new IOException("Unable to read file attributes of " + file);
        }
        return attrib;
    }


    public static boolean isCompressed(File file) throws IOException {
        return (getAttributes(file) & Kernel32.FILE_ATTRIBUTE_COMPRESSED) != 0;
    }


    public static void setCompressed(File file, boolean compressed) throws IOException {
        HANDLE hFile = Kernel32.INSTANCE.CreateFile(
            pathString(file),
            Kernel32.GENERIC_READ | Kernel32.GENERIC_WRITE,
            Kernel32.FILE_SHARE_READ,
            null,
            Kernel32.OPEN_EXISTING,
            0,
            null);
        try {
            if (!Kernel32.INSTANCE.DeviceIoControl(
                hFile,
                Kernel32Extra.FSCTL_SET_COMPRESSION,
                new ShortByReference((short)(
                    compressed
                        ? Kernel32Extra.COMPRESSION_FORMAT_DEFAULT
                        : Kernel32Extra.COMPRESSION_FORMAT_NONE
                )).getPointer(),
                2,
                null, 0,
                new IntByReference(),
                null
            )) throw new IOException("Unable to alter compression attribute of " + file);
        } finally {
            Kernel32.INSTANCE.CloseHandle(hFile);
        }
    }


    public static boolean volumeSupportsFileCompression(File file) throws IOException {
        IntByReference flags = new IntByReference();
        if (!Kernel32Extra.INSTANCE.GetVolumeInformation(
            pathString(file.getAbsoluteFile().toPath().getRoot().toFile()),
            null, 0,
            null,
            null,
            flags,
            null, 0
        )) throw new IOException("GetVolumeInformation failure");
        return (flags.getValue() & Kernel32.FILE_FILE_COMPRESSION) != 0;
    }
}
于 2013-10-24T13:12:46.360 回答
1

而不是 DosFileAttributes 尝试使用 BasicFileAttributes 并检查 isOther() 和 isRegularFile() 标志。这可能会告诉您文件是否已压缩。如果不是,您将不得不为 NTFS 创建自己的 FileSystemProvider impl,或者编写小的 JNI 代码,使用 WinAPI 为您读取该标志。

于 2013-10-23T15:20:39.797 回答