Add FileAccess class for filesystem access
This commit is contained in:
parent
d3a01c2c66
commit
ed1c8b9d3e
3 changed files with 926 additions and 1 deletions
|
@ -49,6 +49,7 @@ import org.reflections.scanners.Scanners;
|
||||||
import org.reflections.util.ClasspathHelper;
|
import org.reflections.util.ClasspathHelper;
|
||||||
import org.reflections.util.ConfigurationBuilder;
|
import org.reflections.util.ConfigurationBuilder;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -257,7 +258,7 @@ public final class Engine extends SubsystemClass {
|
||||||
*
|
*
|
||||||
* @since v1-alpha0
|
* @since v1-alpha0
|
||||||
*/
|
*/
|
||||||
private void initializeClasses() {
|
private void initializeClasses() throws IOException {
|
||||||
LOGGER.verb("Initializing engine classes");
|
LOGGER.verb("Initializing engine classes");
|
||||||
|
|
||||||
// Initialize essential engine classes
|
// Initialize essential engine classes
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
* STAROPENSOURCE ENGINE SOURCE FILE
|
||||||
|
* Copyright (c) 2024 The StarOpenSource Engine Authors
|
||||||
|
* Licensed under the GNU Affero General Public License v3
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package de.staropensource.engine.base.type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents various file types.
|
||||||
|
*
|
||||||
|
* @since v1-alpha8
|
||||||
|
*/
|
||||||
|
public enum FileType {
|
||||||
|
/**
|
||||||
|
* The path does not exist.
|
||||||
|
*
|
||||||
|
* @since v1-alpha8
|
||||||
|
*/
|
||||||
|
VOID,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* It's a regular file.
|
||||||
|
*
|
||||||
|
* @since v1-alpha8
|
||||||
|
*/
|
||||||
|
FILE,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* It's a directory containing files.
|
||||||
|
*
|
||||||
|
* @since v1-alpha8
|
||||||
|
*/
|
||||||
|
DIRECTORY,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The file type is unknown to the sos!engine.
|
||||||
|
*
|
||||||
|
* @since v1-alpha8
|
||||||
|
*/
|
||||||
|
UNKNOWN
|
||||||
|
}
|
|
@ -0,0 +1,869 @@
|
||||||
|
/*
|
||||||
|
* STAROPENSOURCE ENGINE SOURCE FILE
|
||||||
|
* Copyright (c) 2024 The StarOpenSource Engine Authors
|
||||||
|
* Licensed under the GNU Affero General Public License v3
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package de.staropensource.engine.base.utility;
|
||||||
|
|
||||||
|
import de.staropensource.engine.base.Engine;
|
||||||
|
import de.staropensource.engine.base.logging.LoggerInstance;
|
||||||
|
import de.staropensource.engine.base.type.EngineState;
|
||||||
|
import de.staropensource.engine.base.type.FileType;
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.*;
|
||||||
|
import java.nio.file.attribute.PosixFilePermissions;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a simplified way of
|
||||||
|
* accessing files and directories.
|
||||||
|
*
|
||||||
|
* @since v1-alpha8
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@SuppressWarnings({ "JavadocDeclaration", "unused" })
|
||||||
|
public final class FileAccess {
|
||||||
|
// -----> Static variables
|
||||||
|
/**
|
||||||
|
* Contains the {@link LoggerInstance} for this instance.
|
||||||
|
*
|
||||||
|
* @see LoggerInstance
|
||||||
|
* @since v1-alpha8
|
||||||
|
*/
|
||||||
|
private static final @NotNull LoggerInstance LOGGER = new LoggerInstance.Builder().setClazz(FileAccess.class).setOrigin("ENGINE").build();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains a list of all files and directories
|
||||||
|
* which should be deleted at shutdown.
|
||||||
|
* <p>
|
||||||
|
* While this feature is built into Java, in
|
||||||
|
* our testing it did not seem to work correctly.
|
||||||
|
* That's why we're implementing it here.
|
||||||
|
*
|
||||||
|
* @since v1-alpha8
|
||||||
|
*/
|
||||||
|
private static @NotNull Path[] scheduledDeletion = new Path[0];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains a {@link FileAccess} instance to
|
||||||
|
* a cache directory provided by the engine.
|
||||||
|
*
|
||||||
|
* @since v1-alpha8
|
||||||
|
* -- GETTER --
|
||||||
|
* Returns a {@link FileAccess} instance to
|
||||||
|
* a cache directory provided by the engine.
|
||||||
|
*
|
||||||
|
* @return cache directory
|
||||||
|
* @since v1-alpha8
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
private static FileAccess cacheDirectory;
|
||||||
|
|
||||||
|
// -----> Instance variables
|
||||||
|
/**
|
||||||
|
* Contains the {@link LoggerInstance} for this instance.
|
||||||
|
*
|
||||||
|
* @see LoggerInstance
|
||||||
|
* @since v1-alpha8
|
||||||
|
*/
|
||||||
|
private final @NotNull LoggerInstance logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains the {@link Path} to this file or directory.
|
||||||
|
*
|
||||||
|
* @since v1-alpha8
|
||||||
|
* -- GETTER --
|
||||||
|
* Returns the {@link Path} to this file or directory.
|
||||||
|
* <p>
|
||||||
|
* Please only use this method when you have to.
|
||||||
|
* Use the methods in this class instead, if you can.
|
||||||
|
*
|
||||||
|
* @return associated {@link Path} instance
|
||||||
|
* @since v1-alpha8
|
||||||
|
*/
|
||||||
|
private final @NotNull Path path;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains the {@link File} to this file or directory.
|
||||||
|
*
|
||||||
|
* @since v1-alpha8
|
||||||
|
* -- GETTER --
|
||||||
|
* Returns the {@link File} to this file or directory.
|
||||||
|
* <p>
|
||||||
|
* Please only use this method when you have to.
|
||||||
|
* Use the methods in this class instead, if you can.
|
||||||
|
*
|
||||||
|
* @return associated {@link File} instance
|
||||||
|
* @since v1-alpha8
|
||||||
|
*/
|
||||||
|
private final @NotNull File file;
|
||||||
|
|
||||||
|
// -----> Constructors
|
||||||
|
/**
|
||||||
|
* Creates and initializes an instance of this class.
|
||||||
|
*
|
||||||
|
* @throws InvalidPathException if a {@link Path} cannot be created (see {@link FileSystem#getPath(String, String...)})
|
||||||
|
* @since v1-alpha8
|
||||||
|
*/
|
||||||
|
public FileAccess(@NotNull String pathString) throws InvalidPathException {
|
||||||
|
this.path = formatPath(pathString).toAbsolutePath();
|
||||||
|
this.file = new File(pathString);
|
||||||
|
logger = new LoggerInstance.Builder().setClazz(getClass()).setOrigin("ENGINE").setMetadata(path.toString()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and initializes an instance of this class.
|
||||||
|
*
|
||||||
|
* @since v1-alpha8
|
||||||
|
*/
|
||||||
|
public FileAccess(@NotNull Path path) {
|
||||||
|
this.path = path.toAbsolutePath();
|
||||||
|
this.file = this.path.toFile();
|
||||||
|
logger = new LoggerInstance.Builder().setClazz(getClass()).setOrigin("ENGINE").setMetadata(path.toString()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and initializes an instance of this class.
|
||||||
|
*
|
||||||
|
* @throws InvalidPathException if a {@link Path} cannot be created (see {@link FileSystem#getPath(String, String...)})
|
||||||
|
* @since v1-alpha8
|
||||||
|
*/
|
||||||
|
public FileAccess(@NotNull File file) throws InvalidPathException {
|
||||||
|
this.path = file.toPath().toAbsolutePath();
|
||||||
|
this.file = file;
|
||||||
|
logger = new LoggerInstance.Builder().setClazz(getClass()).setOrigin("ENGINE").setMetadata(path.toString()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and initializes an instance of this class.
|
||||||
|
*
|
||||||
|
* @throws FileSystemNotFoundException if the filesystem is not supported by Java
|
||||||
|
* @throws IllegalArgumentException if the URI is improperly formatted
|
||||||
|
* @since v1-alpha8
|
||||||
|
*/
|
||||||
|
public FileAccess(@NotNull URI uri) throws FileSystemNotFoundException, IllegalArgumentException {
|
||||||
|
this.path = Path.of(uri);
|
||||||
|
this.file = new File(uri);
|
||||||
|
logger = new LoggerInstance.Builder().setClazz(getClass()).setOrigin("ENGINE").setMetadata(path.toString()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// -----> Static instance initialization
|
||||||
|
/**
|
||||||
|
* Initializes all uninitialized static
|
||||||
|
* {@link FileAccess} instances.
|
||||||
|
*
|
||||||
|
* @since v1-alpha8
|
||||||
|
*/
|
||||||
|
public static void initializeInstances() throws IOException {
|
||||||
|
if (cacheDirectory == null) {
|
||||||
|
String temp = System.getProperty("os.name").toLowerCase(Locale.ROOT);
|
||||||
|
|
||||||
|
if (temp.contains("nix") || temp.contains("nux") || temp.contains("aix") || temp.contains("bsd"))
|
||||||
|
temp = "/tmp/";
|
||||||
|
else if (temp.contains("win"))
|
||||||
|
temp = System.getProperty("user.home").replace("\\", "/") + "/AppData/Local/Temp/";
|
||||||
|
else if (temp.contains("mac"))
|
||||||
|
LOGGER.crash("macOS is not supported yet");
|
||||||
|
else
|
||||||
|
LOGGER.crash("The operating system \"" + temp + "\" is not yet supported by the StarOpenSource Engine");
|
||||||
|
|
||||||
|
cacheDirectory = new FileAccess(temp + "sosengine-cache-" + ProcessHandle.current().pid()).createDirectory().deleteOnShutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Deletes all files scheduled for deletion.
|
||||||
|
* Only works during engine shutdown.
|
||||||
|
*
|
||||||
|
* @since v1-alpha8
|
||||||
|
*/
|
||||||
|
public static void deleteScheduled() {
|
||||||
|
if (Engine.getInstance().getState() == EngineState.SHUTDOWN || Engine.getInstance().getState() == EngineState.CRASHED) {
|
||||||
|
LOGGER.verb("Deleting files scheduled for deletion on shutdown");
|
||||||
|
|
||||||
|
for (Path path : scheduledDeletion)
|
||||||
|
try (Stream<Path> stream = Files.walk(path)) {
|
||||||
|
LOGGER.diag("Deleting file or directory \"" + path + "\"");
|
||||||
|
//noinspection ResultOfMethodCallIgnored
|
||||||
|
stream.sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete);
|
||||||
|
|
||||||
|
if (Files.exists(path))
|
||||||
|
LOGGER.error("Deleting file or directory \"" + path + "\" failed");
|
||||||
|
} catch (Exception exception) {
|
||||||
|
LOGGER.error("File or directory \"" + path + "\" could not be deleted\n" + Miscellaneous.getStackTraceHeader(exception) + "\n" + Miscellaneous.getStackTraceAsString(exception, true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// -----> Path formatting
|
||||||
|
/**
|
||||||
|
* Formats the path into a {@link Path} instance.
|
||||||
|
*
|
||||||
|
* @since v1-alpha8
|
||||||
|
*/
|
||||||
|
public static @NotNull Path formatPath(@NotNull String path) {
|
||||||
|
return Path.of(
|
||||||
|
path
|
||||||
|
.replace("/./", "/")
|
||||||
|
.replace("/", File.separator)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unformats the a {@link Path} instance into a string.
|
||||||
|
*
|
||||||
|
* @since v1-alpha8
|
||||||
|
*/
|
||||||
|
public static @NotNull String unformatPath(@NotNull Path path) {
|
||||||
|
return path
|
||||||
|
.toString()
|
||||||
|
.replace(File.separator, "/");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// -----> File getters & setters
|
||||||
|
/**
|
||||||
|
* Returns the absolute path of this file.
|
||||||
|
*
|
||||||
|
* @return absolute path
|
||||||
|
* @since v1-alpha8
|
||||||
|
*/
|
||||||
|
public @NotNull String getPath() {
|
||||||
|
return unformatPath(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the file name.
|
||||||
|
*
|
||||||
|
* @param excludeExtension if to remove the extension (e.g. {@code .txt}, {@code .java})
|
||||||
|
* @return file name
|
||||||
|
* @since v1-alpha8
|
||||||
|
*/
|
||||||
|
public @NotNull String getFileName(boolean excludeExtension) {
|
||||||
|
if (excludeExtension)
|
||||||
|
return file.getName().replaceFirst("[.][^.]+$", "");
|
||||||
|
else
|
||||||
|
return file.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether or not this file exists.
|
||||||
|
*
|
||||||
|
* @return exists?
|
||||||
|
* @since v1-alpha8
|
||||||
|
*/
|
||||||
|
public boolean exists() {
|
||||||
|
return Files.exists(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the type of this file.
|
||||||
|
*
|
||||||
|
* @return file type
|
||||||
|
* @since v1-alpha8
|
||||||
|
*/
|
||||||
|
public @NotNull FileType getType() {
|
||||||
|
if (!exists())
|
||||||
|
return FileType.VOID;
|
||||||
|
else if (Files.isRegularFile(path))
|
||||||
|
return FileType.FILE;
|
||||||
|
else if (Files.isDirectory(path))
|
||||||
|
return FileType.DIRECTORY;
|
||||||
|
else
|
||||||
|
return FileType.UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether or not this file is a symbolic link.
|
||||||
|
*
|
||||||
|
* @return symbolic link?
|
||||||
|
* @since v1-alpha8
|
||||||
|
*/
|
||||||
|
public boolean isSymbolicLink() {
|
||||||
|
return Files.isSymbolicLink(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether or not the file is hidden.
|
||||||
|
*
|
||||||
|
* @return is hidden?
|
||||||
|
* @throws IOException on an IO error
|
||||||
|
* @since v1-alpha8
|
||||||
|
*/
|
||||||
|
public boolean isHidden() throws IOException {
|
||||||
|
return Files.isHidden(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the names of all files and
|
||||||
|
* directories in this directory.
|
||||||
|
*
|
||||||
|
* @return array of file and directory names
|
||||||
|
* @throws UnsupportedOperationException if the file is not a directory
|
||||||
|
* @throws IOException on an IO error
|
||||||
|
* @since v1-alpha8
|
||||||
|
*/
|
||||||
|
public @NotNull String @NotNull [] listContents() throws UnsupportedOperationException, IOException {
|
||||||
|
if (getType() != FileType.DIRECTORY)
|
||||||
|
throw new UnsupportedOperationException("The file \"" + path + "\" is not a directory");
|
||||||
|
|
||||||
|
String[] list = file.list();
|
||||||
|
|
||||||
|
if (list == null)
|
||||||
|
throw new IOException("list is null");
|
||||||
|
else
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the destination of the symbolic link.
|
||||||
|
*
|
||||||
|
* @return file type
|
||||||
|
* @throws UnsupportedOperationException if the file is not a symbolic link
|
||||||
|
* @throws IOException on an IO error
|
||||||
|
* @since v1-alpha8
|
||||||
|
*/
|
||||||
|
public @NotNull String getLinkDestination() throws IOException {
|
||||||
|
if (!isSymbolicLink())
|
||||||
|
throw new UnsupportedOperationException("The file \"" + path + "\" is not a symbolic link");
|
||||||
|
return unformatPath(Files.readSymbolicLink(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----> Permissions
|
||||||
|
/**
|
||||||
|
* Returns whether or not the file can be read from.
|
||||||
|
*
|
||||||
|
* @return can be read?
|
||||||
|
* @since v1-alpha8
|
||||||
|
*/
|
||||||
|
public boolean isReadable() {
|
||||||
|
return Files.isReadable(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether or not the file can be written to.
|
||||||
|
*
|
||||||
|
* @return can be written?
|
||||||
|
* @since v1-alpha8
|
||||||
|
*/
|
||||||
|
public boolean isWritable() {
|
||||||
|
return Files.isWritable(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether or not the file can be executed.
|
||||||
|
*
|
||||||
|
* @return can be executed?
|
||||||
|
* @since v1-alpha8
|
||||||
|
*/
|
||||||
|
public boolean isExecutable() {
|
||||||
|
return Files.isExecutable(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the file's permissions the
|
||||||
|
* POSIX {@code rwxrwxrwx} format.
|
||||||
|
*
|
||||||
|
* @return POSIX permissions format
|
||||||
|
* @since v1-alpha8
|
||||||
|
*/
|
||||||
|
public @NotNull String getPosixPermissions() throws IOException {
|
||||||
|
try {
|
||||||
|
return PosixFilePermissions.toString(Files.getPosixFilePermissions(path));
|
||||||
|
} catch (UnsupportedOperationException exception) {
|
||||||
|
// POSIX permissions are not supported
|
||||||
|
// For the Macrohard Windoze users under us
|
||||||
|
StringBuilder output = new StringBuilder();
|
||||||
|
|
||||||
|
if (isReadable())
|
||||||
|
output.append("r");
|
||||||
|
if (isWritable())
|
||||||
|
output.append("w");
|
||||||
|
if (isExecutable())
|
||||||
|
output.append("x");
|
||||||
|
|
||||||
|
// Repeat the same thing two times
|
||||||
|
output.repeat(output, 2);
|
||||||
|
|
||||||
|
return output.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the file's permissions the
|
||||||
|
* POSIX {@code rwxrwxrwx} format.
|
||||||
|
*
|
||||||
|
* @param permissions POSIX {@code rwxrwxrwx} permission format
|
||||||
|
* @return this instance
|
||||||
|
* @throws IllegalArgumentException if the format of the {@code permissions} argument is invalid
|
||||||
|
* @throws IOException on an IO error
|
||||||
|
* @since v1-alpha8
|
||||||
|
*/
|
||||||
|
@SuppressWarnings({ "RegExpSingleCharAlternation", "ResultOfMethodCallIgnored" })
|
||||||
|
public @NotNull FileAccess setPosixPermissions(@NotNull String permissions) throws IllegalArgumentException, IOException {
|
||||||
|
if (
|
||||||
|
permissions.length() != 9
|
||||||
|
|| !permissions.matches("^(r|-)(w|-)(x|-)(r|-)(w|-)(x|-)(r|-)(w|-)(x|-)$")
|
||||||
|
)
|
||||||
|
throw new IllegalArgumentException("Invalid permission format: " + permissions);
|
||||||
|
|
||||||
|
try {
|
||||||
|
logger.diag("Setting POSIX file permissions for \"" + path + "\" to '" + permissions + "'");
|
||||||
|
Files.setPosixFilePermissions(path, PosixFilePermissions.fromString(permissions));
|
||||||
|
} catch (UnsupportedOperationException exception) {
|
||||||
|
logger.diag("Setting POSIX file permissions for \"" + path + "\" to '" + permissions.substring(0, 2) + "'");
|
||||||
|
char @NotNull [] chars = permissions.toCharArray();
|
||||||
|
|
||||||
|
for (int permission = 0; permission < 3; permission++) {
|
||||||
|
boolean enabled = chars[permission] != '-';
|
||||||
|
|
||||||
|
switch (permission) {
|
||||||
|
case 0 -> file.setReadable(enabled);
|
||||||
|
case 1 -> file.setWritable(enabled);
|
||||||
|
case 2 -> file.setExecutable(enabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// -----> Filesystem information
|
||||||
|
/**
|
||||||
|
* Returns the filesystem of this file.
|
||||||
|
*
|
||||||
|
* @return filesystem
|
||||||
|
* @since v1-alpha8
|
||||||
|
*/
|
||||||
|
public @NotNull FileSystem getFilesystem() {
|
||||||
|
return path.getFileSystem();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Returns whether or not the filesystem is POSIX-compliant.
|
||||||
|
*
|
||||||
|
* @return POSIX compliant?
|
||||||
|
* @since v1-alpha8
|
||||||
|
*/
|
||||||
|
public boolean isFilesystemPosixCompliant() {
|
||||||
|
return path.getFileSystem().supportedFileAttributeViews().contains("posix");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all forbidden file names.
|
||||||
|
* <p>
|
||||||
|
* The required functionality is not yet
|
||||||
|
* implemented. As such, this method
|
||||||
|
* will just return an empty array.
|
||||||
|
*
|
||||||
|
* @return forbidden file names
|
||||||
|
* @since v1-alpha8
|
||||||
|
*/
|
||||||
|
@ApiStatus.Experimental
|
||||||
|
public @NotNull String @NotNull [] getRestrictedFileNames() {
|
||||||
|
return new String[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all forbidden characters in file names.
|
||||||
|
* <p>
|
||||||
|
* The required functionality is not yet
|
||||||
|
* implemented. As such, this method
|
||||||
|
* will just return an empty array.
|
||||||
|
*
|
||||||
|
* @return forbidden characters
|
||||||
|
* @since v1-alpha8
|
||||||
|
*/
|
||||||
|
@ApiStatus.Experimental
|
||||||
|
public char @NotNull [] getRestrictedCharacters() {
|
||||||
|
return new char[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// -----> Directory traversal
|
||||||
|
/**
|
||||||
|
* Returns the parent directory.
|
||||||
|
*
|
||||||
|
* @return new {@link FileAccess} instance to the parent directory
|
||||||
|
* @since v1-alpha8
|
||||||
|
*/
|
||||||
|
public @NotNull FileAccess parent() {
|
||||||
|
return new FileAccess(path.getParent());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Traverses through directories and files.
|
||||||
|
*
|
||||||
|
* @param path path to traverse
|
||||||
|
* @return new {@link FileAccess} instance
|
||||||
|
* @since v1-alpha8
|
||||||
|
*/
|
||||||
|
public @NotNull FileAccess traverse(@NotNull String path) {
|
||||||
|
return new FileAccess(this.path.resolve(formatPath(path)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Traverses through directories and files.
|
||||||
|
*
|
||||||
|
* @param path path to traverse
|
||||||
|
* @return new {@link FileAccess} instance
|
||||||
|
* @throws FileNotFoundException if the specified path does not exist
|
||||||
|
* @since v1-alpha8
|
||||||
|
*/
|
||||||
|
public @NotNull FileAccess traverseIfExists(@NotNull String path) throws FileNotFoundException {
|
||||||
|
Path pathResolved = this.path.resolve(formatPath(path));
|
||||||
|
|
||||||
|
if (!Files.exists(pathResolved))
|
||||||
|
throw new FileNotFoundException("Traversal failed as relative path \"" + path + "\" does not exist from absolute path \"" + path + "\"");
|
||||||
|
|
||||||
|
return new FileAccess(pathResolved);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// -----> File/Directory creation and deletion
|
||||||
|
/**
|
||||||
|
* Deletes the file.
|
||||||
|
* If it doesn't exist, nothing will be done.
|
||||||
|
*
|
||||||
|
* @return this instance
|
||||||
|
* @since v1-alpha8
|
||||||
|
*/
|
||||||
|
public @NotNull FileAccess delete() {
|
||||||
|
if (exists()) {
|
||||||
|
logger.diag("Deleting \"" + path + "\"");
|
||||||
|
//noinspection ResultOfMethodCallIgnored
|
||||||
|
file.delete();
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks this file for deletion at engine shutdown.
|
||||||
|
*
|
||||||
|
* @return this instance
|
||||||
|
* @see Engine#shutdown()
|
||||||
|
* @see Engine#shutdown(int)
|
||||||
|
* @since v1-alpha8
|
||||||
|
*/
|
||||||
|
public @NotNull FileAccess deleteOnShutdown() {
|
||||||
|
logger.diag("Marking \"" + path + "\" for deletion at engine shutdown");
|
||||||
|
|
||||||
|
// Append path to scheduledDeletion array
|
||||||
|
List<@NotNull Path> scheduledDeletionList = new ArrayList<>(Arrays.stream(scheduledDeletion).toList());
|
||||||
|
scheduledDeletionList.add(path);
|
||||||
|
scheduledDeletion = scheduledDeletionList.toArray(new Path[0]);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the file.
|
||||||
|
* If it already exists, nothing will be done.
|
||||||
|
*
|
||||||
|
* @return this instance
|
||||||
|
* @throws IOException on an IO error
|
||||||
|
* @since v1-alpha8
|
||||||
|
*/
|
||||||
|
@SuppressWarnings({"UnusedReturnValue", "ResultOfMethodCallIgnored"})
|
||||||
|
public @NotNull FileAccess createFile() throws IOException {
|
||||||
|
if (!exists()) {
|
||||||
|
logger.diag("Creating a file at \"" + path + "\"");
|
||||||
|
file.getParentFile().mkdirs();
|
||||||
|
file.createNewFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the directory recursively.
|
||||||
|
* If it already exists, nothing will be done.
|
||||||
|
*
|
||||||
|
* @return this instance
|
||||||
|
* @throws IOException on an IO error
|
||||||
|
* @since v1-alpha8
|
||||||
|
*/
|
||||||
|
public @NotNull FileAccess createDirectory() throws IOException {
|
||||||
|
if (!exists()) {
|
||||||
|
logger.diag("Creating a directory at \"" + path + "\"");
|
||||||
|
if (!file.mkdirs())
|
||||||
|
throw new IOException("Creating directory \"" + path + "\" recursively failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a symbolic link at this location.
|
||||||
|
* If it already exists, nothing will be done.
|
||||||
|
*
|
||||||
|
* @param hard creates a hard link if {@code true} or a symbolic link if {@code false}
|
||||||
|
* @return this instance
|
||||||
|
* @throws IOException on an IO error
|
||||||
|
* @since v1-alpha8
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("UnusedReturnValue")
|
||||||
|
public @NotNull FileAccess createLink(boolean hard, @NotNull String destination) throws IOException {
|
||||||
|
if (!exists()) {
|
||||||
|
logger.diag("Creating a " + (hard ? "hard" : "symbolic") + " link at \"" + path + "\"");
|
||||||
|
|
||||||
|
if (hard)
|
||||||
|
Files.createLink(path, formatPath(destination));
|
||||||
|
else
|
||||||
|
Files.createSymbolicLink(path, formatPath(destination));
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// -----> File locking
|
||||||
|
/**
|
||||||
|
* Returns whether or not this file is locked.
|
||||||
|
*
|
||||||
|
* @return is locked?
|
||||||
|
* @since v1-alpha8
|
||||||
|
*/
|
||||||
|
public boolean isLocked() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Locks this file.
|
||||||
|
*
|
||||||
|
* @return this instance
|
||||||
|
* @since v1-alpha8
|
||||||
|
*/
|
||||||
|
public @NotNull FileAccess lock() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unlocks this file.
|
||||||
|
*
|
||||||
|
* @return this instance
|
||||||
|
* @since v1-alpha8
|
||||||
|
*/
|
||||||
|
public @NotNull FileAccess unlock() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// -----> Content reading
|
||||||
|
/**
|
||||||
|
* Returns the contents of this file.
|
||||||
|
* <p>
|
||||||
|
* Returns an empty array if this file
|
||||||
|
* is not of type {@link FileType#FILE}.
|
||||||
|
*
|
||||||
|
* @return file contents in bytes
|
||||||
|
* @throws IOException on an IO error
|
||||||
|
* @throws OutOfMemoryError if the file is larger than the allocated amount of memory
|
||||||
|
* @since v1-alpha8
|
||||||
|
*/
|
||||||
|
public byte @NotNull [] readBytes() throws IOException, OutOfMemoryError {
|
||||||
|
if (getType() != FileType.FILE)
|
||||||
|
return new byte[0];
|
||||||
|
|
||||||
|
logger.diag("Reading file \"" + path + "\" (bytes)");
|
||||||
|
return Files.readAllBytes(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the contents of this file.
|
||||||
|
* <p>
|
||||||
|
* Returns an empty list if this file
|
||||||
|
* is not of type {@link FileType#FILE}.
|
||||||
|
*
|
||||||
|
* @return file contents in bytes
|
||||||
|
* @throws IOException on an IO error
|
||||||
|
* @throws OutOfMemoryError if the file is larger than the allocated amount of memory
|
||||||
|
* @since v1-alpha8
|
||||||
|
*/
|
||||||
|
public @NotNull List<@NotNull String> readLines() throws IOException, OutOfMemoryError {
|
||||||
|
if (getType() != FileType.FILE)
|
||||||
|
return new ArrayList<>();
|
||||||
|
|
||||||
|
logger.diag("Reading file \"" + path + "\" (lines)");
|
||||||
|
return Files.readAllLines(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the contents of this file.
|
||||||
|
* This method will decode the bytes using the
|
||||||
|
* {@link StandardCharsets#UTF_8} character set.
|
||||||
|
* <p>
|
||||||
|
* Returns an empty string if this file
|
||||||
|
* is not of type {@link FileType#FILE}.
|
||||||
|
*
|
||||||
|
* @return file contents as a string
|
||||||
|
* @throws IOException on an IO error
|
||||||
|
* @throws OutOfMemoryError if the file is larger than the allocated amount of memory
|
||||||
|
* @since v1-alpha8
|
||||||
|
*/
|
||||||
|
public @NotNull String readContent() throws IOException, OutOfMemoryError {
|
||||||
|
return readContent(StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the contents of this file.
|
||||||
|
* <p>
|
||||||
|
* Returns an empty string if this file
|
||||||
|
* is not of type {@link FileType#FILE}.
|
||||||
|
*
|
||||||
|
* @param charset charset to decode the bytes with
|
||||||
|
* @return file contents as a string
|
||||||
|
* @throws IOException on an IO error
|
||||||
|
* @throws OutOfMemoryError if the file is larger than the allocated amount of memory
|
||||||
|
* @since v1-alpha8
|
||||||
|
*/
|
||||||
|
public @NotNull String readContent(@NotNull Charset charset) throws IOException, OutOfMemoryError {
|
||||||
|
if (getType() != FileType.FILE)
|
||||||
|
return "";
|
||||||
|
|
||||||
|
logger.diag("Reading file \"" + path + "\" (string)");
|
||||||
|
return Files.readString(path, charset);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// -----> Content writing
|
||||||
|
/**
|
||||||
|
* Writes the specified bytes into this file.
|
||||||
|
*
|
||||||
|
* @param bytes bytes to write
|
||||||
|
* @param async allows the operating system to decide when to flush the file to disk if {@code true}, flushes the data to disk immediately if {@code false}
|
||||||
|
* @throws UnsupportedOperationException if the type of this file is neither {@link FileType#VOID} or {@link FileType#FILE}
|
||||||
|
* @throws IOException on an IO error
|
||||||
|
* @return this instance
|
||||||
|
*/
|
||||||
|
public @NotNull FileAccess writeBytes(byte @NotNull [] bytes, boolean async) throws UnsupportedOperationException, IOException {
|
||||||
|
if (getType() == FileType.VOID)
|
||||||
|
createFile();
|
||||||
|
else if (getType() != FileType.FILE)
|
||||||
|
throw new UnsupportedOperationException("File \"" + path + "\" is not of type FileType.VOID or FileType.FILE");
|
||||||
|
|
||||||
|
createFile();
|
||||||
|
logger.diag("Writing file \"" + path + "\" (bytes, " + (async ? "async" : "dsync") + ")");
|
||||||
|
Files.write(path, bytes, StandardOpenOption.WRITE, async ? StandardOpenOption.DSYNC : StandardOpenOption.SYNC);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes the specified bytes into this file.
|
||||||
|
* This method will encode the string using the
|
||||||
|
* {@link StandardCharsets#UTF_8} character set.
|
||||||
|
*
|
||||||
|
* @param string string to write
|
||||||
|
* @param async allows the operating system to decide when to flush the file to disk if {@code true}, flushes the data to disk immediately if {@code false}
|
||||||
|
* @throws UnsupportedOperationException if the type of this file is neither {@link FileType#VOID} or {@link FileType#FILE}
|
||||||
|
* @throws IOException on an IO error
|
||||||
|
* @return this instance
|
||||||
|
*/
|
||||||
|
public @NotNull FileAccess writeString(@NotNull String string, boolean async) throws UnsupportedOperationException, IOException {
|
||||||
|
return writeString(string, StandardCharsets.UTF_8, async);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes the specified bytes into this file.
|
||||||
|
*
|
||||||
|
* @param string string to write
|
||||||
|
* @param charset charset to encode the string in
|
||||||
|
* @param async allows the operating system to decide when to flush the file to disk if {@code true}, flushes the data to disk immediately if {@code false}
|
||||||
|
* @throws UnsupportedOperationException if the type of this file is neither {@link FileType#VOID} or {@link FileType#FILE}
|
||||||
|
* @throws IOException on an IO error
|
||||||
|
* @return this instance
|
||||||
|
*/
|
||||||
|
public @NotNull FileAccess writeString(@NotNull String string, @NotNull Charset charset, boolean async) throws UnsupportedOperationException, IOException {
|
||||||
|
if (getType() == FileType.VOID)
|
||||||
|
createFile();
|
||||||
|
else if (getType() != FileType.FILE)
|
||||||
|
throw new UnsupportedOperationException("File \"" + path + "\" is not of type FileType.VOID or FileType.FILE");
|
||||||
|
|
||||||
|
logger.diag("Writing file \"" + path + "\" (string, " + (async ? "async" : "dsync") + ")");
|
||||||
|
Files.writeString(path, string, charset, StandardOpenOption.WRITE, async ? StandardOpenOption.DSYNC : StandardOpenOption.SYNC);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// -----> Content appending
|
||||||
|
/**
|
||||||
|
* Appends the specified bytes to this file.
|
||||||
|
*
|
||||||
|
* @param bytes bytes to append
|
||||||
|
* @param async allows the operating system to decide when to flush the file to disk if {@code true}, flushes the data to disk immediately if {@code false}
|
||||||
|
* @throws UnsupportedOperationException if the type of this file is neither {@link FileType#VOID} or {@link FileType#FILE}
|
||||||
|
* @throws IOException on an IO error
|
||||||
|
* @return this instance
|
||||||
|
*/
|
||||||
|
public @NotNull FileAccess appendBytes(byte @NotNull [] bytes, boolean async) throws UnsupportedOperationException, IOException {
|
||||||
|
if (getType() == FileType.VOID)
|
||||||
|
createFile();
|
||||||
|
else if (getType() != FileType.FILE)
|
||||||
|
throw new UnsupportedOperationException("File \"" + path + "\" is not of type FileType.VOID or FileType.FILE");
|
||||||
|
|
||||||
|
logger.diag("Appending file \"" + path + "\" (bytes, " + (async ? "async" : "dsync") + ")");
|
||||||
|
Files.write(path, bytes, StandardOpenOption.APPEND, async ? StandardOpenOption.DSYNC : StandardOpenOption.SYNC);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends the specified string to this file.
|
||||||
|
* This method will encode the string using the
|
||||||
|
* {@link StandardCharsets#UTF_8} character set.
|
||||||
|
*
|
||||||
|
* @param string string to append
|
||||||
|
* @param async allows the operating system to decide when to flush the file to disk if {@code true}, flushes the data to disk immediately if {@code false}
|
||||||
|
* @throws UnsupportedOperationException if the type of this file is neither {@link FileType#VOID} or {@link FileType#FILE}
|
||||||
|
* @throws IOException on an IO error
|
||||||
|
* @return this instance
|
||||||
|
*/
|
||||||
|
public @NotNull FileAccess appendString(@NotNull String string, boolean async) throws UnsupportedOperationException, IOException {
|
||||||
|
return appendString(string, StandardCharsets.UTF_8, async);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends the specified string to this file.
|
||||||
|
*
|
||||||
|
* @param string string to append
|
||||||
|
* @param charset charset to encode the string in
|
||||||
|
* @param async allows the operating system to decide when to flush the file to disk if {@code true}, flushes the data to disk immediately if {@code false}
|
||||||
|
* @throws UnsupportedOperationException if the type of this file is neither {@link FileType#VOID} or {@link FileType#FILE}
|
||||||
|
* @throws IOException on an IO error
|
||||||
|
* @return this instance
|
||||||
|
*/
|
||||||
|
public @NotNull FileAccess appendString(@NotNull String string, @NotNull Charset charset, boolean async) throws UnsupportedOperationException, IOException {
|
||||||
|
if (getType() == FileType.VOID)
|
||||||
|
createFile();
|
||||||
|
else if (getType() != FileType.FILE)
|
||||||
|
throw new UnsupportedOperationException("File \"" + path + "\" is not of type FileType.VOID or FileType.FILE");
|
||||||
|
|
||||||
|
logger.diag("Appending file \"" + path + "\" (string, " + (async ? "async" : "dsync") + ")");
|
||||||
|
Files.writeString(path, string, charset, StandardOpenOption.APPEND, async ? StandardOpenOption.DSYNC : StandardOpenOption.SYNC);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue