Added layered VFS constructed from merging FS definitions from data packs.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
package li.cil.oc2.common;
|
||||
|
||||
import li.cil.oc2.common.bus.device.data.FileSystems;
|
||||
import li.cil.oc2.common.bus.device.rpc.RPCMethodParameterTypeAdapters;
|
||||
import li.cil.oc2.common.capabilities.Capabilities;
|
||||
import li.cil.oc2.common.integration.IMC;
|
||||
@@ -33,11 +34,13 @@ public final class CommonSetup {
|
||||
|
||||
public static void handleServerAboutToStart(final FMLServerAboutToStartEvent event) {
|
||||
BlobStorage.setServer(event.getServer());
|
||||
FileSystems.initialize(event.getServer());
|
||||
}
|
||||
|
||||
public static void handleServerStopped(final FMLServerStoppedEvent event) {
|
||||
BlobStorage.synchronize();
|
||||
Allocator.resetAndCheckLeaks();
|
||||
FileSystems.reset();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package li.cil.oc2.common.bus.device.data;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import li.cil.oc2.common.vm.fs.LayeredFileSystem;
|
||||
import li.cil.oc2.common.vm.fs.ResourceFileSystem;
|
||||
import li.cil.sedna.fs.*;
|
||||
import net.minecraft.resources.IResource;
|
||||
import net.minecraft.resources.IResourceManager;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.*;
|
||||
|
||||
public final class FileSystems {
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
|
||||
private static final LayeredFileSystem LAYERED_FILE_SYSTEM = new LayeredFileSystem();
|
||||
|
||||
public static FileSystem getLayeredFileSystem() {
|
||||
return LAYERED_FILE_SYSTEM;
|
||||
}
|
||||
|
||||
public static void initialize(final MinecraftServer server) {
|
||||
reset();
|
||||
|
||||
final IResourceManager resourceManager = server.getDataPackRegistries().getResourceManager();
|
||||
|
||||
final Collection<ResourceLocation> fileSystemDescriptorLocations = resourceManager
|
||||
.getAllResourceLocations("file_systems", s -> s.endsWith(".fs.json"));
|
||||
|
||||
for (final ResourceLocation fileSystemDescriptorLocation : fileSystemDescriptorLocations) {
|
||||
try {
|
||||
final IResource fileSystemDescriptor = resourceManager.getResource(fileSystemDescriptorLocation);
|
||||
final JsonObject json = new JsonParser().parse(new InputStreamReader(fileSystemDescriptor.getInputStream())).getAsJsonObject();
|
||||
final String type = json.getAsJsonPrimitive("type").getAsString();
|
||||
switch (type) {
|
||||
case "virtio-9p": {
|
||||
final ResourceLocation location = new ResourceLocation(json.getAsJsonPrimitive("location").getAsString());
|
||||
final ResourceFileSystem fileSystem = new ResourceFileSystem(resourceManager, location);
|
||||
LAYERED_FILE_SYSTEM.addLayer(fileSystem);
|
||||
break;
|
||||
}
|
||||
case "virtio-blk": {
|
||||
LOGGER.error("Not yet implemented.");
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
LOGGER.error("Unsupported file system type [{}].", type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (final Throwable e) {
|
||||
LOGGER.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void reset() {
|
||||
LAYERED_FILE_SYSTEM.clear();
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import li.cil.oc2.common.block.ComputerBlock;
|
||||
import li.cil.oc2.common.bus.AbstractDeviceBusController;
|
||||
import li.cil.oc2.common.bus.TileEntityDeviceBusController;
|
||||
import li.cil.oc2.common.bus.TileEntityDeviceBusElement;
|
||||
import li.cil.oc2.common.bus.device.data.FileSystems;
|
||||
import li.cil.oc2.common.bus.device.util.Devices;
|
||||
import li.cil.oc2.common.bus.device.util.ItemDeviceInfo;
|
||||
import li.cil.oc2.common.capabilities.Capabilities;
|
||||
@@ -36,7 +37,6 @@ import li.cil.oc2.common.vm.VirtualMachineRunner;
|
||||
import li.cil.sedna.api.memory.MemoryAccessException;
|
||||
import li.cil.sedna.device.serial.UART16550A;
|
||||
import li.cil.sedna.device.virtio.VirtIOFileSystemDevice;
|
||||
import li.cil.sedna.fs.HostFileSystem;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.nbt.CompoundNBT;
|
||||
@@ -682,7 +682,7 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic
|
||||
context.getMemoryRangeAllocator().claimMemoryRange(uart);
|
||||
board.setStandardOutputDevice(uart);
|
||||
|
||||
vfs = new VirtIOFileSystemDevice(context.getMemoryMap(), "scripts", new HostFileSystem());
|
||||
vfs = new VirtIOFileSystemDevice(context.getMemoryMap(), "data", FileSystems.getLayeredFileSystem());
|
||||
context.getInterruptAllocator().claimInterrupt(VFS_INTERRUPT).ifPresent(interrupt ->
|
||||
vfs.getInterrupt().set(interrupt, context.getInterruptController()));
|
||||
context.getMemoryRangeAllocator().claimMemoryRange(vfs);
|
||||
|
||||
140
src/main/java/li/cil/oc2/common/vm/fs/LayeredFileSystem.java
Normal file
140
src/main/java/li/cil/oc2/common/vm/fs/LayeredFileSystem.java
Normal file
@@ -0,0 +1,140 @@
|
||||
package li.cil.oc2.common.vm.fs;
|
||||
|
||||
import li.cil.sedna.fs.*;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public final class LayeredFileSystem implements FileSystem {
|
||||
private final ArrayList<FileSystem> fileSystems = new ArrayList<>();
|
||||
|
||||
public void addLayer(final FileSystem fileSystem) {
|
||||
fileSystems.add(fileSystem);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
fileSystems.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileSystemStats statfs() throws IOException {
|
||||
final FileSystemStats result = new FileSystemStats();
|
||||
for (final FileSystem fileSystem : fileSystems) {
|
||||
final FileSystemStats stats = fileSystem.statfs();
|
||||
result.blockCount += stats.blockCount; // not correct if blocksize differs, but whatever
|
||||
result.freeBlockCount += stats.freeBlockCount;
|
||||
result.availableBlockCount += stats.availableBlockCount;
|
||||
result.fileCount += stats.fileCount;
|
||||
result.freeFileCount += stats.freeFileCount;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getUniqueId(final Path path) throws IOException {
|
||||
for (final FileSystem fileSystem : fileSystems) {
|
||||
if (fileSystem.exists(path)) {
|
||||
return fileSystem.getUniqueId(path);
|
||||
}
|
||||
}
|
||||
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists(final Path path) {
|
||||
for (final FileSystem fileSystem : fileSystems) {
|
||||
if (fileSystem.exists(path)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirectory(final Path path) {
|
||||
for (final FileSystem fileSystem : fileSystems) {
|
||||
if (fileSystem.exists(path)) {
|
||||
return fileSystem.isDirectory(path);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWritable(final Path path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReadable(final Path path) {
|
||||
for (final FileSystem fileSystem : fileSystems) {
|
||||
if (fileSystem.exists(path)) {
|
||||
return fileSystem.isReadable(path);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExecutable(final Path path) {
|
||||
for (final FileSystem fileSystem : fileSystems) {
|
||||
if (fileSystem.exists(path)) {
|
||||
return fileSystem.isExecutable(path);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BasicFileAttributes getAttributes(final Path path) throws IOException {
|
||||
for (final FileSystem fileSystem : fileSystems) {
|
||||
if (fileSystem.exists(path)) {
|
||||
return fileSystem.getAttributes(path);
|
||||
}
|
||||
}
|
||||
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mkdir(final Path path) throws IOException {
|
||||
throw new IOException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileHandle open(final Path path, final int flags) throws IOException {
|
||||
if ((flags & FileMode.WRITE) != 0) {
|
||||
throw new IOException();
|
||||
}
|
||||
|
||||
for (final FileSystem fileSystem : fileSystems) {
|
||||
if (fileSystem.exists(path)) {
|
||||
return fileSystem.open(path, flags);
|
||||
}
|
||||
}
|
||||
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileHandle create(final Path path, final int flags) throws IOException {
|
||||
throw new IOException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unlink(final Path path) throws IOException {
|
||||
throw new IOException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rename(final Path oldPath, final Path newPath) throws IOException {
|
||||
throw new IOException();
|
||||
}
|
||||
}
|
||||
333
src/main/java/li/cil/oc2/common/vm/fs/ResourceFileSystem.java
Normal file
333
src/main/java/li/cil/oc2/common/vm/fs/ResourceFileSystem.java
Normal file
@@ -0,0 +1,333 @@
|
||||
package li.cil.oc2.common.vm.fs;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import li.cil.sedna.fs.*;
|
||||
import net.minecraft.resources.IResource;
|
||||
import net.minecraft.resources.IResourceManager;
|
||||
import net.minecraft.resources.data.IMetadataSectionSerializer;
|
||||
import net.minecraft.util.JSONUtils;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.nio.file.attribute.FileTime;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public final class ResourceFileSystem implements FileSystem {
|
||||
private final IResourceManager resourceManager;
|
||||
private final Node root;
|
||||
|
||||
public ResourceFileSystem(final IResourceManager resourceManager, final ResourceLocation rootLocation) {
|
||||
this.resourceManager = resourceManager;
|
||||
this.root = new Node(resourceManager);
|
||||
|
||||
final String rootLocationPath = rootLocation.getPath();
|
||||
final Collection<ResourceLocation> allLocations = resourceManager.getAllResourceLocations(rootLocationPath, s -> true);
|
||||
for (final ResourceLocation location : allLocations) {
|
||||
final String path = location.getPath();
|
||||
assert path.startsWith(rootLocationPath);
|
||||
final String localPath = path.substring(rootLocationPath.length());
|
||||
if (localPath.isEmpty()) {
|
||||
continue; // Skip the directory we're using as root.
|
||||
}
|
||||
|
||||
// Ensure we have a mutable list since insert removes the first item for each level.
|
||||
final ArrayList<String> pathParts = Arrays.stream(localPath.split("/")).filter(s -> !s.isEmpty())
|
||||
.collect(Collectors.toCollection(ArrayList::new));
|
||||
this.root.insert(pathParts, location);
|
||||
}
|
||||
|
||||
root.buildEntries();
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileSystemStats statfs() {
|
||||
return new FileSystemStats();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getUniqueId(final Path path) throws IOException {
|
||||
return getNodeOrThrow(path).hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists(final Path path) {
|
||||
return getNode(path) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirectory(final Path path) {
|
||||
final Node node = getNode(path);
|
||||
return node != null && node.isDirectory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWritable(final Path path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReadable(final Path path) {
|
||||
return exists(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExecutable(final Path path) {
|
||||
final Node node = getNode(path);
|
||||
return node != null && node.isExecutable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BasicFileAttributes getAttributes(final Path path) throws IOException {
|
||||
final Node node = getNodeOrThrow(path);
|
||||
return new BasicFileAttributes() {
|
||||
@Nullable
|
||||
@Override
|
||||
public FileTime lastModifiedTime() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public FileTime lastAccessTime() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public FileTime creationTime() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRegularFile() {
|
||||
return !node.isDirectory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirectory() {
|
||||
return node.isDirectory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSymbolicLink() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOther() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long size() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object fileKey() {
|
||||
return node;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mkdir(final Path path) throws IOException {
|
||||
throw new IOException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileHandle open(final Path path, final int flags) throws IOException {
|
||||
if ((flags & FileMode.WRITE) != 0) {
|
||||
throw new IOException();
|
||||
}
|
||||
|
||||
final Node node = getNodeOrThrow(path);
|
||||
if (node.isDirectory) {
|
||||
return new FileHandle() {
|
||||
@Override
|
||||
public int read(final long offset, final ByteBuffer buffer) throws IOException {
|
||||
throw new IOException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int write(final long offset, final ByteBuffer buffer) throws IOException {
|
||||
throw new IOException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DirectoryEntry> readdir() {
|
||||
return node.entries;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
};
|
||||
} else {
|
||||
assert node.location != null;
|
||||
|
||||
// Resource InputStreams don't always support seeking, so we have to copy to memory :/
|
||||
final InputStream stream = resourceManager.getResource(node.location).getInputStream();
|
||||
final ByteBuffer data = ByteBuffer.wrap(IOUtils.toByteArray(stream));
|
||||
stream.close();
|
||||
return new FileHandle() {
|
||||
@Override
|
||||
public int read(final long offset, final ByteBuffer buffer) throws IOException {
|
||||
if (offset < 0 || offset > data.capacity()) {
|
||||
throw new IOException();
|
||||
}
|
||||
data.position((int) offset);
|
||||
final int count = Math.min(buffer.remaining(), data.capacity() - data.position());
|
||||
data.limit(data.position() + count);
|
||||
buffer.put(data);
|
||||
return count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int write(final long offset, final ByteBuffer buffer) throws IOException {
|
||||
throw new IOException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DirectoryEntry> readdir() throws IOException {
|
||||
throw new IOException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
stream.close();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileHandle create(final Path path, final int flags) throws IOException {
|
||||
throw new IOException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unlink(final Path path) throws IOException {
|
||||
throw new IOException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rename(final Path oldPath, final Path newPath) throws IOException {
|
||||
throw new IOException();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Node getNode(final Path path) {
|
||||
Node node = root;
|
||||
for (final String part : path.getParts()) {
|
||||
final Node child = node.children.get(part);
|
||||
if (child == null) {
|
||||
return null;
|
||||
}
|
||||
node = child;
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
private Node getNodeOrThrow(final Path path) throws IOException {
|
||||
final Node node = getNode(path);
|
||||
if (node == null) {
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
private static final class Node {
|
||||
public final IResourceManager resourceManager;
|
||||
@Nullable public final ResourceLocation location;
|
||||
public final boolean isExecutable;
|
||||
public final boolean isDirectory;
|
||||
public final HashMap<String, Node> children = new HashMap<>();
|
||||
public final ArrayList<DirectoryEntry> entries = new ArrayList<>();
|
||||
|
||||
public Node(final IResourceManager resourceManager) {
|
||||
this(resourceManager, null);
|
||||
}
|
||||
|
||||
public Node(final IResourceManager resourceManager, @Nullable final ResourceLocation location) {
|
||||
this.resourceManager = resourceManager;
|
||||
this.location = location;
|
||||
|
||||
boolean isDirectory;
|
||||
boolean isExecutable;
|
||||
if (location != null) {
|
||||
try (final IResource resource = resourceManager.getResource(location)) {
|
||||
// Successfully retrieved resource, meaning it's a file.
|
||||
final FileAttributesMetadataSection metadata = resource.getMetadata(FileAttributesMetadataSection.SERIALIZER);
|
||||
isExecutable = metadata != null && metadata.isExecutable();
|
||||
isDirectory = false;
|
||||
} catch (final IOException e) {
|
||||
isExecutable = true;
|
||||
isDirectory = true;
|
||||
}
|
||||
} else {
|
||||
isExecutable = true;
|
||||
isDirectory = true;
|
||||
}
|
||||
|
||||
this.isExecutable = isExecutable;
|
||||
this.isDirectory = isDirectory;
|
||||
}
|
||||
|
||||
public void insert(final List<String> path, final ResourceLocation location) {
|
||||
final String head = path.remove(0);
|
||||
if (path.isEmpty()) {
|
||||
final Node node = new Node(resourceManager, location);
|
||||
children.put(head, node);
|
||||
} else {
|
||||
children.computeIfAbsent(head, unused -> new Node(resourceManager)).insert(path, location);
|
||||
}
|
||||
}
|
||||
|
||||
public void buildEntries() {
|
||||
children.forEach((name, child) -> {
|
||||
final DirectoryEntry directoryEntry = new DirectoryEntry();
|
||||
directoryEntry.name = name;
|
||||
directoryEntry.type = child.isDirectory ? FileType.DIRECTORY : FileType.FILE;
|
||||
entries.add(directoryEntry);
|
||||
|
||||
child.buildEntries();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static final class FileAttributesMetadataSectionSerializer implements IMetadataSectionSerializer<FileAttributesMetadataSection> {
|
||||
@Override
|
||||
public String getSectionName() {
|
||||
return "attributes";
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileAttributesMetadataSection deserialize(final JsonObject json) {
|
||||
final boolean isExecutable = JSONUtils.getBoolean(json, "is_executable");
|
||||
return new FileAttributesMetadataSection(isExecutable);
|
||||
}
|
||||
}
|
||||
|
||||
private static class FileAttributesMetadataSection {
|
||||
public static final FileAttributesMetadataSectionSerializer SERIALIZER = new FileAttributesMetadataSectionSerializer();
|
||||
|
||||
public final boolean isExecutable;
|
||||
|
||||
public FileAttributesMetadataSection(final boolean isExecutable) {
|
||||
this.isExecutable = isExecutable;
|
||||
}
|
||||
|
||||
public boolean isExecutable() {
|
||||
return isExecutable;
|
||||
}
|
||||
}
|
||||
}
|
||||
7
src/main/java/li/cil/oc2/common/vm/fs/package-info.java
Normal file
7
src/main/java/li/cil/oc2/common/vm/fs/package-info.java
Normal file
@@ -0,0 +1,7 @@
|
||||
@ParametersAreNonnullByDefault
|
||||
@MethodsReturnNonnullByDefault
|
||||
package li.cil.oc2.common.vm.fs;
|
||||
|
||||
import mcp.MethodsReturnNonnullByDefault;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
4
src/main/resources/data/oc2/file_systems/scripts.fs.json
Normal file
4
src/main/resources/data/oc2/file_systems/scripts.fs.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"type": "virtio-9p",
|
||||
"location": "oc2:file_systems/scripts"
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/lua
|
||||
|
||||
local devices = require("devices")
|
||||
local json = require("cjson").new()
|
||||
for _,device in ipairs(devices:list()) do
|
||||
local line = device.deviceId .. "\t"
|
||||
local isFirstTypeName = true
|
||||
table.sort(device.typeNames)
|
||||
for _,typeName in ipairs(device.typeNames) do
|
||||
if isFirstTypeName then
|
||||
isFirstTypeName = false
|
||||
else
|
||||
line = line .. ", "
|
||||
end
|
||||
line = line .. typeName
|
||||
end
|
||||
print(line)
|
||||
end
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"attributes": {
|
||||
"is_executable": true
|
||||
}
|
||||
}
|
||||
209
src/main/resources/data/oc2/file_systems/scripts/lua/devices.lua
Normal file
209
src/main/resources/data/oc2/file_systems/scripts/lua/devices.lua
Normal file
@@ -0,0 +1,209 @@
|
||||
local fcntl = require("posix.fcntl")
|
||||
local unistd = require("posix.unistd")
|
||||
local poll = require("posix.poll")
|
||||
local cjson = require("cjson").new()
|
||||
|
||||
local Device = {}
|
||||
Device.__index = function(_, key)
|
||||
return rawget(Device, key) or function(self, ...)
|
||||
return Device.invoke(self, key, ...)
|
||||
end
|
||||
end
|
||||
Device.__tostring = function(self)
|
||||
local doc = ""
|
||||
|
||||
if not rawget(self, "methods") then
|
||||
self.methods = self.bus:methods(self.deviceId)
|
||||
end
|
||||
|
||||
for _, method in ipairs(self.methods) do
|
||||
if method.description then
|
||||
doc = doc .. method.description .. "\n"
|
||||
end
|
||||
|
||||
if method.parameters then
|
||||
local i = 1
|
||||
for _, p in ipairs(method.parameters) do
|
||||
if p.description then
|
||||
doc = doc .. " "
|
||||
if p.name then
|
||||
doc = doc .. p.name
|
||||
else
|
||||
doc = doc .. "arg" .. i
|
||||
end
|
||||
doc = doc .. " " .. p.description .. "\n"
|
||||
end
|
||||
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
|
||||
doc = doc .. method.name .. "("
|
||||
if method.parameters then
|
||||
local i = 1
|
||||
for _, p in ipairs(method.parameters) do
|
||||
if i > 1 then
|
||||
doc = doc .. ", "
|
||||
end
|
||||
if p.name then
|
||||
doc = doc .. p.name
|
||||
else
|
||||
doc = doc .. "arg" .. i
|
||||
end
|
||||
doc = doc .. ": " .. p.type
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
doc = doc .. "): " .. method.returnType .. "\n"
|
||||
end
|
||||
|
||||
return doc
|
||||
end
|
||||
|
||||
function Device:new(bus, device)
|
||||
device.bus = bus
|
||||
return setmetatable(device, self)
|
||||
end
|
||||
|
||||
function Device:invoke(methodName, ...)
|
||||
return self.bus:invoke(self.deviceId, methodName, ...)
|
||||
end
|
||||
|
||||
local DeviceBus = {}
|
||||
DeviceBus.__index = DeviceBus
|
||||
|
||||
local message_delimiter = string.char(0)
|
||||
|
||||
local function parseError(result)
|
||||
if result.type == "error" then
|
||||
return result.data
|
||||
else
|
||||
return "unexpected message type " .. result.type
|
||||
end
|
||||
end
|
||||
|
||||
local function readOne(fd)
|
||||
local result, status, errnum = poll.rpoll(fd, 10)
|
||||
if result == 1 then
|
||||
return unistd.read(fd, 1)
|
||||
else
|
||||
return result, status, errnum
|
||||
end
|
||||
end
|
||||
|
||||
local function readMessage(device)
|
||||
local value
|
||||
local message = ""
|
||||
while true do
|
||||
value = readOne(device.fd)
|
||||
if not value then
|
||||
unistd.sleep(1)
|
||||
else
|
||||
if value == message_delimiter or value == 0 then
|
||||
if message:match("%S") ~= nil then
|
||||
local ok, result = pcall(cjson.decode, message)
|
||||
if ok then
|
||||
return result
|
||||
end
|
||||
else
|
||||
message = ""
|
||||
end
|
||||
else
|
||||
message = message .. value
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function writeMessage(device, data)
|
||||
local message = cjson.encode(data)
|
||||
return unistd.write(device.fd,
|
||||
message_delimiter ..
|
||||
message ..
|
||||
message_delimiter)
|
||||
end
|
||||
|
||||
function DeviceBus:new(path)
|
||||
local fd, status = fcntl.open(path, fcntl.O_RDWR)
|
||||
if not fd then
|
||||
return nil, status
|
||||
end
|
||||
|
||||
os.execute("stty -F " .. path .. " raw -echo")
|
||||
|
||||
return setmetatable({ fd = fd }, self)
|
||||
end
|
||||
|
||||
function DeviceBus:close()
|
||||
unistd.close(self.fd)
|
||||
end
|
||||
|
||||
function DeviceBus:list()
|
||||
writeMessage(self, { type = "list" })
|
||||
local result = readMessage(self)
|
||||
if result.type == "list" then
|
||||
return result.data
|
||||
else
|
||||
return nil, parseError(result)
|
||||
end
|
||||
end
|
||||
|
||||
function DeviceBus:get(deviceId)
|
||||
local devices, status = self:list()
|
||||
if not devices then
|
||||
return nil, status
|
||||
end
|
||||
|
||||
for _, device in ipairs(devices) do
|
||||
if device.deviceId == deviceId then
|
||||
return Device:new(self, device)
|
||||
end
|
||||
end
|
||||
|
||||
return nil, "no device with id [" .. deviceId .. "]"
|
||||
end
|
||||
|
||||
function DeviceBus:find(deviceTypeName)
|
||||
local devices, status = self:list()
|
||||
if not devices then
|
||||
return nil, status
|
||||
end
|
||||
|
||||
for _, device in ipairs(devices) do
|
||||
if device.typeNames then
|
||||
for _, typeName in ipairs(device.typeNames) do
|
||||
if typeName == deviceTypeName then
|
||||
return Device:new(self, device)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return nil, "no device of type [" .. deviceTypeName .. "]"
|
||||
end
|
||||
|
||||
function DeviceBus:methods(deviceId)
|
||||
writeMessage(self, { type = "methods", data = deviceId })
|
||||
local result = readMessage(self)
|
||||
if result.type == "methods" then
|
||||
return result.data
|
||||
else
|
||||
error(parseError(result))
|
||||
end
|
||||
end
|
||||
|
||||
function DeviceBus:invoke(deviceId, methodName, ...)
|
||||
writeMessage(self, { type = "invoke", data = {
|
||||
deviceId = deviceId,
|
||||
name = methodName,
|
||||
parameters = { ... }
|
||||
} })
|
||||
local result = readMessage(self)
|
||||
if result.type == "result" then
|
||||
return result.data
|
||||
else
|
||||
error(parseError(result))
|
||||
end
|
||||
end
|
||||
|
||||
return DeviceBus:new("/dev/hvc0")
|
||||
Reference in New Issue
Block a user