From caf890ce885010efa045fa3c88907c6e52c8092f Mon Sep 17 00:00:00 2001 From: JaniruTEC Date: Thu, 7 May 2020 20:35:22 +0200 Subject: [PATCH] Added FileInfo-API Added EasyFileInfo to wrap the result of DokanyFileSystem#getFileInformation (ByHandleFileInformation) and Added FindFileInfo to wrap the result of DokanyFileSystem#findFiles (WinBase.WIN32_FIND_DATA) to aid in creating a wrapper ("EasyDokanFileSystem") for DokanyFileSystem Added AbstractFileInfo as common base class for FileInfos (like EasyFileInfo and FindFileInfo) Added DefaultFileTimePolicy to help in returning valid FileTimes to Dokan if unavailable to AbstractFileInfo This commit is part of my effort to fix dokan-java's issue 32 (https://github.com/dokan-dev/dokan-java/issues/32): Adding wrappers for high-level programming --- .../dokan_java/DefaultFileTimePolicy.java | 13 ++ .../dokan_java/wrappers/AbstractFileInfo.java | 166 ++++++++++++++++++ .../dokan_java/wrappers/EasyFileInfo.java | 87 +++++++++ .../dokan_java/wrappers/FindFileInfo.java | 147 ++++++++++++++++ 4 files changed, 413 insertions(+) create mode 100644 src/main/java/dev/dokan/dokan_java/constants/dokan_java/DefaultFileTimePolicy.java create mode 100644 src/main/java/dev/dokan/dokan_java/wrappers/AbstractFileInfo.java create mode 100644 src/main/java/dev/dokan/dokan_java/wrappers/EasyFileInfo.java create mode 100644 src/main/java/dev/dokan/dokan_java/wrappers/FindFileInfo.java diff --git a/src/main/java/dev/dokan/dokan_java/constants/dokan_java/DefaultFileTimePolicy.java b/src/main/java/dev/dokan/dokan_java/constants/dokan_java/DefaultFileTimePolicy.java new file mode 100644 index 0000000..2031d97 --- /dev/null +++ b/src/main/java/dev/dokan/dokan_java/constants/dokan_java/DefaultFileTimePolicy.java @@ -0,0 +1,13 @@ +package dev.dokan.dokan_java.constants.dokan_java; + +/** + * [TO BE REPLACED WITH LICENSE NOTE] + */ +public enum DefaultFileTimePolicy { + + STATIC_YEAR_1601, + STATIC_YEAR_1970, + INHERIT_ELSE_1601, + INHERIT_ELSE_1970 + +} \ No newline at end of file diff --git a/src/main/java/dev/dokan/dokan_java/wrappers/AbstractFileInfo.java b/src/main/java/dev/dokan/dokan_java/wrappers/AbstractFileInfo.java new file mode 100644 index 0000000..d453597 --- /dev/null +++ b/src/main/java/dev/dokan/dokan_java/wrappers/AbstractFileInfo.java @@ -0,0 +1,166 @@ +package dev.dokan.dokan_java.wrappers; + +import dev.dokan.dokan_java.constants.dokan_java.DefaultFileTimePolicy; +import dev.dokan.dokan_java.constants.microsoft.FileAttribute; +import dev.dokan.dokan_java.structure.EnumIntegerSet; + +import java.nio.file.attribute.FileTime; +import java.util.concurrent.atomic.AtomicInteger; + +public class AbstractFileInfo { + + //See field by same name in com.sun.jna.platform.win32.WinBase.FILETIME; + private final static long WINDOWS_EPOCH_0 = -11644473600000L; + private final static long UNIX_EPOCH_0 = 0; + + private final static FileTime YEAR_1601 = FileTime.fromMillis(WINDOWS_EPOCH_0); + private final static FileTime YEAR_1970 = FileTime.fromMillis(UNIX_EPOCH_0); + + private final AtomicInteger fileAttributes; + + private FileTime creationTime; + private FileTime lastAccessTime; + private FileTime lastWriteTime; + + private DefaultFileTimePolicy fileTimePolicy = DefaultFileTimePolicy.INHERIT_ELSE_1970; + private long fileSize; + + public AbstractFileInfo(EnumIntegerSet attributes) { + this(attributes.isEmpty() ? FileAttribute.NORMAL.getMask() : attributes.toInt()); + } + + public AbstractFileInfo(int attributes) { + this.fileAttributes = new AtomicInteger(attributes); + } + + public int getFlags() { + return this.fileAttributes.get(); + } + + public void setFlags(int flags) { + this.fileAttributes.set(flags); + } + + public EnumIntegerSet getFileAttributes() { + return EnumIntegerSet.enumSetFromInt(this.fileAttributes.get(), FileAttribute.values()); + } + + public boolean getFlag(FileAttribute flag) { + return (this.fileAttributes.get() & flag.getMask()) != 0; + } + + public boolean setFlag(FileAttribute flag) { + return updateFlag(flag, true); + } + + public boolean unsetFlag(FileAttribute flag) { + return updateFlag(flag, false); + } + + public boolean updateFlag(FileAttribute flag, boolean value) { + int prev = this.fileAttributes.getAndUpdate(current -> current & (value ? flag.getMask() : ~flag.getMask())); + return (prev & flag.getMask()) != 0; + } + + public void setTimes(long creationTime, long lastAccessTime, long lastWriteTime) { + setCreationTime(creationTime); + setLastAccessTime(lastAccessTime); + setLastWriteTime(lastWriteTime); + } + + public void setTimes(FileTime creationTime, FileTime lastAccessTime, FileTime lastWriteTime) { + setCreationTime(creationTime); + setLastAccessTime(lastAccessTime); + setLastWriteTime(lastWriteTime); + } + + public FileTime getStaticCreationTime() { + return this.creationTime; + } + + public FileTime getCreationTime() { + if(this.creationTime != null) { + return this.creationTime; + } + return getAlternateTime(); + } + + public void setCreationTime(FileTime creationTime) { + this.creationTime = creationTime; + } + + public void setCreationTime(long millis) { + setCreationTime(FileTime.fromMillis(millis)); + } + + public FileTime getStaticLastWriteTime() { + return this.lastWriteTime; + } + + public FileTime getLastWriteTime() { + if(this.lastWriteTime != null) { + return this.lastWriteTime; + } + return getAlternateTime(); + } + + public void setLastWriteTime(FileTime lastWriteTime) { + this.lastWriteTime = lastWriteTime; + } + + public void setLastWriteTime(long millis) { + setLastWriteTime(FileTime.fromMillis(millis)); + } + + public FileTime getStaticLastAccessTime() { + return this.lastAccessTime; + } + + public FileTime getLastAccessTime() { + if(this.lastAccessTime != null) { + return this.lastAccessTime; + } + return getAlternateTime(); + } + + public void setLastAccessTime(FileTime lastAccessTime) { + this.lastAccessTime = lastAccessTime; + } + + public void setLastAccessTime(long millis) { + setLastAccessTime(FileTime.fromMillis(millis)); + } + + public long getFileSize() { + return this.fileSize; + } + + public void setFileSize(long fileSize) { + this.fileSize = fileSize; + } + + private FileTime getAlternateTime() { + if(this.fileTimePolicy == DefaultFileTimePolicy.STATIC_YEAR_1601) { + return YEAR_1601; + } + if(this.fileTimePolicy == DefaultFileTimePolicy.STATIC_YEAR_1970) { + return YEAR_1970; + } + + //--> Inheritance + //#1 Priority: Last write Time + if(this.lastWriteTime != null) { + return this.lastWriteTime; + } + //#2 Priority: Creation Time + if(this.creationTime != null) { + return this.creationTime; + } + //#3 Priority: Access Time + if(this.lastAccessTime != null) { + return this.lastAccessTime; + } + //#4 Resort to default time by policy + return this.fileTimePolicy == DefaultFileTimePolicy.INHERIT_ELSE_1601 ? YEAR_1601 : YEAR_1970; + } +} \ No newline at end of file diff --git a/src/main/java/dev/dokan/dokan_java/wrappers/EasyFileInfo.java b/src/main/java/dev/dokan/dokan_java/wrappers/EasyFileInfo.java new file mode 100644 index 0000000..4216ca3 --- /dev/null +++ b/src/main/java/dev/dokan/dokan_java/wrappers/EasyFileInfo.java @@ -0,0 +1,87 @@ +package dev.dokan.dokan_java.wrappers; + +import dev.dokan.dokan_java.constants.microsoft.FileAttribute; +import dev.dokan.dokan_java.structure.ByHandleFileInformation; +import dev.dokan.dokan_java.structure.EnumIntegerSet; + +import java.nio.file.Path; + +/** + * [TO BE REPLACED WITH LICENSE NOTE] + */ +public class EasyFileInfo extends AbstractFileInfo { + + private final Path path; + + private int volumeSerialNumber; + private long fileIndex; + private int numberOfLinks; + + public EasyFileInfo(Path path) { + this(path, FileAttribute.NORMAL.getMask()); + } + + public EasyFileInfo(Path path, EnumIntegerSet attributes) { + super(attributes); + this.path = path; + } + + public EasyFileInfo(Path path, int attributes) { + super(attributes); + this.path = path; + } + + public Path getPath() { + return this.path; + } + + public int getVolumeSerialNumber() { + return this.volumeSerialNumber; + } + + public void setVolumeSerialNumber(int volumeSerialNumber) { + this.volumeSerialNumber = volumeSerialNumber; + } + + public long getFileIndex() { + return this.fileIndex; + } + + public void setFileIndex(long fileIndex) { + this.fileIndex = fileIndex; + } + + public int getNumberOfLinks() { + return this.numberOfLinks; + } + + public void setNumberOfLinks(int numberOfLinks) { + this.numberOfLinks = numberOfLinks; + } + + public ByHandleFileInformation toByHandleFileInformation() { + return toByHandleFileInformation(this.path); + } + + public ByHandleFileInformation toByHandleFileInformation(Path pathOverride) { + ByHandleFileInformation info = new ByHandleFileInformation(pathOverride, + getFlags(), + getCreationTime(), + getLastAccessTime(), + getLastWriteTime(), + this.volumeSerialNumber, + getFileSize(), + this.fileIndex); + info.nNumberOfLinks = this.numberOfLinks; + + return info; + } + + public void copyTo(ByHandleFileInformation byHandleFileInformation) { + copyTo(byHandleFileInformation, this.path); + } + + public void copyTo(ByHandleFileInformation byHandleFileInformation, Path pathOverride) { + toByHandleFileInformation(pathOverride).copyTo(byHandleFileInformation); //That's not the most efficient way to do this, but it's less prone to human error + } +} \ No newline at end of file diff --git a/src/main/java/dev/dokan/dokan_java/wrappers/FindFileInfo.java b/src/main/java/dev/dokan/dokan_java/wrappers/FindFileInfo.java new file mode 100644 index 0000000..60ebc35 --- /dev/null +++ b/src/main/java/dev/dokan/dokan_java/wrappers/FindFileInfo.java @@ -0,0 +1,147 @@ +package dev.dokan.dokan_java.wrappers; + +import com.sun.jna.platform.win32.WinBase; +import dev.dokan.dokan_java.DokanyUtils; +import dev.dokan.dokan_java.constants.EnumInteger; +import dev.dokan.dokan_java.constants.microsoft.FileAttribute; +import dev.dokan.dokan_java.constants.microsoft.MicrosoftReparsePointTag; +import dev.dokan.dokan_java.structure.EnumIntegerSet; + +import java.util.Objects; +import java.util.function.Function; + +/** + * [TO BE REPLACED WITH LICENSE NOTE] + */ +public class FindFileInfo extends AbstractFileInfo { + + private static final int MAX_ALTERNATIVE_NAME_LENGTH = 14; + private static final FileAttribute REPARSE_POINT_FLAG = FileAttribute.REPARSE_POINT; + private static final String REPARSE_POINT_NOT_SET_MESSAGE = + "Reparse point tags are disabled on this File(-Handle); Flag \"FileAttribute.REPARSE_POINT\" not set"; + private final int RESERVED_1_FIELD_DEFAULT = 0; + + private int reparsePointTag; + // private int reserved_1; //Not yet specified by Microsoft + private String fileName; + private String alternativeName; + + public FindFileInfo(EnumIntegerSet attributes) { + super(attributes); + } + + public FindFileInfo(int attributes) { + super(attributes); + } + + public int getReparsePointTagValueLeniently() { + return getReparsePointFlag() ? this.reparsePointTag : 0; + } + + public int getReparsePointTagValue() { + tryReparsePoint(); + return this.reparsePointTag; + } + + public void setReparsePointTagValue(int reparsePointTagValue) { + tryReparsePoint(); + this.reparsePointTag = reparsePointTagValue; + } + + public MicrosoftReparsePointTag getMSReparsePointTag() { + return getReparsePointTag(MicrosoftReparsePointTag.values()); + } + + public void setMSReparsePointTag(MicrosoftReparsePointTag tag) { + setReparsePointTag(tag); + } + + public T getReparsePointTag(T[] possibleValues) { + return EnumInteger.enumFromInt(getReparsePointTagValue(), possibleValues); + } + + public void setReparsePointTag(T tag) { + setReparsePointTagValue(tag.getMask()); + } + + public > T getReparsePointTag(Function parser) { + return parser.apply(getReparsePointTagValue()); + } + + public > void setReparsePointTag(T tag, Function parser) { + setReparsePointTagValue(parser.apply(tag)); + } + + public boolean getReparsePointFlag() { + return getFlag(REPARSE_POINT_FLAG); + } + + public boolean setReparsePointFlag() { + return updateReparsePointFlag(true); + } + + public boolean unsetReparsePointFlag() { + return updateReparsePointFlag(false); + } + + public boolean updateReparsePointFlag(boolean value) { + /* + * The previous tag value is not discarded when updating the flag to false. + * But: The tag value will only be present in the WIN32_FIND_DATA if the flag is set to true. + * The following code could change that behavior. + * + * if(value && (prevValueFromUpdate != value)) { setReparsePointTagValue(0); } + */ + return updateFlag(REPARSE_POINT_FLAG, value); + } + + public String getFileName() { + return this.fileName; + } + + public void setFileName(String fileName) { + this.fileName = Objects.requireNonNullElse(fileName, ""); + } + + public String getAlternativeName() { + return this.alternativeName; + } + + public void setAlternativeName(String alternativeName) { + alternativeName = Objects.requireNonNullElse(alternativeName, ""); + if(alternativeName.length() > 14) { + throw new IllegalArgumentException("Alternative name must not be longer than 14 chars"); + } + this.alternativeName = alternativeName; + } + + public String setAlternativeNameLeniently(String alternativeName) { + alternativeName = Objects.requireNonNullElse(alternativeName, ""); + this.alternativeName = alternativeName.substring(0, MAX_ALTERNATIVE_NAME_LENGTH); + + return this.alternativeName; + } + + public WinBase.WIN32_FIND_DATA toWIN32_FIND_DATA() { + long fileSize = getFileSize(); + int fileSizeHigh = (int) (fileSize >> 32 & 0xffffffffL); + int fileSizeLow = (int) (fileSize & 0xffffffffL); + + return new WinBase.WIN32_FIND_DATA(getFlags(), + DokanyUtils.toFILETIME(getCreationTime()), + DokanyUtils.toFILETIME(getLastAccessTime()), + DokanyUtils.toFILETIME(getLastWriteTime()), + fileSizeHigh, + fileSizeLow, + getReparsePointTagValueLeniently(), + this.RESERVED_1_FIELD_DEFAULT, + this.fileName.toCharArray(), + this.alternativeName.toCharArray()); + } + + private void tryReparsePoint() { + if(!getReparsePointFlag()) { + throw new IllegalStateException(REPARSE_POINT_NOT_SET_MESSAGE); + } + } +} \ No newline at end of file