mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-10-26 11:27:38 +00:00
Always use raw bytes in file handles
Historically CC has supported two modes when working with file handles
(and HTTP requests):
- Text mode, which reads/write using UTF-8.
- Binary mode, which reads/writes the raw bytes.
However, this can be confusing at times. CC/Lua doesn't actually support
unicode, so any characters beyond the 0.255 range were replaced with
'?'. This meant that most of the time you were better off just using
binary mode.
This commit unifies text and binary mode - we now /always/ read the raw
bytes of the file, rather than converting to/from UTF-8. Binary mode now
only specifies whether handle.read() returns a number (and .write(123)
writes a byte rather than coercing to a string).
- Refactor the entire handle hierarchy. We now have an AbstractMount
base class, which has the concrete implementation of all methods. The
public-facing classes then re-export these methods by annotating
them with @LuaFunction.
These implementations are based on the
Binary{Readable,Writable}Handle classes. The Encoded{..}Handle
versions are now entirely removed.
- As we no longer need to use BufferedReader/BufferedWriter, we can
remove quite a lot of logic in Filesystem to handle wrapping
closeable objects.
- Add a new WritableMount.openFile method, which generalises
openForWrite/openForAppend to accept OpenOptions. This allows us to
support update mode (r+, w+) in fs.open.
- fs.open now uses the new handle types, and supports update (r+, w+)
mode.
- http.request now uses the new readable handle type. We no longer
encode the request body to UTF-8, nor decode the response from UTF-8.
- Websockets now return text frame's contents directly, rather than
converting it from UTF-8. Sending text frames now attempts to treat
the passed string as UTF-8, rather than treating it as latin1.
This commit is contained in:
@@ -7,7 +7,8 @@ package dan200.computercraft.api.filesystem;
|
||||
import javax.annotation.Nullable;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.nio.file.attribute.FileTime;
|
||||
import java.time.Instant;
|
||||
|
||||
import static dan200.computercraft.api.filesystem.MountConstants.EPOCH;
|
||||
|
||||
/**
|
||||
* A simple version of {@link BasicFileAttributes}, which provides what information a {@link Mount} already exposes.
|
||||
@@ -20,8 +21,6 @@ import java.time.Instant;
|
||||
public record FileAttributes(
|
||||
boolean isDirectory, long size, FileTime creationTime, FileTime lastModifiedTime
|
||||
) implements BasicFileAttributes {
|
||||
private static final FileTime EPOCH = FileTime.from(Instant.EPOCH);
|
||||
|
||||
/**
|
||||
* Create a new {@link FileAttributes} instance with the {@linkplain #creationTime() creation time} and
|
||||
* {@linkplain #lastModifiedTime() last modified time} set to the Unix epoch.
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.api.filesystem;
|
||||
|
||||
import java.nio.file.OpenOption;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.nio.file.attribute.FileTime;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Useful constants functions for working with mounts.
|
||||
*
|
||||
* @see Mount
|
||||
* @see WritableMount
|
||||
*/
|
||||
public final class MountConstants {
|
||||
/**
|
||||
* A {@link FileTime} set to the Unix EPOCH, intended for {@link BasicFileAttributes}'s file times.
|
||||
*/
|
||||
public static final FileTime EPOCH = FileTime.from(Instant.EPOCH);
|
||||
|
||||
/**
|
||||
* The minimum size of a file for file {@linkplain WritableMount#getCapacity() capacity calculations}.
|
||||
*/
|
||||
public static final long MINIMUM_FILE_SIZE = 500;
|
||||
|
||||
/**
|
||||
* The error message used when the file does not exist.
|
||||
*/
|
||||
public static final String NO_SUCH_FILE = "No such file";
|
||||
|
||||
/**
|
||||
* The error message used when trying to use a file as a directory (for instance when
|
||||
* {@linkplain Mount#list(String, List) listing its contents}).
|
||||
*/
|
||||
public static final String NOT_A_DIRECTORY = "Not a directory";
|
||||
|
||||
/**
|
||||
* The error message used when trying to use a directory as a file (for instance when
|
||||
* {@linkplain Mount#openForRead(String) opening for reading}).
|
||||
*/
|
||||
public static final String NOT_A_FILE = "Not a file";
|
||||
|
||||
/**
|
||||
* The error message used when attempting to modify a read-only file or mount.
|
||||
*/
|
||||
public static final String ACCESS_DENIED = "Access denied";
|
||||
|
||||
/**
|
||||
* The error message used when trying to overwrite a file (for instance when
|
||||
* {@linkplain WritableMount#rename(String, String) renaming files} or {@linkplain WritableMount#makeDirectory(String)
|
||||
* creating directories}).
|
||||
*/
|
||||
public static final String FILE_EXISTS = "File exists";
|
||||
|
||||
/**
|
||||
* The error message used when trying to {@linkplain WritableMount#openForWrite(String) opening a directory to read}.
|
||||
*/
|
||||
public static final String CANNOT_WRITE_TO_DIRECTORY = "Cannot write to directory";
|
||||
|
||||
/**
|
||||
* The error message used when the mount runs out of space.
|
||||
*/
|
||||
public static final String OUT_OF_SPACE = "Out of space";
|
||||
|
||||
/**
|
||||
* The error message to throw when an unsupported set of options were passed to
|
||||
* {@link WritableMount#openFile(String, Set)}.
|
||||
*/
|
||||
public static final String UNSUPPORTED_MODE = "Unsupported mode";
|
||||
|
||||
public static final Set<OpenOption> READ_OPTIONS = Set.of(StandardOpenOption.READ);
|
||||
|
||||
public static final Set<OpenOption> WRITE_OPTIONS = Set.of(StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
||||
|
||||
public static final Set<OpenOption> APPEND_OPTIONS = Set.of(StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.APPEND);
|
||||
|
||||
private MountConstants() {
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,13 @@ package dan200.computercraft.api.filesystem;
|
||||
import dan200.computercraft.api.peripheral.IComputerAccess;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
import java.nio.file.OpenOption;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.nio.file.attribute.FileAttribute;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents a part of a virtual filesystem that can be mounted onto a computer using {@link IComputerAccess#mount(String, Mount)}
|
||||
@@ -51,22 +56,51 @@ public interface WritableMount extends Mount {
|
||||
void rename(String source, String dest) throws IOException;
|
||||
|
||||
/**
|
||||
* Opens a file with a given path, and returns an {@link OutputStream} for writing to it.
|
||||
* Opens a file with a given path, and returns an {@link SeekableByteChannel} for writing to it.
|
||||
*
|
||||
* @param path A file path in normalised format, relative to the mount location. ie: "programs/myprogram".
|
||||
* @return A stream for writing to.
|
||||
* @return A channel for writing to.
|
||||
* @throws IOException If the file could not be opened for writing.
|
||||
* @deprecated Replaced with more the generic {@link #openFile(String, Set)}.
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
SeekableByteChannel openForWrite(String path) throws IOException;
|
||||
|
||||
/**
|
||||
* Opens a file with a given path, and returns an {@link OutputStream} for appending to it.
|
||||
* Opens a file with a given path, and returns an {@link SeekableByteChannel} for appending to it.
|
||||
*
|
||||
* @param path A file path in normalised format, relative to the mount location. ie: "programs/myprogram".
|
||||
* @return A stream for writing to.
|
||||
* @return A channel for writing to.
|
||||
* @throws IOException If the file could not be opened for writing.
|
||||
* @deprecated Replaced with more the generic {@link #openFile(String, Set)}.
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
SeekableByteChannel openForAppend(String path) throws IOException;
|
||||
|
||||
/**
|
||||
* Opens a file with a given path, and returns an {@link SeekableByteChannel}.
|
||||
* <p>
|
||||
* This allows opening a file in a variety of options, much like {@link FileChannel#open(Path, Set, FileAttribute[])}.
|
||||
* <p>
|
||||
* At minimum, the option sets {@link MountConstants#READ_OPTIONS}, {@link MountConstants#WRITE_OPTIONS} and
|
||||
* {@link MountConstants#APPEND_OPTIONS} should be supported. It is recommended any valid combination of
|
||||
* {@link StandardOpenOption#READ}, {@link StandardOpenOption#WRITE}, {@link StandardOpenOption#CREATE},
|
||||
* {@link StandardOpenOption#TRUNCATE_EXISTING} and {@link StandardOpenOption#APPEND} are supported.
|
||||
* <p>
|
||||
* Unsupported modes (or combinations of modes) should throw an exception with the message
|
||||
* {@link MountConstants#UNSUPPORTED_MODE "Unsupported mode"}.
|
||||
*
|
||||
* @param path A file path in normalised format, relative to the mount location. ie: "programs/myprogram".
|
||||
* @param options For options used for opening a file.
|
||||
* @return A channel for writing to.
|
||||
* @throws IOException If the file could not be opened for writing.
|
||||
*/
|
||||
SeekableByteChannel openForAppend(String path) throws IOException;
|
||||
default SeekableByteChannel openFile(String path, Set<OpenOption> options) throws IOException {
|
||||
if (options.equals(MountConstants.READ_OPTIONS)) return openForRead(path);
|
||||
if (options.equals(MountConstants.WRITE_OPTIONS)) return openForWrite(path);
|
||||
if (options.equals(MountConstants.APPEND_OPTIONS)) return openForAppend(path);
|
||||
throw new IOException(MountConstants.UNSUPPORTED_MODE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the amount of free space on the mount, in bytes. You should decrease this value as the user writes to the
|
||||
|
||||
@@ -195,6 +195,19 @@ public interface IArguments {
|
||||
return LuaValues.encode(getString(index));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the argument, converting it to the raw-byte representation of its string by following Lua conventions.
|
||||
* <p>
|
||||
* This is equivalent to {@link #getStringCoerced(int)}, but then
|
||||
*
|
||||
* @param index The argument number.
|
||||
* @return The argument's value. This is a <em>read only</em> buffer.
|
||||
* @throws LuaException If the argument cannot be converted to Java.
|
||||
*/
|
||||
default ByteBuffer getBytesCoerced(int index) throws LuaException {
|
||||
return LuaValues.encode(getStringCoerced(index));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a string argument as an enum value.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user