CC-Tweaked/projects/core/src/main/java/dan200/computercraft/core/filesystem/FileMount.java

146 lines
5.1 KiB
Java

/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.filesystem;
import dan200.computercraft.api.filesystem.FileAttributes;
import dan200.computercraft.api.filesystem.FileOperationException;
import dan200.computercraft.api.filesystem.Mount;
import java.io.IOException;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.FileSystemException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Collections;
import java.util.List;
import java.util.Set;
/**
* A {@link Mount} implementation which provides read-only access to a directory.
*/
public class FileMount implements Mount {
private static final Set<OpenOption> READ_OPTIONS = Collections.singleton(StandardOpenOption.READ);
protected final Path root;
public FileMount(Path root) {
this.root = root;
}
/**
* Resolve a mount-relative path to one on the file system.
*
* @param path The path to resolve.
* @return The resolved path.
*/
protected Path resolvePath(String path) {
return root.resolve(path);
}
protected boolean created() {
return Files.exists(root);
}
@Override
public boolean exists(String path) {
return path.isEmpty() || Files.exists(resolvePath(path));
}
@Override
public boolean isDirectory(String path) {
return path.isEmpty() || Files.isDirectory(resolvePath(path));
}
@Override
public void list(String path, List<String> contents) throws FileOperationException {
if (path.isEmpty() && !created()) return;
try (var stream = Files.newDirectoryStream(resolvePath(path))) {
stream.forEach(x -> contents.add(x.getFileName().toString()));
} catch (IOException e) {
throw remapException(path, e);
}
}
@Override
public long getSize(String path) throws FileOperationException {
var attributes = getAttributes(path);
return attributes.isDirectory() ? 0 : attributes.size();
}
@Override
public BasicFileAttributes getAttributes(String path) throws FileOperationException {
if (path.isEmpty() && !created()) return new FileAttributes(true, 0);
try {
return Files.readAttributes(resolvePath(path), BasicFileAttributes.class);
} catch (IOException e) {
throw remapException(path, e);
}
}
@Override
public SeekableByteChannel openForRead(String path) throws FileOperationException {
var file = resolvePath(path);
if (!Files.isRegularFile(file)) throw new FileOperationException(path, "No such file");
try {
return Files.newByteChannel(file, READ_OPTIONS);
} catch (IOException e) {
throw remapException(path, e);
}
}
/**
* Remap a {@link IOException} to a friendlier {@link FileOperationException}.
*
* @param fallbackPath The path currently being operated on. This is used in errors when we cannot determine a more accurate path.
* @param exn The exception that occurred.
* @return The wrapped exception.
*/
protected FileOperationException remapException(String fallbackPath, IOException exn) {
return exn instanceof FileSystemException fsExn
? remapException(fallbackPath, fsExn)
: new FileOperationException(fallbackPath, exn.getMessage() == null ? "Operation failed" : exn.getMessage());
}
/**
* Remap a {@link FileSystemException} to a friendlier {@link FileOperationException}, attempting to remap the path
* provided.
*
* @param fallbackPath The path currently being operated on. This is used in errors when we cannot determine a more accurate path.
* @param exn The exception that occurred.
* @return The wrapped exception.
*/
protected FileOperationException remapException(String fallbackPath, FileSystemException exn) {
var reason = getReason(exn);
var failedFile = exn.getFile();
if (failedFile == null) return new FileOperationException(fallbackPath, reason);
var failedPath = Path.of(failedFile);
return failedPath.startsWith(root)
? new FileOperationException(root.relativize(failedPath).toString(), reason)
: new FileOperationException(fallbackPath, reason);
}
/**
* Get the user-friendly reason for a {@link FileSystemException}.
*
* @param exn The exception that occurred.
* @return The friendly reason for this exception.
*/
protected String getReason(FileSystemException exn) {
if (exn instanceof FileAlreadyExistsException) return "File exists";
if (exn instanceof NoSuchFileException) return "No such file";
if (exn instanceof NotDirectoryException) return "Not a directory";
if (exn instanceof AccessDeniedException) return "Access denied";
var reason = exn.getReason();
return reason != null ? reason.trim() : "Operation failed";
}
}