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