refactor: rework fs error system
This commit is contained in:
parent
cf99845f6b
commit
1aad92ecec
@ -10,12 +10,12 @@ import me.topchetoeu.jscript.utils.interop.WrapperName;
|
|||||||
|
|
||||||
@WrapperName("File")
|
@WrapperName("File")
|
||||||
public class FileLib {
|
public class FileLib {
|
||||||
public final File file;
|
public final File fd;
|
||||||
|
|
||||||
@Expose public PromiseLib __pointer(Arguments args) {
|
@Expose public PromiseLib __pointer(Arguments args) {
|
||||||
return PromiseLib.await(args.ctx, () -> {
|
return PromiseLib.await(args.ctx, () -> {
|
||||||
try {
|
try {
|
||||||
return file.seek(0, 1);
|
return fd.seek(0, 1);
|
||||||
}
|
}
|
||||||
catch (FilesystemException e) { throw e.toEngineException(); }
|
catch (FilesystemException e) { throw e.toEngineException(); }
|
||||||
});
|
});
|
||||||
@ -23,9 +23,9 @@ public class FileLib {
|
|||||||
@Expose public PromiseLib __length(Arguments args) {
|
@Expose public PromiseLib __length(Arguments args) {
|
||||||
return PromiseLib.await(args.ctx, () -> {
|
return PromiseLib.await(args.ctx, () -> {
|
||||||
try {
|
try {
|
||||||
long curr = file.seek(0, 1);
|
long curr = fd.seek(0, 1);
|
||||||
long res = file.seek(0, 2);
|
long res = fd.seek(0, 2);
|
||||||
file.seek(curr, 0);
|
fd.seek(curr, 0);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
catch (FilesystemException e) { throw e.toEngineException(); }
|
catch (FilesystemException e) { throw e.toEngineException(); }
|
||||||
@ -38,7 +38,7 @@ public class FileLib {
|
|||||||
try {
|
try {
|
||||||
var buff = new byte[n];
|
var buff = new byte[n];
|
||||||
var res = new ArrayValue();
|
var res = new ArrayValue();
|
||||||
int resI = file.read(buff);
|
int resI = fd.read(buff);
|
||||||
|
|
||||||
for (var i = resI - 1; i >= 0; i--) res.set(args.ctx, i, (int)buff[i]);
|
for (var i = resI - 1; i >= 0; i--) res.set(args.ctx, i, (int)buff[i]);
|
||||||
return res;
|
return res;
|
||||||
@ -53,7 +53,7 @@ public class FileLib {
|
|||||||
var res = new byte[val.size()];
|
var res = new byte[val.size()];
|
||||||
|
|
||||||
for (var i = 0; i < val.size(); i++) res[i] = (byte)Values.toNumber(args.ctx, val.get(i));
|
for (var i = 0; i < val.size(); i++) res[i] = (byte)Values.toNumber(args.ctx, val.get(i));
|
||||||
file.write(res);
|
fd.write(res);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -62,7 +62,7 @@ public class FileLib {
|
|||||||
}
|
}
|
||||||
@Expose public PromiseLib __close(Arguments args) {
|
@Expose public PromiseLib __close(Arguments args) {
|
||||||
return PromiseLib.await(args.ctx, () -> {
|
return PromiseLib.await(args.ctx, () -> {
|
||||||
file.close();
|
fd.close();
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -72,13 +72,13 @@ public class FileLib {
|
|||||||
var whence = args.getInt(1);
|
var whence = args.getInt(1);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return file.seek(ptr, whence);
|
return fd.seek(ptr, whence);
|
||||||
}
|
}
|
||||||
catch (FilesystemException e) { throw e.toEngineException(); }
|
catch (FilesystemException e) { throw e.toEngineException(); }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public FileLib(File file) {
|
public FileLib(File fd) {
|
||||||
this.file = file;
|
this.fd = fd;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,13 +8,14 @@ import me.topchetoeu.jscript.core.engine.Context;
|
|||||||
import me.topchetoeu.jscript.core.engine.values.ObjectValue;
|
import me.topchetoeu.jscript.core.engine.values.ObjectValue;
|
||||||
import me.topchetoeu.jscript.core.engine.values.Values;
|
import me.topchetoeu.jscript.core.engine.values.Values;
|
||||||
import me.topchetoeu.jscript.core.exceptions.EngineException;
|
import me.topchetoeu.jscript.core.exceptions.EngineException;
|
||||||
|
import me.topchetoeu.jscript.utils.filesystem.ActionType;
|
||||||
import me.topchetoeu.jscript.utils.filesystem.EntryType;
|
import me.topchetoeu.jscript.utils.filesystem.EntryType;
|
||||||
|
import me.topchetoeu.jscript.utils.filesystem.ErrorReason;
|
||||||
import me.topchetoeu.jscript.utils.filesystem.File;
|
import me.topchetoeu.jscript.utils.filesystem.File;
|
||||||
import me.topchetoeu.jscript.utils.filesystem.FileStat;
|
import me.topchetoeu.jscript.utils.filesystem.FileStat;
|
||||||
import me.topchetoeu.jscript.utils.filesystem.Filesystem;
|
import me.topchetoeu.jscript.utils.filesystem.Filesystem;
|
||||||
import me.topchetoeu.jscript.utils.filesystem.FilesystemException;
|
import me.topchetoeu.jscript.utils.filesystem.FilesystemException;
|
||||||
import me.topchetoeu.jscript.utils.filesystem.Mode;
|
import me.topchetoeu.jscript.utils.filesystem.Mode;
|
||||||
import me.topchetoeu.jscript.utils.filesystem.FilesystemException.FSCode;
|
|
||||||
import me.topchetoeu.jscript.utils.interop.Arguments;
|
import me.topchetoeu.jscript.utils.interop.Arguments;
|
||||||
import me.topchetoeu.jscript.utils.interop.Expose;
|
import me.topchetoeu.jscript.utils.interop.Expose;
|
||||||
import me.topchetoeu.jscript.utils.interop.ExposeField;
|
import me.topchetoeu.jscript.utils.interop.ExposeField;
|
||||||
@ -50,11 +51,10 @@ public class FilesystemLib {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (fs.stat(path).type != EntryType.FILE) {
|
if (fs.stat(path).type != EntryType.FILE) {
|
||||||
throw new FilesystemException(path, FSCode.NOT_FILE);
|
throw new FilesystemException(ErrorReason.DOESNT_EXIST, "Not a file").setAction(ActionType.OPEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
var file = fs.open(path, _mode);
|
return new FileLib(fs.open(path, _mode));
|
||||||
return new FileLib(file);
|
|
||||||
}
|
}
|
||||||
catch (FilesystemException e) { throw e.toEngineException(); }
|
catch (FilesystemException e) { throw e.toEngineException(); }
|
||||||
});
|
});
|
||||||
@ -75,7 +75,7 @@ public class FilesystemLib {
|
|||||||
var path = fs.normalize(args.getString(0));
|
var path = fs.normalize(args.getString(0));
|
||||||
|
|
||||||
if (fs.stat(path).type != EntryType.FOLDER) {
|
if (fs.stat(path).type != EntryType.FOLDER) {
|
||||||
throw new FilesystemException(path, FSCode.NOT_FOLDER);
|
throw new FilesystemException(ErrorReason.DOESNT_EXIST, "Not a directory").setAction(ActionType.OPEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
file = fs.open(path, Mode.READ);
|
file = fs.open(path, Mode.READ);
|
||||||
|
@ -152,6 +152,8 @@ public class Internals {
|
|||||||
glob.define(null, "Encoding", false, wp.getNamespace(EncodingLib.class));
|
glob.define(null, "Encoding", false, wp.getNamespace(EncodingLib.class));
|
||||||
glob.define(null, "Filesystem", false, wp.getNamespace(FilesystemLib.class));
|
glob.define(null, "Filesystem", false, wp.getNamespace(FilesystemLib.class));
|
||||||
|
|
||||||
|
glob.define(false, wp.getConstr(FileLib.class));
|
||||||
|
|
||||||
glob.define(false, wp.getConstr(DateLib.class));
|
glob.define(false, wp.getConstr(DateLib.class));
|
||||||
glob.define(false, wp.getConstr(ObjectLib.class));
|
glob.define(false, wp.getConstr(ObjectLib.class));
|
||||||
glob.define(false, wp.getConstr(FunctionLib.class));
|
glob.define(false, wp.getConstr(FunctionLib.class));
|
||||||
|
@ -24,6 +24,7 @@ import me.topchetoeu.jscript.utils.filesystem.MemoryFilesystem;
|
|||||||
import me.topchetoeu.jscript.utils.filesystem.Mode;
|
import me.topchetoeu.jscript.utils.filesystem.Mode;
|
||||||
import me.topchetoeu.jscript.utils.filesystem.PhysicalFilesystem;
|
import me.topchetoeu.jscript.utils.filesystem.PhysicalFilesystem;
|
||||||
import me.topchetoeu.jscript.utils.filesystem.RootFilesystem;
|
import me.topchetoeu.jscript.utils.filesystem.RootFilesystem;
|
||||||
|
import me.topchetoeu.jscript.utils.filesystem.STDFilesystem;
|
||||||
import me.topchetoeu.jscript.utils.modules.ModuleRepo;
|
import me.topchetoeu.jscript.utils.modules.ModuleRepo;
|
||||||
import me.topchetoeu.jscript.utils.permissions.PermissionsManager;
|
import me.topchetoeu.jscript.utils.permissions.PermissionsManager;
|
||||||
import me.topchetoeu.jscript.utils.permissions.PermissionsProvider;
|
import me.topchetoeu.jscript.utils.permissions.PermissionsProvider;
|
||||||
@ -108,6 +109,7 @@ public class JScriptRepl {
|
|||||||
var fs = new RootFilesystem(PermissionsProvider.get(environment));
|
var fs = new RootFilesystem(PermissionsProvider.get(environment));
|
||||||
fs.protocols.put("temp", new MemoryFilesystem(Mode.READ_WRITE));
|
fs.protocols.put("temp", new MemoryFilesystem(Mode.READ_WRITE));
|
||||||
fs.protocols.put("file", new PhysicalFilesystem("."));
|
fs.protocols.put("file", new PhysicalFilesystem("."));
|
||||||
|
fs.protocols.put("std", STDFilesystem.ofStd(System.in, System.out, System.err));
|
||||||
|
|
||||||
environment.add(PermissionsProvider.ENV_KEY, PermissionsManager.ALL_PERMS);
|
environment.add(PermissionsProvider.ENV_KEY, PermissionsManager.ALL_PERMS);
|
||||||
environment.add(Filesystem.ENV_KEY, fs);
|
environment.add(Filesystem.ENV_KEY, fs);
|
||||||
|
@ -0,0 +1,28 @@
|
|||||||
|
package me.topchetoeu.jscript.utils.filesystem;
|
||||||
|
|
||||||
|
public enum ActionType {
|
||||||
|
UNKNOWN(0, "An operation performed upon", "An operation was performed upon"),
|
||||||
|
READ(1, "Reading from", "Read from"),
|
||||||
|
WRITE(2, "Writting to", "Wrote to"),
|
||||||
|
SEEK(3, "Seeking in", "Sought in"),
|
||||||
|
CLOSE(4, "Closing", "Closed"),
|
||||||
|
STAT(5, "Stat of", "Statted"),
|
||||||
|
OPEN(6, "Opening", "Opened"),
|
||||||
|
CREATE(7, "Creating", "Created"),
|
||||||
|
DELETE(8, "Deleting", "Deleted"),
|
||||||
|
CLOSE_FS(9, "Closing filesystem", "Closed filesystem");
|
||||||
|
|
||||||
|
public final int code;
|
||||||
|
public final String continuous, past;
|
||||||
|
|
||||||
|
public String readable(boolean usePast) {
|
||||||
|
if (usePast) return past;
|
||||||
|
else return continuous;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ActionType(int code, String continuous, String past) {
|
||||||
|
this.code = code;
|
||||||
|
this.continuous = continuous;
|
||||||
|
this.past = past;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,59 @@
|
|||||||
|
package me.topchetoeu.jscript.utils.filesystem;
|
||||||
|
|
||||||
|
public abstract class BaseFile<T> implements File {
|
||||||
|
private T handle;
|
||||||
|
private Mode mode;
|
||||||
|
|
||||||
|
protected final T handle() {
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract int onRead(byte[] buff);
|
||||||
|
protected abstract void onWrite(byte[] buff);
|
||||||
|
protected abstract long onSeek(long offset, int pos);
|
||||||
|
protected abstract boolean onClose();
|
||||||
|
|
||||||
|
@Override public int read(byte[] buff) {
|
||||||
|
try {
|
||||||
|
if (handle == null) throw new FilesystemException(ErrorReason.CLOSED);
|
||||||
|
if (!mode.readable) throw new FilesystemException(ErrorReason.NO_PERMISSION, "File not open for reading.");
|
||||||
|
return onRead(buff);
|
||||||
|
}
|
||||||
|
catch (FilesystemException e) { throw e.setAction(ActionType.READ); }
|
||||||
|
}
|
||||||
|
@Override public void write(byte[] buff) {
|
||||||
|
try {
|
||||||
|
if (handle == null) throw new FilesystemException(ErrorReason.CLOSED);
|
||||||
|
if (!mode.writable) throw new FilesystemException(ErrorReason.NO_PERMISSION, "File not open for writting.");
|
||||||
|
onWrite(buff);
|
||||||
|
}
|
||||||
|
catch (FilesystemException e) { throw e.setAction(ActionType.WRITE); }
|
||||||
|
}
|
||||||
|
@Override public long seek(long offset, int pos) {
|
||||||
|
try {
|
||||||
|
if (handle == null) throw new FilesystemException(ErrorReason.CLOSED);
|
||||||
|
if (!mode.writable) throw new FilesystemException(ErrorReason.NO_PERMISSION, "File not open for seeking.");
|
||||||
|
return onSeek(offset, pos);
|
||||||
|
}
|
||||||
|
catch (FilesystemException e) { throw e.setAction(ActionType.SEEK); }
|
||||||
|
}
|
||||||
|
@Override public boolean close() {
|
||||||
|
if (handle != null) {
|
||||||
|
try {
|
||||||
|
var res = onClose();
|
||||||
|
handle = null;
|
||||||
|
mode = Mode.NONE;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
catch (FilesystemException e) { throw e.setAction(ActionType.CLOSE); }
|
||||||
|
}
|
||||||
|
else return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BaseFile(T handle, Mode mode) {
|
||||||
|
this.mode = mode;
|
||||||
|
this.handle = handle;
|
||||||
|
|
||||||
|
if (mode == Mode.NONE) this.handle = null;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
package me.topchetoeu.jscript.utils.filesystem;
|
||||||
|
|
||||||
|
public enum ErrorReason {
|
||||||
|
UNKNOWN(0, "failed", false),
|
||||||
|
NO_PERMISSION(1, "is not allowed", false),
|
||||||
|
CLOSED(1, "that was closed", true),
|
||||||
|
UNSUPPORTED(2, "is not supported", false),
|
||||||
|
ILLEGAL_ARGS(3, "with illegal arguments", true),
|
||||||
|
DOESNT_EXIST(4, "that doesn't exist", true),
|
||||||
|
ALREADY_EXISTS(5, "that already exists", true),
|
||||||
|
ILLEGAL_PATH(6, "with illegal path", true),
|
||||||
|
NO_PARENT(7, "with a missing parent folder", true);
|
||||||
|
|
||||||
|
public final int code;
|
||||||
|
public final boolean usePast;
|
||||||
|
public final String readable;
|
||||||
|
|
||||||
|
private ErrorReason(int code, String readable, boolean usePast) {
|
||||||
|
this.code = code;
|
||||||
|
this.readable = readable;
|
||||||
|
this.usePast = usePast;
|
||||||
|
}
|
||||||
|
}
|
@ -3,51 +3,42 @@ package me.topchetoeu.jscript.utils.filesystem;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
|
||||||
import me.topchetoeu.jscript.common.Buffer;
|
import me.topchetoeu.jscript.common.Buffer;
|
||||||
import me.topchetoeu.jscript.utils.LineWriter;
|
|
||||||
import me.topchetoeu.jscript.utils.filesystem.FilesystemException.FSCode;
|
|
||||||
import me.topchetoeu.jscript.utils.permissions.Permission;
|
|
||||||
import me.topchetoeu.jscript.utils.permissions.PermissionsProvider;
|
|
||||||
|
|
||||||
public interface File {
|
public interface File {
|
||||||
int read(byte[] buff);
|
default int read(byte[] buff) { throw new FilesystemException(ErrorReason.UNSUPPORTED).setAction(ActionType.READ); }
|
||||||
void write(byte[] buff);
|
default void write(byte[] buff) { throw new FilesystemException(ErrorReason.UNSUPPORTED).setAction(ActionType.WRITE); }
|
||||||
long seek(long offset, int pos);
|
default long seek(long offset, int pos) { throw new FilesystemException(ErrorReason.UNSUPPORTED).setAction(ActionType.SEEK); }
|
||||||
void close();
|
default boolean close() { return false; }
|
||||||
|
|
||||||
default File wrap(String name, PermissionsProvider perms, Permission read, Permission write, Permission seek, Permission close) {
|
default byte[] readAll() {
|
||||||
var self = this;
|
var parts = new LinkedList<byte[]>();
|
||||||
|
var buff = new byte[1024];
|
||||||
|
var size = 0;
|
||||||
|
|
||||||
return new File() {
|
while (true) {
|
||||||
@Override public int read(byte[] buff) {
|
var n = read(buff);
|
||||||
if (read != null && perms.hasPermission(read, name)) return self.read(buff);
|
if (n == 0) break;
|
||||||
else throw new FilesystemException(name, FSCode.NO_PERMISSIONS_R);
|
|
||||||
}
|
parts.add(buff);
|
||||||
@Override public void write(byte[] buff) {
|
size += n;
|
||||||
if (write != null && perms.hasPermission(write, name)) self.write(buff);
|
|
||||||
else throw new FilesystemException(name, FSCode.NO_PERMISSIONS_RW);
|
|
||||||
}
|
|
||||||
@Override public long seek(long offset, int pos) {
|
|
||||||
if (seek != null && perms.hasPermission(seek, name)) return self.seek(offset, pos);
|
|
||||||
else throw new FilesystemException(name, FSCode.NO_PERMISSIONS_R);
|
|
||||||
}
|
|
||||||
@Override public void close() {
|
|
||||||
if (close != null && perms.hasPermission(close, name)) self.close();
|
|
||||||
else throw new FilesystemException(name, FSCode.NO_PERMISSIONS_R);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buff = new byte[size];
|
||||||
|
|
||||||
|
var i = 0;
|
||||||
|
|
||||||
|
for (var part : parts) {
|
||||||
|
System.arraycopy(part, 0, buff, i, part.length);
|
||||||
|
i += part.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return buff;
|
||||||
|
}
|
||||||
default String readToString() {
|
default String readToString() {
|
||||||
long len = seek(0, 2);
|
return new String(readAll());
|
||||||
if (len < 0) return null;
|
|
||||||
seek(0, 0);
|
|
||||||
|
|
||||||
byte[] res = new byte[(int)len];
|
|
||||||
len = read(res);
|
|
||||||
|
|
||||||
return new String(res);
|
|
||||||
}
|
}
|
||||||
default String readLine() {
|
default String readLine() {
|
||||||
var res = new Buffer();
|
var res = new Buffer();
|
||||||
@ -66,74 +57,105 @@ public interface File {
|
|||||||
return new String(res.data());
|
return new String(res.data());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static File ofStream(String name, InputStream str) {
|
public static File ofStream(InputStream str) {
|
||||||
return new File() {
|
return new File() {
|
||||||
@Override public int read(byte[] buff) {
|
@Override public int read(byte[] buff) {
|
||||||
try {
|
try {
|
||||||
return str.read(buff);
|
try { return str.read(buff); }
|
||||||
|
catch (NullPointerException e) { throw new FilesystemException(ErrorReason.ILLEGAL_ARGS, e.getMessage()); }
|
||||||
|
catch (IOException e) { throw new FilesystemException(ErrorReason.UNKNOWN, e.getMessage()); }
|
||||||
}
|
}
|
||||||
catch (IOException e) {
|
catch (FilesystemException e) { throw e.setAction(ActionType.READ); }
|
||||||
throw new FilesystemException(name, FSCode.NO_PERMISSIONS_R);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@Override public void write(byte[] buff) {
|
|
||||||
throw new FilesystemException(name, FSCode.NO_PERMISSIONS_RW);
|
|
||||||
}
|
|
||||||
@Override public long seek(long offset, int pos) {
|
|
||||||
throw new FilesystemException(name, FSCode.UNSUPPORTED_OPERATION);
|
|
||||||
}
|
|
||||||
@Override public void close() {
|
|
||||||
throw new FilesystemException(name, FSCode.UNSUPPORTED_OPERATION);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
public static File ofStream(String name, OutputStream str) {
|
public static File ofStream(OutputStream str) {
|
||||||
return new File() {
|
return new File() {
|
||||||
@Override public int read(byte[] buff) {
|
|
||||||
throw new FilesystemException(name, FSCode.NO_PERMISSIONS_R);
|
|
||||||
}
|
|
||||||
@Override public void write(byte[] buff) {
|
@Override public void write(byte[] buff) {
|
||||||
try {
|
try {
|
||||||
str.write(buff);
|
try { str.write(buff); }
|
||||||
|
catch (NullPointerException e) {throw new FilesystemException(ErrorReason.ILLEGAL_ARGS, e.getMessage()); }
|
||||||
|
catch (IOException e) { throw new FilesystemException(ErrorReason.UNKNOWN, e.getMessage()); }
|
||||||
}
|
}
|
||||||
catch (IOException e) {
|
catch (FilesystemException e) { throw e.setAction(ActionType.WRITE); }
|
||||||
throw new FilesystemException(name, FSCode.NO_PERMISSIONS_RW);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@Override public long seek(long offset, int pos) {
|
|
||||||
throw new FilesystemException(name, FSCode.UNSUPPORTED_OPERATION);
|
|
||||||
}
|
|
||||||
@Override public void close() {
|
|
||||||
throw new FilesystemException(name, FSCode.UNSUPPORTED_OPERATION);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
public static File ofLineWriter(String name, LineWriter writer) {
|
public static File ofLineWriter(LineWriter writer) {
|
||||||
var buff = new Buffer();
|
var buff = new Buffer();
|
||||||
|
|
||||||
return new File() {
|
return new File() {
|
||||||
@Override public int read(byte[] buff) {
|
|
||||||
throw new FilesystemException(name, FSCode.NO_PERMISSIONS_R);
|
|
||||||
}
|
|
||||||
@Override public void write(byte[] val) {
|
@Override public void write(byte[] val) {
|
||||||
|
try {
|
||||||
|
if (val == null) throw new FilesystemException(ErrorReason.ILLEGAL_ARGS, "Given buffer is null.");
|
||||||
|
|
||||||
for (var b : val) {
|
for (var b : val) {
|
||||||
if (b == '\n') {
|
if (b == '\n') {
|
||||||
try {
|
try {
|
||||||
writer.writeLine(new String(buff.data()));
|
writer.writeLine(new String(buff.data()));
|
||||||
}
|
}
|
||||||
catch (IOException e) {
|
catch (IOException e) {
|
||||||
throw new FilesystemException(name, FSCode.NO_PERMISSIONS_RW);
|
throw new FilesystemException(ErrorReason.UNKNOWN, e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else buff.append(b);
|
else buff.append(b);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@Override public long seek(long offset, int pos) {
|
catch (FilesystemException e) { throw e.setAction(ActionType.WRITE); }
|
||||||
throw new FilesystemException(name, FSCode.UNSUPPORTED_OPERATION);
|
|
||||||
}
|
|
||||||
@Override public void close() {
|
|
||||||
throw new FilesystemException(name, FSCode.UNSUPPORTED_OPERATION);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
public static File ofLineReader(LineReader reader) {
|
||||||
|
return new File() {
|
||||||
|
private int offset = 0;
|
||||||
|
private byte[] prev = new byte[0];
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read(byte[] buff) {
|
||||||
|
try {
|
||||||
|
if (buff == null) throw new FilesystemException(ErrorReason.ILLEGAL_ARGS, "Given buffer is null.");
|
||||||
|
var ptr = 0;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (prev == null) break;
|
||||||
|
if (offset >= prev.length) {
|
||||||
|
try {
|
||||||
|
var line = reader.readLine();
|
||||||
|
|
||||||
|
if (line == null) {
|
||||||
|
prev = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else prev = (line + "\n").getBytes();
|
||||||
|
|
||||||
|
offset = 0;
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
throw new FilesystemException(ErrorReason.UNKNOWN, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ptr + prev.length - offset > buff.length) {
|
||||||
|
var n = buff.length - ptr;
|
||||||
|
System.arraycopy(prev, offset, buff, ptr, buff.length - ptr);
|
||||||
|
offset += n;
|
||||||
|
ptr += n;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var n = prev.length - offset;
|
||||||
|
System.arraycopy(prev, offset, buff, ptr, n);
|
||||||
|
offset += n;
|
||||||
|
ptr += n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
catch (FilesystemException e) { throw e.setAction(ActionType.READ); }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public static File ofIterator(Iterator<String> it) {
|
||||||
|
return ofLineReader(LineReader.ofIterator(it));
|
||||||
|
}
|
||||||
}
|
}
|
@ -5,6 +5,9 @@ public class FileStat {
|
|||||||
public final EntryType type;
|
public final EntryType type;
|
||||||
|
|
||||||
public FileStat(Mode mode, EntryType type) {
|
public FileStat(Mode mode, EntryType type) {
|
||||||
|
if (mode == Mode.NONE) type = EntryType.NONE;
|
||||||
|
if (type == EntryType.NONE) mode = Mode.NONE;
|
||||||
|
|
||||||
this.mode = mode;
|
this.mode = mode;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
}
|
}
|
||||||
|
@ -6,10 +6,11 @@ import me.topchetoeu.jscript.core.engine.values.Symbol;
|
|||||||
public interface Filesystem {
|
public interface Filesystem {
|
||||||
public static final Symbol ENV_KEY = Symbol.get("Environment.fs");
|
public static final Symbol ENV_KEY = Symbol.get("Environment.fs");
|
||||||
|
|
||||||
String normalize(String... path);
|
default String normalize(String... path) { return Paths.normalize(path); }
|
||||||
File open(String path, Mode mode) throws FilesystemException;
|
default boolean create(String path, EntryType type) { throw new FilesystemException(ErrorReason.UNSUPPORTED).setAction(ActionType.CREATE); }
|
||||||
void create(String path, EntryType type) throws FilesystemException;
|
File open(String path, Mode mode);
|
||||||
FileStat stat(String path) throws FilesystemException;
|
FileStat stat(String path);
|
||||||
|
void close();
|
||||||
|
|
||||||
public static Filesystem get(Extensions exts) {
|
public static Filesystem get(Extensions exts) {
|
||||||
return exts.get(ENV_KEY);
|
return exts.get(ENV_KEY);
|
||||||
|
@ -1,57 +1,90 @@
|
|||||||
package me.topchetoeu.jscript.utils.filesystem;
|
package me.topchetoeu.jscript.utils.filesystem;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
import me.topchetoeu.jscript.core.engine.values.Values;
|
import me.topchetoeu.jscript.core.engine.values.Values;
|
||||||
import me.topchetoeu.jscript.core.exceptions.EngineException;
|
import me.topchetoeu.jscript.core.exceptions.EngineException;
|
||||||
|
|
||||||
public class FilesystemException extends RuntimeException {
|
public class FilesystemException extends RuntimeException {
|
||||||
public static enum FSCode {
|
public final ErrorReason reason;
|
||||||
DOESNT_EXIST(0x1),
|
public final String details;
|
||||||
NOT_FILE(0x2),
|
private ActionType action;
|
||||||
NOT_FOLDER(0x3),
|
private EntryType entry = EntryType.FILE;
|
||||||
NO_PERMISSIONS_R(0x4),
|
private String path;
|
||||||
NO_PERMISSIONS_RW(0x5),
|
|
||||||
FOLDER_NOT_EMPTY(0x6),
|
|
||||||
ALREADY_EXISTS(0x7),
|
|
||||||
FOLDER_EXISTS(0x8),
|
|
||||||
UNSUPPORTED_OPERATION(0x9);
|
|
||||||
|
|
||||||
public final int code;
|
public FilesystemException setPath(String path) {
|
||||||
|
this.path = path;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public FilesystemException setAction(ActionType action) {
|
||||||
|
if (action == null) action = ActionType.UNKNOWN;
|
||||||
|
|
||||||
private FSCode(int code) { this.code = code; }
|
this.action = action;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public FilesystemException setEntry(EntryType entry) {
|
||||||
|
if (entry == null) entry = EntryType.NONE;
|
||||||
|
|
||||||
|
this.entry = entry;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final String[] MESSAGES = {
|
public ActionType action() {
|
||||||
"How did we get here?",
|
return action;
|
||||||
"The file or folder '%s' doesn't exist or is inaccessible.",
|
|
||||||
"'%s' is not a file",
|
|
||||||
"'%s' is not a folder",
|
|
||||||
"No permissions to read '%s'",
|
|
||||||
"No permissions to write '%s'",
|
|
||||||
"Can't delete '%s', since it is a full folder.",
|
|
||||||
"'%s' already exists.",
|
|
||||||
"An unsupported operation was performed on the file '%s'."
|
|
||||||
};
|
|
||||||
|
|
||||||
public final String message, filename;
|
|
||||||
public final FSCode code;
|
|
||||||
|
|
||||||
public FilesystemException(String message, String filename, FSCode code) {
|
|
||||||
super(code + ": " + String.format(message, filename));
|
|
||||||
this.message = message;
|
|
||||||
this.code = code;
|
|
||||||
this.filename = filename;
|
|
||||||
}
|
}
|
||||||
public FilesystemException(String filename, FSCode code) {
|
public String path() {
|
||||||
super(code + ": " + String.format(MESSAGES[code.code], filename));
|
return path;
|
||||||
this.message = MESSAGES[code.code];
|
}
|
||||||
this.code = code;
|
public EntryType entry() {
|
||||||
this.filename = filename;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
public EngineException toEngineException() {
|
public EngineException toEngineException() {
|
||||||
var res = EngineException.ofError("IOError", getMessage());
|
var res = EngineException.ofError("IOError", getMessage());
|
||||||
Values.setMember(null, res.value, "code", code);
|
|
||||||
Values.setMember(null, res.value, "filename", filename.toString());
|
Values.setMember(null, res.value, "action", action.code);
|
||||||
|
Values.setMember(null, res.value, "reason", reason.code);
|
||||||
|
Values.setMember(null, res.value, "path", path);
|
||||||
|
Values.setMember(null, res.value, "entry", entry.name);
|
||||||
|
if (details != null) Values.setMember(null, res.value, "details", details);
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override public String getMessage() {
|
||||||
|
var parts = new ArrayList<String>(10);
|
||||||
|
|
||||||
|
path = String.join(" ", parts).trim();
|
||||||
|
if (path.isEmpty()) path = null;
|
||||||
|
parts.clear();
|
||||||
|
|
||||||
|
parts.add(action == null ? "An action performed upon " : action.readable(reason.usePast));
|
||||||
|
|
||||||
|
if (entry == EntryType.FILE) parts.add("file");
|
||||||
|
if (entry == EntryType.FOLDER) parts.add("folder");
|
||||||
|
|
||||||
|
if (path != null) parts.add(path);
|
||||||
|
|
||||||
|
parts.add(reason.readable);
|
||||||
|
|
||||||
|
var msg = String.join(" ", parts);
|
||||||
|
if (details != null) msg += ": " + details;
|
||||||
|
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FilesystemException(ErrorReason type, String details) {
|
||||||
|
super();
|
||||||
|
if (type == null) type = ErrorReason.UNKNOWN;
|
||||||
|
|
||||||
|
this.details = details;
|
||||||
|
this.reason = type;
|
||||||
|
}
|
||||||
|
public FilesystemException(ErrorReason type) {
|
||||||
|
this(type, null);
|
||||||
|
}
|
||||||
|
public FilesystemException() {
|
||||||
|
this(null);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
package me.topchetoeu.jscript.utils.filesystem;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class HandleManager {
|
||||||
|
private Set<File> files = new HashSet<>();
|
||||||
|
|
||||||
|
public File put(File val) {
|
||||||
|
var handle = new File() {
|
||||||
|
@Override public int read(byte[] buff) {
|
||||||
|
return val.read(buff);
|
||||||
|
}
|
||||||
|
@Override public void write(byte[] buff) {
|
||||||
|
val.write(buff);
|
||||||
|
}
|
||||||
|
@Override public long seek(long offset, int pos) {
|
||||||
|
return val.seek(offset, pos);
|
||||||
|
}
|
||||||
|
@Override public boolean close() {
|
||||||
|
return files.remove(this) && val.close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
files.add(handle);
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
public void close() {
|
||||||
|
while (!files.isEmpty()) {
|
||||||
|
files.stream().findFirst().get().close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package me.topchetoeu.jscript.utils.filesystem;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
|
||||||
|
public interface LineReader {
|
||||||
|
String readLine() throws IOException;
|
||||||
|
|
||||||
|
public static LineReader ofIterator(Iterator<String> it) {
|
||||||
|
return () -> {
|
||||||
|
if (it.hasNext()) return it.next();
|
||||||
|
else return null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package me.topchetoeu.jscript.utils;
|
package me.topchetoeu.jscript.utils.filesystem;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
@ -1,73 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.utils.filesystem;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
import me.topchetoeu.jscript.utils.filesystem.FilesystemException.FSCode;
|
|
||||||
|
|
||||||
public class ListFile implements File {
|
|
||||||
private Iterator<String> it;
|
|
||||||
private String filename;
|
|
||||||
private byte[] currFile;
|
|
||||||
private long ptr = 0, start = 0, end = 0;
|
|
||||||
|
|
||||||
private void next() {
|
|
||||||
if (it != null && it.hasNext()) {
|
|
||||||
start = end;
|
|
||||||
currFile = (it.next() + "\n").getBytes();
|
|
||||||
end = start + currFile.length;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
it = null;
|
|
||||||
currFile = null;
|
|
||||||
end = -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
it = null;
|
|
||||||
currFile = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int read(byte[] buff) {
|
|
||||||
if (ptr < start) return 0;
|
|
||||||
if (it == null) return 0;
|
|
||||||
|
|
||||||
var i = 0;
|
|
||||||
|
|
||||||
while (i < buff.length) {
|
|
||||||
while (i + ptr >= end) {
|
|
||||||
next();
|
|
||||||
if (it == null) return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int cpyN = Math.min(currFile.length, buff.length - i);
|
|
||||||
System.arraycopy(currFile, (int)(ptr + i - start), buff, i, cpyN);
|
|
||||||
|
|
||||||
i += cpyN;
|
|
||||||
}
|
|
||||||
|
|
||||||
ptr += i;
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long seek(long offset, int pos) {
|
|
||||||
if (pos == 2) throw new FilesystemException(filename, FSCode.UNSUPPORTED_OPERATION);
|
|
||||||
if (pos == 1) offset += ptr;
|
|
||||||
return ptr = offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(byte[] buff) {
|
|
||||||
throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_RW);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ListFile(String filename, Stream<String> stream) throws IOException {
|
|
||||||
this.it = stream.iterator();
|
|
||||||
this.filename = filename;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,62 +1,35 @@
|
|||||||
package me.topchetoeu.jscript.utils.filesystem;
|
package me.topchetoeu.jscript.utils.filesystem;
|
||||||
|
|
||||||
import me.topchetoeu.jscript.common.Buffer;
|
import me.topchetoeu.jscript.common.Buffer;
|
||||||
import me.topchetoeu.jscript.utils.filesystem.FilesystemException.FSCode;
|
|
||||||
|
|
||||||
public class MemoryFile implements File {
|
class MemoryFile extends BaseFile<Buffer> {
|
||||||
private int ptr;
|
private int ptr;
|
||||||
private Mode mode;
|
|
||||||
private Buffer data;
|
|
||||||
private String filename;
|
|
||||||
|
|
||||||
public Buffer data() { return data; }
|
@Override protected int onRead(byte[] buff) {
|
||||||
|
var res = handle().read(ptr, buff);
|
||||||
@Override
|
|
||||||
public int read(byte[] buff) {
|
|
||||||
if (data == null || !mode.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R);
|
|
||||||
var res = data.read(ptr, buff);
|
|
||||||
ptr += res;
|
ptr += res;
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
@Override
|
@Override protected void onWrite(byte[] buff) {
|
||||||
public void write(byte[] buff) {
|
handle().write(ptr, buff);
|
||||||
if (data == null || !mode.writable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_RW);
|
|
||||||
|
|
||||||
data.write(ptr, buff);
|
|
||||||
ptr += buff.length;
|
ptr += buff.length;
|
||||||
}
|
}
|
||||||
|
@Override protected long onSeek(long offset, int pos) {
|
||||||
@Override
|
|
||||||
public long seek(long offset, int pos) {
|
|
||||||
if (data == null || !mode.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R);
|
|
||||||
|
|
||||||
if (pos == 0) ptr = (int)offset;
|
if (pos == 0) ptr = (int)offset;
|
||||||
else if (pos == 1) ptr += (int)offset;
|
else if (pos == 1) ptr += (int)offset;
|
||||||
else if (pos == 2) ptr = data.length() - (int)offset;
|
else if (pos == 2) ptr = handle().length() - (int)offset;
|
||||||
|
|
||||||
if (ptr < 0) ptr = 0;
|
if (ptr < 0) ptr = 0;
|
||||||
if (ptr > data.length()) ptr = data.length();
|
if (ptr > handle().length()) ptr = handle().length();
|
||||||
|
|
||||||
return pos;
|
return pos;
|
||||||
}
|
}
|
||||||
|
@Override protected boolean onClose() {
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
mode = Mode.NONE;
|
|
||||||
ptr = 0;
|
ptr = 0;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MemoryFile(String filename, Buffer buff, Mode mode) {
|
public MemoryFile(Buffer buff, Mode mode) {
|
||||||
this.filename = filename;
|
super(buff, mode);
|
||||||
this.data = buff;
|
|
||||||
this.mode = mode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static MemoryFile fromFileList(String filename, java.io.File[] list) {
|
|
||||||
var res = new StringBuilder();
|
|
||||||
|
|
||||||
for (var el : list) res.append(el.getName()).append('\n');
|
|
||||||
|
|
||||||
return new MemoryFile(filename, new Buffer(res.toString().getBytes()), Mode.READ);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -6,75 +6,79 @@ import java.util.HashSet;
|
|||||||
|
|
||||||
import me.topchetoeu.jscript.common.Buffer;
|
import me.topchetoeu.jscript.common.Buffer;
|
||||||
import me.topchetoeu.jscript.common.Filename;
|
import me.topchetoeu.jscript.common.Filename;
|
||||||
import me.topchetoeu.jscript.utils.filesystem.FilesystemException.FSCode;
|
|
||||||
|
|
||||||
public class MemoryFilesystem implements Filesystem {
|
public class MemoryFilesystem implements Filesystem {
|
||||||
public final Mode mode;
|
public final Mode mode;
|
||||||
private HashMap<Path, Buffer> files = new HashMap<>();
|
private HashMap<Path, Buffer> files = new HashMap<>();
|
||||||
private HashSet<Path> folders = new HashSet<>();
|
private HashSet<Path> folders = new HashSet<>();
|
||||||
|
private HandleManager handles = new HandleManager();
|
||||||
|
|
||||||
private Path realPath(String path) {
|
private Path realPath(String path) {
|
||||||
return Filename.normalize(path);
|
return Filename.normalize(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public String normalize(String... path) {
|
||||||
public String normalize(String... path) {
|
|
||||||
return Paths.normalize(path);
|
return Paths.normalize(path);
|
||||||
}
|
}
|
||||||
|
@Override public File open(String _path, Mode perms) {
|
||||||
@Override
|
try {
|
||||||
public void create(String _path, EntryType type) {
|
|
||||||
var path = realPath(_path);
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case FILE:
|
|
||||||
if (!folders.contains(path.getParent())) throw new FilesystemException(path.toString(), FSCode.DOESNT_EXIST);
|
|
||||||
if (folders.contains(path) || files.containsKey(path)) throw new FilesystemException(path.toString(), FSCode.ALREADY_EXISTS);
|
|
||||||
if (folders.contains(path)) throw new FilesystemException(path.toString(), FSCode.ALREADY_EXISTS);
|
|
||||||
files.put(path, new Buffer());
|
|
||||||
break;
|
|
||||||
case FOLDER:
|
|
||||||
if (!folders.contains(path.getParent())) throw new FilesystemException(_path, FSCode.DOESNT_EXIST);
|
|
||||||
if (folders.contains(path) || files.containsKey(path)) throw new FilesystemException(path.toString(), FSCode.ALREADY_EXISTS);
|
|
||||||
folders.add(path);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
case NONE:
|
|
||||||
if (!folders.remove(path) && files.remove(path) == null) throw new FilesystemException(path.toString(), FSCode.DOESNT_EXIST);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public File open(String _path, Mode perms) {
|
|
||||||
var path = realPath(_path);
|
var path = realPath(_path);
|
||||||
var pcount = path.getNameCount();
|
var pcount = path.getNameCount();
|
||||||
|
|
||||||
if (files.containsKey(path)) return new MemoryFile(path.toString(), files.get(path), perms);
|
if (files.containsKey(path)) return handles.put(new MemoryFile(files.get(path), perms));
|
||||||
else if (folders.contains(path)) {
|
else if (folders.contains(path)) {
|
||||||
var res = new StringBuilder();
|
var res = new StringBuilder();
|
||||||
|
|
||||||
for (var folder : folders) {
|
for (var folder : folders) {
|
||||||
if (pcount + 1 != folder.getNameCount()) continue;
|
if (pcount + 1 != folder.getNameCount()) continue;
|
||||||
if (!folder.startsWith(path)) continue;
|
if (!folder.startsWith(path)) continue;
|
||||||
res.append(folder.toFile().getName()).append('\n');
|
res.append(folder.toFile().getName()).append('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var file : files.keySet()) {
|
for (var file : files.keySet()) {
|
||||||
if (pcount + 1 != file.getNameCount()) continue;
|
if (pcount + 1 != file.getNameCount()) continue;
|
||||||
if (!file.startsWith(path)) continue;
|
if (!file.startsWith(path)) continue;
|
||||||
res.append(file.toFile().getName()).append('\n');
|
res.append(file.toFile().getName()).append('\n');
|
||||||
}
|
}
|
||||||
return new MemoryFile(path.toString(), new Buffer(res.toString().getBytes()), perms.intersect(Mode.READ));
|
|
||||||
}
|
|
||||||
else throw new FilesystemException(path.toString(), FSCode.DOESNT_EXIST);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
return handles.put(new MemoryFile(new Buffer(res.toString().getBytes()), perms.intersect(Mode.READ)));
|
||||||
public FileStat stat(String _path) {
|
}
|
||||||
|
else throw new FilesystemException(ErrorReason.DOESNT_EXIST);
|
||||||
|
}
|
||||||
|
catch (FilesystemException e) { throw e.setPath(_path).setAction(ActionType.OPEN); }
|
||||||
|
}
|
||||||
|
@Override public boolean create(String _path, EntryType type) {
|
||||||
|
try {
|
||||||
|
var path = realPath(_path);
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case FILE:
|
||||||
|
if (!folders.contains(path.getParent())) throw new FilesystemException(ErrorReason.NO_PARENT);
|
||||||
|
if (folders.contains(path) || files.containsKey(path)) return false;
|
||||||
|
files.put(path, new Buffer());
|
||||||
|
return true;
|
||||||
|
case FOLDER:
|
||||||
|
if (!folders.contains(path.getParent())) throw new FilesystemException(ErrorReason.NO_PARENT);
|
||||||
|
if (folders.contains(path) || files.containsKey(path)) return false;
|
||||||
|
folders.add(path);
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
case NONE:
|
||||||
|
return folders.remove(path) || files.remove(path) != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (FilesystemException e) { throw e.setPath(_path).setAction(ActionType.CREATE); }
|
||||||
|
}
|
||||||
|
@Override public FileStat stat(String _path) {
|
||||||
var path = realPath(_path);
|
var path = realPath(_path);
|
||||||
|
|
||||||
if (files.containsKey(path)) return new FileStat(mode, EntryType.FILE);
|
if (files.containsKey(path)) return new FileStat(mode, EntryType.FILE);
|
||||||
else if (folders.contains(path)) return new FileStat(mode, EntryType.FOLDER);
|
else if (folders.contains(path)) return new FileStat(mode, EntryType.FOLDER);
|
||||||
else return new FileStat(Mode.NONE, EntryType.NONE);
|
else return new FileStat(Mode.NONE, EntryType.NONE);
|
||||||
}
|
}
|
||||||
|
@Override public void close() throws FilesystemException {
|
||||||
|
handles.close();
|
||||||
|
}
|
||||||
|
|
||||||
public MemoryFilesystem put(String path, byte[] data) {
|
public MemoryFilesystem put(String path, byte[] data) {
|
||||||
var _path = realPath(path);
|
var _path = realPath(path);
|
||||||
|
@ -3,6 +3,7 @@ package me.topchetoeu.jscript.utils.filesystem;
|
|||||||
public enum Mode {
|
public enum Mode {
|
||||||
NONE("", false, false),
|
NONE("", false, false),
|
||||||
READ("r", true, false),
|
READ("r", true, false),
|
||||||
|
WRITE("rw", false, true),
|
||||||
READ_WRITE("rw", true, true);
|
READ_WRITE("rw", true, true);
|
||||||
|
|
||||||
public final String name;
|
public final String name;
|
||||||
@ -10,9 +11,7 @@ public enum Mode {
|
|||||||
public final boolean writable;
|
public final boolean writable;
|
||||||
|
|
||||||
public Mode intersect(Mode other) {
|
public Mode intersect(Mode other) {
|
||||||
if (this == NONE || other == NONE) return NONE;
|
return of(readable && other.readable, writable && other.writable);
|
||||||
if (this == READ_WRITE && other == READ_WRITE) return READ_WRITE;
|
|
||||||
return READ;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Mode(String mode, boolean r, boolean w) {
|
private Mode(String mode, boolean r, boolean w) {
|
||||||
@ -21,9 +20,20 @@ public enum Mode {
|
|||||||
this.writable = w;
|
this.writable = w;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Mode of(boolean read, boolean write) {
|
||||||
|
if (read && write) return READ_WRITE;
|
||||||
|
if (read) return READ;
|
||||||
|
if (write) return WRITE;
|
||||||
|
return NONE;
|
||||||
|
}
|
||||||
|
|
||||||
public static Mode parse(String mode) {
|
public static Mode parse(String mode) {
|
||||||
switch (mode) {
|
switch (mode.toLowerCase()) {
|
||||||
case "r": return READ;
|
case "r": return READ;
|
||||||
|
case "w": return WRITE;
|
||||||
|
case "r+":
|
||||||
|
case "w+":
|
||||||
|
case "wr":
|
||||||
case "rw": return READ_WRITE;
|
case "rw": return READ_WRITE;
|
||||||
default: return NONE;
|
default: return NONE;
|
||||||
}
|
}
|
||||||
|
@ -3,55 +3,33 @@ package me.topchetoeu.jscript.utils.filesystem;
|
|||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.RandomAccessFile;
|
import java.io.RandomAccessFile;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
import me.topchetoeu.jscript.utils.filesystem.FilesystemException.FSCode;
|
public class PhysicalFile extends BaseFile<RandomAccessFile> {
|
||||||
|
@Override protected int onRead(byte[] buff) {
|
||||||
public class PhysicalFile implements File {
|
try { return handle().read(buff); }
|
||||||
private String filename;
|
catch (IOException e) { throw new FilesystemException(ErrorReason.NO_PERMISSION).setAction(ActionType.READ); }
|
||||||
private RandomAccessFile file;
|
|
||||||
private Mode mode;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int read(byte[] buff) {
|
|
||||||
if (file == null || !mode.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R);
|
|
||||||
else try { return file.read(buff); }
|
|
||||||
catch (IOException e) { throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R); }
|
|
||||||
}
|
}
|
||||||
@Override
|
@Override protected void onWrite(byte[] buff) {
|
||||||
public void write(byte[] buff) {
|
try { handle().write(buff); }
|
||||||
if (file == null || !mode.writable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_RW);
|
catch (IOException e) { throw new FilesystemException(ErrorReason.NO_PERMISSION).setAction(ActionType.WRITE); }
|
||||||
else try { file.write(buff); }
|
|
||||||
catch (IOException e) { throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_RW); }
|
|
||||||
}
|
}
|
||||||
|
@Override protected long onSeek(long offset, int pos) {
|
||||||
@Override
|
|
||||||
public long seek(long offset, int pos) {
|
|
||||||
if (file == null || !mode.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (pos == 1) offset += file.getFilePointer();
|
if (pos == 1) offset += handle().getFilePointer();
|
||||||
else if (pos == 2) offset += file.length();
|
else if (pos == 2) offset += handle().length();
|
||||||
file.seek(offset);
|
handle().seek(offset);
|
||||||
return offset;
|
return offset;
|
||||||
}
|
}
|
||||||
catch (IOException e) { throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R); }
|
catch (IOException e) { throw new FilesystemException(ErrorReason.NO_PERMISSION).setAction(ActionType.SEEK); }
|
||||||
}
|
}
|
||||||
|
@Override protected boolean onClose() {
|
||||||
@Override
|
try { handle().close(); }
|
||||||
public void close() {
|
|
||||||
if (file == null) return;
|
|
||||||
try { file.close(); }
|
|
||||||
catch (IOException e) {} // SHUT
|
catch (IOException e) {} // SHUT
|
||||||
file = null;
|
return true;
|
||||||
mode = Mode.NONE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public PhysicalFile(String name, String path, Mode mode) throws FileNotFoundException {
|
public PhysicalFile(Path path, Mode mode) throws FileNotFoundException {
|
||||||
this.filename = name;
|
super(new RandomAccessFile(path.toFile(), mode.name), mode);
|
||||||
this.mode = mode;
|
|
||||||
|
|
||||||
if (mode == Mode.NONE) file = null;
|
|
||||||
else try { file = new RandomAccessFile(path, mode.name); }
|
|
||||||
catch (FileNotFoundException e) { throw new FilesystemException(filename, FSCode.DOESNT_EXIST); }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,50 +1,48 @@
|
|||||||
package me.topchetoeu.jscript.utils.filesystem;
|
package me.topchetoeu.jscript.utils.filesystem;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.nio.file.FileAlreadyExistsException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.NoSuchFileException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
|
||||||
import me.topchetoeu.jscript.utils.filesystem.FilesystemException.FSCode;
|
|
||||||
|
|
||||||
public class PhysicalFilesystem implements Filesystem {
|
public class PhysicalFilesystem implements Filesystem {
|
||||||
public final String root;
|
public final String root;
|
||||||
|
private HandleManager handles = new HandleManager();
|
||||||
|
|
||||||
private void checkMode(Path path, Mode mode) {
|
private void checkMode(Path path, Mode mode) {
|
||||||
if (!path.startsWith(root)) throw new FilesystemException(path.toString(), FSCode.NO_PERMISSIONS_R);
|
if (!path.startsWith(root)) throw new FilesystemException(ErrorReason.NO_PERMISSION, "Tried to jailbreak the sandbox.");
|
||||||
|
|
||||||
if (mode.readable && !Files.isReadable(path)) throw new FilesystemException(path.toString(), FSCode.NO_PERMISSIONS_R);
|
if (mode.readable && !Files.isReadable(path)) throw new FilesystemException(ErrorReason.NO_PERMISSION, "No read permissions");
|
||||||
if (mode.writable && !Files.isWritable(path)) throw new FilesystemException(path.toString(), FSCode.NO_PERMISSIONS_RW);
|
if (mode.writable && !Files.isWritable(path)) throw new FilesystemException(ErrorReason.NO_PERMISSION, "No write permissions");
|
||||||
}
|
}
|
||||||
|
|
||||||
private Path realPath(String path) {
|
private Path realPath(String path) {
|
||||||
return Path.of(Paths.chroot(root, path));
|
return Path.of(Paths.chroot(root, path));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public String normalize(String... paths) {
|
||||||
public String normalize(String... paths) {
|
|
||||||
return Paths.normalize(paths);
|
return Paths.normalize(paths);
|
||||||
}
|
}
|
||||||
|
@Override public File open(String _path, Mode perms) {
|
||||||
@Override
|
try {
|
||||||
public File open(String _path, Mode perms) {
|
var path = realPath(normalize(_path));
|
||||||
_path = normalize(_path);
|
|
||||||
var path = realPath(_path);
|
|
||||||
|
|
||||||
checkMode(path, perms);
|
checkMode(path, perms);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (Files.isDirectory(path)) return new ListFile(_path, Files.list(path).map((v -> v.getFileName().toString())));
|
if (Files.isDirectory(path)) return handles.put(File.ofIterator(
|
||||||
else return new PhysicalFile(_path, path.toString(), perms);
|
Files.list(path).map(v -> v.getFileName().toString()).iterator()
|
||||||
|
));
|
||||||
|
else return handles.put(new PhysicalFile(path, perms));
|
||||||
}
|
}
|
||||||
catch (IOException e) { throw new FilesystemException(path.toString(), FSCode.DOESNT_EXIST); }
|
catch (IOException e) { throw new FilesystemException(ErrorReason.DOESNT_EXIST); }
|
||||||
}
|
}
|
||||||
|
catch (FilesystemException e) { throw e.setAction(ActionType.OPEN).setPath(_path); }
|
||||||
@Override
|
}
|
||||||
public void create(String _path, EntryType type) {
|
@Override public boolean create(String _path, EntryType type) {
|
||||||
|
try {
|
||||||
var path = realPath(_path);
|
var path = realPath(_path);
|
||||||
|
|
||||||
if (type == EntryType.NONE != Files.exists(path)) throw new FilesystemException(path.toString(), FSCode.ALREADY_EXISTS);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case FILE:
|
case FILE:
|
||||||
@ -58,11 +56,14 @@ public class PhysicalFilesystem implements Filesystem {
|
|||||||
Files.delete(path);
|
Files.delete(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (IOException e) { throw new FilesystemException(path.toString(), FSCode.NO_PERMISSIONS_RW); }
|
catch (FileAlreadyExistsException | NoSuchFileException e) { return false; }
|
||||||
|
catch (IOException e) { throw new FilesystemException(ErrorReason.NO_PARENT); }
|
||||||
}
|
}
|
||||||
|
catch (FilesystemException e) { throw e.setAction(ActionType.CREATE).setPath(_path); }
|
||||||
|
|
||||||
@Override
|
return true;
|
||||||
public FileStat stat(String _path) {
|
}
|
||||||
|
@Override public FileStat stat(String _path) {
|
||||||
var path = realPath(_path);
|
var path = realPath(_path);
|
||||||
|
|
||||||
if (!Files.exists(path)) return new FileStat(Mode.NONE, EntryType.NONE);
|
if (!Files.exists(path)) return new FileStat(Mode.NONE, EntryType.NONE);
|
||||||
@ -81,6 +82,12 @@ public class PhysicalFilesystem implements Filesystem {
|
|||||||
Files.isDirectory(path) ? EntryType.FOLDER : EntryType.FILE
|
Files.isDirectory(path) ? EntryType.FOLDER : EntryType.FILE
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@Override public void close() throws FilesystemException {
|
||||||
|
try {
|
||||||
|
handles.close();
|
||||||
|
}
|
||||||
|
catch (FilesystemException e) { throw e.setAction(ActionType.CLOSE_FS); }
|
||||||
|
}
|
||||||
|
|
||||||
public PhysicalFilesystem(String root) {
|
public PhysicalFilesystem(String root) {
|
||||||
this.root = Paths.normalize(Path.of(root).toAbsolutePath().toString());
|
this.root = Paths.normalize(Path.of(root).toAbsolutePath().toString());
|
||||||
|
@ -4,7 +4,6 @@ import java.util.HashMap;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import me.topchetoeu.jscript.common.Filename;
|
import me.topchetoeu.jscript.common.Filename;
|
||||||
import me.topchetoeu.jscript.utils.filesystem.FilesystemException.FSCode;
|
|
||||||
import me.topchetoeu.jscript.utils.permissions.Matcher;
|
import me.topchetoeu.jscript.utils.permissions.Matcher;
|
||||||
import me.topchetoeu.jscript.utils.permissions.PermissionsProvider;
|
import me.topchetoeu.jscript.utils.permissions.PermissionsProvider;
|
||||||
|
|
||||||
@ -20,8 +19,22 @@ public class RootFilesystem implements Filesystem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void modeAllowed(String _path, Mode mode) throws FilesystemException {
|
private void modeAllowed(String _path, Mode mode) throws FilesystemException {
|
||||||
if (mode.readable && perms != null && !canRead(_path)) throw new FilesystemException(_path, FSCode.NO_PERMISSIONS_R);
|
if (mode.readable && perms != null && !canRead(_path)) {
|
||||||
if (mode.writable && perms != null && !canWrite(_path)) throw new FilesystemException(_path, FSCode.NO_PERMISSIONS_RW);
|
throw new FilesystemException(ErrorReason.NO_PERMISSION, "No read permissions").setPath(_path);
|
||||||
|
}
|
||||||
|
if (mode.writable && perms != null && !canWrite(_path)) {
|
||||||
|
throw new FilesystemException(ErrorReason.NO_PERMISSION, "No wtrite permissions").setPath(_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Filesystem getProtocol(Filename filename) {
|
||||||
|
var protocol = protocols.get(filename.protocol);
|
||||||
|
|
||||||
|
if (protocol == null) {
|
||||||
|
throw new FilesystemException(ErrorReason.DOESNT_EXIST, "The protocol '" + filename.protocol + "' doesn't exist.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return protocol;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public String normalize(String... paths) {
|
@Override public String normalize(String... paths) {
|
||||||
@ -37,30 +50,43 @@ public class RootFilesystem implements Filesystem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
@Override public File open(String path, Mode perms) throws FilesystemException {
|
@Override public File open(String path, Mode perms) throws FilesystemException {
|
||||||
|
try {
|
||||||
var filename = Filename.parse(path);
|
var filename = Filename.parse(path);
|
||||||
var protocol = protocols.get(filename.protocol);
|
var protocol = getProtocol(filename);
|
||||||
if (protocol == null) throw new FilesystemException(filename.toString(), FSCode.DOESNT_EXIST);
|
|
||||||
modeAllowed(filename.toString(), perms);
|
modeAllowed(filename.toString(), perms);
|
||||||
|
return protocol.open(filename.path, perms);
|
||||||
try { return protocol.open(filename.path, perms); }
|
|
||||||
catch (FilesystemException e) { throw new FilesystemException(filename.toString(), e.code); }
|
|
||||||
}
|
}
|
||||||
@Override public void create(String path, EntryType type) throws FilesystemException {
|
catch (FilesystemException e) { throw e.setPath(path).setAction(ActionType.OPEN); }
|
||||||
|
}
|
||||||
|
@Override public boolean create(String path, EntryType type) throws FilesystemException {
|
||||||
|
try {
|
||||||
var filename = Filename.parse(path);
|
var filename = Filename.parse(path);
|
||||||
var protocol = protocols.get(filename.protocol);
|
var protocol = getProtocol(filename);
|
||||||
if (protocol == null) throw new FilesystemException(filename.toString(), FSCode.DOESNT_EXIST);
|
|
||||||
modeAllowed(filename.toString(), Mode.READ_WRITE);
|
|
||||||
|
|
||||||
try { protocol.create(filename.path, type); }
|
modeAllowed(filename.toString(), Mode.WRITE);
|
||||||
catch (FilesystemException e) { throw new FilesystemException(filename.toString(), e.code); }
|
return protocol.create(filename.path, type);
|
||||||
|
}
|
||||||
|
catch (FilesystemException e) { throw e.setPath(path).setAction(ActionType.CREATE); }
|
||||||
}
|
}
|
||||||
@Override public FileStat stat(String path) throws FilesystemException {
|
@Override public FileStat stat(String path) throws FilesystemException {
|
||||||
|
try {
|
||||||
var filename = Filename.parse(path);
|
var filename = Filename.parse(path);
|
||||||
var protocol = protocols.get(filename.protocol);
|
var protocol = getProtocol(filename);
|
||||||
if (protocol == null) throw new FilesystemException(filename.toString(), FSCode.DOESNT_EXIST);
|
|
||||||
|
|
||||||
try { return protocol.stat(filename.path); }
|
return protocol.stat(filename.path);
|
||||||
catch (FilesystemException e) { throw new FilesystemException(filename.toString(), e.code); }
|
}
|
||||||
|
catch (FilesystemException e) { throw e.setPath(path).setAction(ActionType.STAT); }
|
||||||
|
}
|
||||||
|
@Override public void close() throws FilesystemException {
|
||||||
|
try {
|
||||||
|
for (var protocol : protocols.values()) {
|
||||||
|
protocol.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
protocols.clear();
|
||||||
|
}
|
||||||
|
catch (FilesystemException e) { throw e.setAction(ActionType.CLOSE_FS); }
|
||||||
}
|
}
|
||||||
|
|
||||||
public RootFilesystem(PermissionsProvider perms) {
|
public RootFilesystem(PermissionsProvider perms) {
|
||||||
|
@ -0,0 +1,42 @@
|
|||||||
|
package me.topchetoeu.jscript.utils.filesystem;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
public class STDFilesystem implements Filesystem {
|
||||||
|
private final HashMap<String, File> handles = new HashMap<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String normalize(String... path) {
|
||||||
|
var res = Paths.normalize(path);
|
||||||
|
while (res.startsWith("/")) res = res.substring(1);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public File open(String path, Mode mode) {
|
||||||
|
path = normalize(path);
|
||||||
|
if (handles.containsKey(path)) return handles.get(path);
|
||||||
|
else throw new FilesystemException(ErrorReason.DOESNT_EXIST).setAction(ActionType.OPEN).setPath(path);
|
||||||
|
}
|
||||||
|
@Override public FileStat stat(String path) {
|
||||||
|
path = normalize(path);
|
||||||
|
if (handles.containsKey(path)) return new FileStat(Mode.READ_WRITE, EntryType.FILE);
|
||||||
|
else return new FileStat(Mode.NONE, EntryType.NONE);
|
||||||
|
}
|
||||||
|
@Override public void close() {
|
||||||
|
handles.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public STDFilesystem add(String name, File handle) {
|
||||||
|
this.handles.put(name, handle);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static STDFilesystem ofStd(InputStream in, OutputStream out, OutputStream err) {
|
||||||
|
return new STDFilesystem()
|
||||||
|
.add("in", File.ofStream(in))
|
||||||
|
.add("out", File.ofStream(out))
|
||||||
|
.add("err", File.ofStream(err));
|
||||||
|
}
|
||||||
|
}
|
@ -1,24 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.utils.filesystem;
|
|
||||||
|
|
||||||
import me.topchetoeu.jscript.core.engine.Extensions;
|
|
||||||
import me.topchetoeu.jscript.core.engine.values.Symbol;
|
|
||||||
import me.topchetoeu.jscript.core.exceptions.EngineException;
|
|
||||||
|
|
||||||
public class Stdio {
|
|
||||||
public static final Symbol STDIN = Symbol.get("IO.stdin");
|
|
||||||
public static final Symbol STDOUT = Symbol.get("IO.stdout");
|
|
||||||
public static final Symbol STDERR = Symbol.get("IO.stderr");
|
|
||||||
|
|
||||||
public static File stdout(Extensions exts) {
|
|
||||||
if (exts.hasNotNull(STDOUT)) return exts.get(STDOUT);
|
|
||||||
else throw EngineException.ofError("stdout is not supported.");
|
|
||||||
}
|
|
||||||
public static File stdin(Extensions exts) {
|
|
||||||
if (exts.hasNotNull(STDIN)) return exts.get(STDIN);
|
|
||||||
else throw EngineException.ofError("stdin is not supported.");
|
|
||||||
}
|
|
||||||
public static File stderr(Extensions exts) {
|
|
||||||
if (exts.hasNotNull(STDERR)) return exts.get(STDERR);
|
|
||||||
else throw EngineException.ofError("stderr is not supported.");
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user