termux-app/termux-shared/src/main/java/com/termux/shared/file/filesystem/FileTypes.java

117 lines
6.2 KiB
Java

package com.termux.shared.file.filesystem;
import android.system.Os;
import androidx.annotation.NonNull;
import com.termux.shared.logger.Logger;
import java.io.File;
public class FileTypes {
/** Flags to represent regular, directory and symlink file types defined by {@link FileType} */
public static final int FILE_TYPE_NORMAL_FLAGS = FileType.REGULAR.getValue() | FileType.DIRECTORY.getValue() | FileType.SYMLINK.getValue();
/** Flags to represent any file type defined by {@link FileType} */
public static final int FILE_TYPE_ANY_FLAGS = Integer.MAX_VALUE; // 1111111111111111111111111111111 (31 1's)
public static String convertFileTypeFlagsToNamesString(int fileTypeFlags) {
StringBuilder fileTypeFlagsStringBuilder = new StringBuilder();
FileType[] fileTypes = {FileType.REGULAR, FileType.DIRECTORY, FileType.SYMLINK, FileType.CHARACTER, FileType.FIFO, FileType.BLOCK, FileType.UNKNOWN};
for (FileType fileType : fileTypes) {
if ((fileTypeFlags & fileType.getValue()) > 0)
fileTypeFlagsStringBuilder.append(fileType.getName()).append(",");
}
String fileTypeFlagsString = fileTypeFlagsStringBuilder.toString();
if (fileTypeFlagsString.endsWith(","))
fileTypeFlagsString = fileTypeFlagsString.substring(0, fileTypeFlagsString.lastIndexOf(","));
return fileTypeFlagsString;
}
/**
* Checks the type of file that exists at {@code filePath}.
*
* Returns:
* - {@link FileType#NO_EXIST} if {@code filePath} is {@code null}, empty, an exception is raised
* or no file exists at {@code filePath}.
* - {@link FileType#REGULAR} if file at {@code filePath} is a regular file.
* - {@link FileType#DIRECTORY} if file at {@code filePath} is a directory file.
* - {@link FileType#SYMLINK} if file at {@code filePath} is a symlink file and {@code followLinks} is {@code false}.
* - {@link FileType#CHARACTER} if file at {@code filePath} is a character special file.
* - {@link FileType#FIFO} if file at {@code filePath} is a fifo special file.
* - {@link FileType#BLOCK} if file at {@code filePath} is a block special file.
* - {@link FileType#UNKNOWN} if file at {@code filePath} is of unknown type.
*
* The {@link File#isFile()} and {@link File#isDirectory()} uses {@link Os#stat(String)} system
* call (not {@link Os#lstat(String)}) to check file type and does follow symlinks.
*
* The {@link File#exists()} uses {@link Os#access(String, int)} system call to check if file is
* accessible and does not follow symlinks. However, it returns {@code false} for dangling symlinks,
* on android at least. Check https://stackoverflow.com/a/57747064/14686958
*
* Basically {@link File} API is not reliable to check for symlinks.
*
* So we get the file type directly with {@link Os#lstat(String)} if {@code followLinks} is
* {@code false} and {@link Os#stat(String)} if {@code followLinks} is {@code true}. All exceptions
* are assumed as non-existence.
*
* The {@link org.apache.commons.io.FileUtils#isSymlink(File)} can also be used for checking
* symlinks but {@link FileAttributes} will provide access to more attributes if necessary,
* including getting other special file types considering that {@link File#exists()} can't be
* used to reliably check for non-existence and exclude the other 3 file types. commons.io is
* also not compatible with android < 8 for many things.
*
* https://cs.android.com/android/platform/superproject/+/android-11.0.0_r3:libcore/ojluni/src/main/java/java/io/File.java;l=793
* https://cs.android.com/android/platform/superproject/+/android-11.0.0_r3:libcore/ojluni/src/main/java/java/io/UnixFileSystem.java;l=248
* https://cs.android.com/android/platform/superproject/+/android-11.0.0_r3:libcore/ojluni/src/main/native/UnixFileSystem_md.c;l=121
* https://cs.android.com/android/_/android/platform/libcore/+/001ac51d61ad7443ba518bf2cf7e086efe698c6d
* https://cs.android.com/android/platform/superproject/+/android-11.0.0_r3:libcore/luni/src/main/java/libcore/io/Os.java;l=51
* https://cs.android.com/android/platform/superproject/+/android-11.0.0_r3:libcore/luni/src/main/java/libcore/io/Libcore.java;l=45
* https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/app/ActivityThread.java;l=7530
*
* @param filePath The {@code path} for file to check.
* @param followLinks The {@code boolean} that decides if symlinks will be followed while
* finding type. If set to {@code true}, then type of symlink target will
* be returned if file at {@code filePath} is a symlink. If set to
* {@code false}, then type of file at {@code filePath} itself will be
* returned.
* @return Returns the {@link FileType} of file.
*/
public static FileType getFileType(final String filePath, final boolean followLinks) {
if (filePath == null || filePath.isEmpty()) return FileType.NO_EXIST;
try {
FileAttributes fileAttributes = FileAttributes.get(filePath, followLinks);
return getFileType(fileAttributes);
} catch (Exception e) {
// If not a ENOENT (No such file or directory) exception
if (!e.getMessage().contains("ENOENT"))
Logger.logError("Failed to get file type for file at path \"" + filePath + "\": " + e.getMessage());
return FileType.NO_EXIST;
}
}
public static FileType getFileType(@NonNull final FileAttributes fileAttributes) {
if (fileAttributes.isRegularFile())
return FileType.REGULAR;
else if (fileAttributes.isDirectory())
return FileType.DIRECTORY;
else if (fileAttributes.isSymbolicLink())
return FileType.SYMLINK;
else if (fileAttributes.isCharacter())
return FileType.CHARACTER;
else if (fileAttributes.isFifo())
return FileType.FIFO;
else if (fileAttributes.isBlock())
return FileType.BLOCK;
else
return FileType.UNKNOWN;
}
}