mirror of
https://github.com/usatiuk/dhfs.git
synced 2025-10-29 04:57:48 +01:00
Some javadocs + CI
This commit is contained in:
7
.github/workflows/server.yml
vendored
7
.github/workflows/server.yml
vendored
@@ -47,7 +47,7 @@ jobs:
|
|||||||
run: cd thirdparty/lazyfs/ && ./build.sh
|
run: cd thirdparty/lazyfs/ && ./build.sh
|
||||||
|
|
||||||
- name: Test with Maven
|
- name: Test with Maven
|
||||||
run: cd dhfs-parent && mvn -T $(nproc) --batch-mode --update-snapshots package verify
|
run: cd dhfs-parent && mvn -T $(nproc) --batch-mode --update-snapshots package verify javadoc:aggregate
|
||||||
|
|
||||||
# - name: Build with Maven
|
# - name: Build with Maven
|
||||||
# run: cd dhfs-parent && mvn --batch-mode --update-snapshots package # -Dquarkus.log.category.\"com.usatiuk.dhfs\".min-level=DEBUG
|
# run: cd dhfs-parent && mvn --batch-mode --update-snapshots package # -Dquarkus.log.category.\"com.usatiuk.dhfs\".min-level=DEBUG
|
||||||
@@ -57,6 +57,11 @@ jobs:
|
|||||||
name: DHFS Server Package
|
name: DHFS Server Package
|
||||||
path: dhfs-parent/dhfs-fuse/target/quarkus-app
|
path: dhfs-parent/dhfs-fuse/target/quarkus-app
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: DHFS Javadocs
|
||||||
|
path: dhfs-parent/target/reports/apidocs/
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
if: ${{ always() }}
|
if: ${{ always() }}
|
||||||
with:
|
with:
|
||||||
|
|||||||
2
dhfs-parent/.gitignore
vendored
2
dhfs-parent/.gitignore
vendored
@@ -41,3 +41,5 @@ nb-configuration.xml
|
|||||||
|
|
||||||
# Plugin directory
|
# Plugin directory
|
||||||
/.quarkus/cli/plugins/
|
/.quarkus/cli/plugins/
|
||||||
|
|
||||||
|
.jqwik-database
|
||||||
@@ -5,6 +5,11 @@ import com.usatiuk.dhfs.remoteobj.JDataRemote;
|
|||||||
import com.usatiuk.dhfs.remoteobj.JDataRemoteDto;
|
import com.usatiuk.dhfs.remoteobj.JDataRemoteDto;
|
||||||
import com.usatiuk.objects.JObjectKey;
|
import com.usatiuk.objects.JObjectKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ChunkData is a data structure that represents an immutable binary blob
|
||||||
|
* @param key unique key
|
||||||
|
* @param data binary data
|
||||||
|
*/
|
||||||
public record ChunkData(JObjectKey key, ByteString data) implements JDataRemote, JDataRemoteDto {
|
public record ChunkData(JObjectKey key, ByteString data) implements JDataRemote, JDataRemoteDto {
|
||||||
@Override
|
@Override
|
||||||
public int estimateSize() {
|
public int estimateSize() {
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
package com.usatiuk.dhfsfs.objects;
|
|
||||||
|
|
||||||
import com.usatiuk.dhfs.ProtoSerializer;
|
|
||||||
import com.usatiuk.dhfs.persistence.ChunkDataP;
|
|
||||||
import com.usatiuk.dhfs.persistence.JObjectKeyP;
|
|
||||||
import com.usatiuk.objects.JObjectKey;
|
|
||||||
import jakarta.inject.Singleton;
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
public class ChunkDataProtoSerializer implements ProtoSerializer<ChunkDataP, ChunkData> {
|
|
||||||
@Override
|
|
||||||
public ChunkData deserialize(ChunkDataP message) {
|
|
||||||
return new ChunkData(
|
|
||||||
JObjectKey.of(message.getKey().getName()),
|
|
||||||
message.getData()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChunkDataP serialize(ChunkData object) {
|
|
||||||
return ChunkDataP.newBuilder()
|
|
||||||
.setKey(JObjectKeyP.newBuilder().setName(object.key().value()).build())
|
|
||||||
.setData(object.data())
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,6 +9,14 @@ import com.usatiuk.objects.JObjectKey;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File is a data structure that represents a file in the file system
|
||||||
|
* @param key unique key
|
||||||
|
* @param mode file mode
|
||||||
|
* @param cTime creation time
|
||||||
|
* @param mTime modification time
|
||||||
|
* @param symlink true if the file is a symlink, false otherwise
|
||||||
|
*/
|
||||||
public record File(JObjectKey key, long mode, long cTime, long mTime,
|
public record File(JObjectKey key, long mode, long cTime, long mTime,
|
||||||
boolean symlink
|
boolean symlink
|
||||||
) implements JDataRemote, JMapHolder<JMapLongKey> {
|
) implements JDataRemote, JMapHolder<JMapLongKey> {
|
||||||
|
|||||||
@@ -7,6 +7,11 @@ import org.apache.commons.lang3.tuple.Pair;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FileDto is a data transfer object that contains a file and its chunks.
|
||||||
|
* @param file the file
|
||||||
|
* @param chunks the list of chunks, each represented as a pair of a long and a JObjectKey
|
||||||
|
*/
|
||||||
public record FileDto(File file, List<Pair<Long, JObjectKey>> chunks) implements JDataRemoteDto {
|
public record FileDto(File file, List<Pair<Long, JObjectKey>> chunks) implements JDataRemoteDto {
|
||||||
@Override
|
@Override
|
||||||
public Class<? extends JDataRemote> objClass() {
|
public Class<? extends JDataRemote> objClass() {
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ import com.usatiuk.dhfs.syncmap.DtoMapper;
|
|||||||
import jakarta.enterprise.context.ApplicationScoped;
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps a {@link File} object to a {@link FileDto} object and vice versa.
|
||||||
|
*/
|
||||||
@ApplicationScoped
|
@ApplicationScoped
|
||||||
public class FileDtoMapper implements DtoMapper<File, FileDto> {
|
public class FileDtoMapper implements DtoMapper<File, FileDto> {
|
||||||
@Inject
|
@Inject
|
||||||
|
|||||||
@@ -10,11 +10,20 @@ import org.apache.commons.lang3.tuple.Pair;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class for working with files.
|
||||||
|
*/
|
||||||
@ApplicationScoped
|
@ApplicationScoped
|
||||||
public class FileHelper {
|
public class FileHelper {
|
||||||
@Inject
|
@Inject
|
||||||
JMapHelper jMapHelper;
|
JMapHelper jMapHelper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the chunks of a file.
|
||||||
|
* Transaction is expected to be already started.
|
||||||
|
* @param file the file to get chunks from
|
||||||
|
* @return a list of pairs of chunk offset and chunk key
|
||||||
|
*/
|
||||||
public List<Pair<Long, JObjectKey>> getChunks(File file) {
|
public List<Pair<Long, JObjectKey>> getChunks(File file) {
|
||||||
ArrayList<Pair<Long, JObjectKey>> chunks = new ArrayList<>();
|
ArrayList<Pair<Long, JObjectKey>> chunks = new ArrayList<>();
|
||||||
try (var it = jMapHelper.getIterator(file)) {
|
try (var it = jMapHelper.getIterator(file)) {
|
||||||
@@ -26,6 +35,13 @@ public class FileHelper {
|
|||||||
return List.copyOf(chunks);
|
return List.copyOf(chunks);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace the chunks of a file.
|
||||||
|
* All previous chunks will be deleted.
|
||||||
|
* Transaction is expected to be already started.
|
||||||
|
* @param file the file to replace chunks in
|
||||||
|
* @param chunks the list of pairs of chunk offset and chunk key
|
||||||
|
*/
|
||||||
public void replaceChunks(File file, List<Pair<Long, JObjectKey>> chunks) {
|
public void replaceChunks(File file, List<Pair<Long, JObjectKey>> chunks) {
|
||||||
jMapHelper.deleteAll(file);
|
jMapHelper.deleteAll(file);
|
||||||
|
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
package com.usatiuk.dhfsfs.objects;
|
|
||||||
|
|
||||||
import com.usatiuk.dhfs.ProtoSerializer;
|
|
||||||
import com.usatiuk.dhfs.persistence.FileDtoP;
|
|
||||||
import com.usatiuk.utils.SerializationHelper;
|
|
||||||
import jakarta.inject.Singleton;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
public class FileProtoSerializer implements ProtoSerializer<FileDtoP, FileDto> {
|
|
||||||
@Override
|
|
||||||
public FileDto deserialize(FileDtoP message) {
|
|
||||||
try (var is = message.getSerializedData().newInput()) {
|
|
||||||
return SerializationHelper.deserialize(is);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public FileDtoP serialize(FileDto object) {
|
|
||||||
return FileDtoP.newBuilder().setSerializedData(SerializationHelper.serialize(object)).build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -23,6 +23,9 @@ import javax.annotation.Nullable;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles synchronization of file objects.
|
||||||
|
*/
|
||||||
@ApplicationScoped
|
@ApplicationScoped
|
||||||
public class FileSyncHandler implements ObjSyncHandler<File, FileDto> {
|
public class FileSyncHandler implements ObjSyncHandler<File, FileDto> {
|
||||||
@Inject
|
@Inject
|
||||||
@@ -45,6 +48,14 @@ public class FileSyncHandler implements ObjSyncHandler<File, FileDto> {
|
|||||||
return jKleppmannTreeManager.getTree(JObjectKey.of("fs")).orElseThrow();
|
return jKleppmannTreeManager.getTree(JObjectKey.of("fs")).orElseThrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve conflict between two file versions, update the file in storage and create a conflict file.
|
||||||
|
*
|
||||||
|
* @param from the peer that sent the update
|
||||||
|
* @param key the key of the file
|
||||||
|
* @param receivedChangelog the changelog of the received file
|
||||||
|
* @param receivedData the received file data
|
||||||
|
*/
|
||||||
private void resolveConflict(PeerId from, JObjectKey key, PMap<PeerId, Long> receivedChangelog,
|
private void resolveConflict(PeerId from, JObjectKey key, PMap<PeerId, Long> receivedChangelog,
|
||||||
@Nullable FileDto receivedData) {
|
@Nullable FileDto receivedData) {
|
||||||
var oursCurMeta = curTx.get(RemoteObjectMeta.class, key).orElse(null);
|
var oursCurMeta = curTx.get(RemoteObjectMeta.class, key).orElse(null);
|
||||||
|
|||||||
@@ -6,6 +6,10 @@ import com.usatiuk.objects.JObjectKey;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JKleppmannTreeNodeMetaDirectory is a record that represents a directory in the JKleppmann tree.
|
||||||
|
* @param name the name of the directory
|
||||||
|
*/
|
||||||
public record JKleppmannTreeNodeMetaDirectory(String name) implements JKleppmannTreeNodeMeta {
|
public record JKleppmannTreeNodeMetaDirectory(String name) implements JKleppmannTreeNodeMeta {
|
||||||
public JKleppmannTreeNodeMeta withName(String name) {
|
public JKleppmannTreeNodeMeta withName(String name) {
|
||||||
return new JKleppmannTreeNodeMetaDirectory(name);
|
return new JKleppmannTreeNodeMetaDirectory(name);
|
||||||
|
|||||||
@@ -6,6 +6,11 @@ import com.usatiuk.objects.JObjectKey;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JKleppmannTreeNodeMetaFile is a record that represents a file in the JKleppmann tree.
|
||||||
|
* @param name the name of the file
|
||||||
|
* @param fileIno a reference to the `File` object
|
||||||
|
*/
|
||||||
public record JKleppmannTreeNodeMetaFile(String name, JObjectKey fileIno) implements JKleppmannTreeNodeMeta {
|
public record JKleppmannTreeNodeMetaFile(String name, JObjectKey fileIno) implements JKleppmannTreeNodeMeta {
|
||||||
@Override
|
@Override
|
||||||
public JKleppmannTreeNodeMeta withName(String name) {
|
public JKleppmannTreeNodeMeta withName(String name) {
|
||||||
|
|||||||
@@ -39,6 +39,9 @@ import java.nio.file.Path;
|
|||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.StreamSupport;
|
import java.util.stream.StreamSupport;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Actual filesystem implementation.
|
||||||
|
*/
|
||||||
@ApplicationScoped
|
@ApplicationScoped
|
||||||
public class DhfsFileService {
|
public class DhfsFileService {
|
||||||
@ConfigProperty(name = "dhfs.files.target_chunk_alignment")
|
@ConfigProperty(name = "dhfs.files.target_chunk_alignment")
|
||||||
@@ -71,6 +74,12 @@ public class DhfsFileService {
|
|||||||
return jKleppmannTreeManager.getTree(JObjectKey.of("fs"), () -> new JKleppmannTreeNodeMetaDirectory(""));
|
return jKleppmannTreeManager.getTree(JObjectKey.of("fs"), () -> new JKleppmannTreeNodeMetaDirectory(""));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new chunk with the given data and a new unique ID.
|
||||||
|
*
|
||||||
|
* @param bytes the data to store in the chunk
|
||||||
|
* @return the created chunk
|
||||||
|
*/
|
||||||
private ChunkData createChunk(ByteString bytes) {
|
private ChunkData createChunk(ByteString bytes) {
|
||||||
var newChunk = new ChunkData(JObjectKey.of(UUID.randomUUID().toString()), bytes);
|
var newChunk = new ChunkData(JObjectKey.of(UUID.randomUUID().toString()), bytes);
|
||||||
remoteTx.putDataNew(newChunk);
|
remoteTx.putDataNew(newChunk);
|
||||||
@@ -82,14 +91,7 @@ public class DhfsFileService {
|
|||||||
getTree();
|
getTree();
|
||||||
}
|
}
|
||||||
|
|
||||||
private JKleppmannTreeNode getDirEntryW(String name) {
|
private JKleppmannTreeNode getDirEntry(String name) {
|
||||||
var res = getTree().traverse(StreamSupport.stream(Path.of(name).spliterator(), false).map(p -> p.toString()).toList());
|
|
||||||
if (res == null) throw new StatusRuntimeExceptionNoStacktrace(Status.NOT_FOUND);
|
|
||||||
var ret = curTx.get(JKleppmannTreeNodeHolder.class, res).map(JKleppmannTreeNodeHolder::node).orElseThrow(() -> new StatusRuntimeException(Status.NOT_FOUND.withDescription("Tree node exists but not found as jObject: " + name)));
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
private JKleppmannTreeNode getDirEntryR(String name) {
|
|
||||||
var res = getTree().traverse(StreamSupport.stream(Path.of(name).spliterator(), false).map(p -> p.toString()).toList());
|
var res = getTree().traverse(StreamSupport.stream(Path.of(name).spliterator(), false).map(p -> p.toString()).toList());
|
||||||
if (res == null) throw new StatusRuntimeExceptionNoStacktrace(Status.NOT_FOUND);
|
if (res == null) throw new StatusRuntimeExceptionNoStacktrace(Status.NOT_FOUND);
|
||||||
var ret = curTx.get(JKleppmannTreeNodeHolder.class, res).map(JKleppmannTreeNodeHolder::node).orElseThrow(() -> new StatusRuntimeException(Status.NOT_FOUND.withDescription("Tree node exists but not found as jObject: " + name)));
|
var ret = curTx.get(JKleppmannTreeNodeHolder.class, res).map(JKleppmannTreeNodeHolder::node).orElseThrow(() -> new StatusRuntimeException(Status.NOT_FOUND.withDescription("Tree node exists but not found as jObject: " + name)));
|
||||||
@@ -103,6 +105,11 @@ public class DhfsFileService {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the attributes of a file or directory.
|
||||||
|
* @param uuid the UUID of the file or directory
|
||||||
|
* @return the attributes of the file or directory
|
||||||
|
*/
|
||||||
public Optional<GetattrRes> getattr(JObjectKey uuid) {
|
public Optional<GetattrRes> getattr(JObjectKey uuid) {
|
||||||
return jObjectTxManager.executeTx(() -> {
|
return jObjectTxManager.executeTx(() -> {
|
||||||
var ref = curTx.get(JData.class, uuid).orElse(null);
|
var ref = curTx.get(JData.class, uuid).orElse(null);
|
||||||
@@ -124,10 +131,15 @@ public class DhfsFileService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to resolve a path to a file or directory.
|
||||||
|
* @param name the path to resolve
|
||||||
|
* @return the key of the file or directory, or an empty optional if it does not exist
|
||||||
|
*/
|
||||||
public Optional<JObjectKey> open(String name) {
|
public Optional<JObjectKey> open(String name) {
|
||||||
return jObjectTxManager.executeTx(() -> {
|
return jObjectTxManager.executeTx(() -> {
|
||||||
try {
|
try {
|
||||||
var ret = getDirEntryR(name);
|
var ret = getDirEntry(name);
|
||||||
return switch (ret.meta()) {
|
return switch (ret.meta()) {
|
||||||
case JKleppmannTreeNodeMetaFile f -> Optional.of(f.fileIno());
|
case JKleppmannTreeNodeMetaFile f -> Optional.of(f.fileIno());
|
||||||
case JKleppmannTreeNodeMetaDirectory f -> Optional.of(ret.key());
|
case JKleppmannTreeNodeMetaDirectory f -> Optional.of(ret.key());
|
||||||
@@ -147,10 +159,16 @@ public class DhfsFileService {
|
|||||||
throw new StatusRuntimeExceptionNoStacktrace(Status.INVALID_ARGUMENT.withDescription("Not a directory: " + entry.key()));
|
throw new StatusRuntimeExceptionNoStacktrace(Status.INVALID_ARGUMENT.withDescription("Not a directory: " + entry.key()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new file with the given name and mode.
|
||||||
|
* @param name the name of the file
|
||||||
|
* @param mode the mode of the file
|
||||||
|
* @return the key of the created file
|
||||||
|
*/
|
||||||
public Optional<JObjectKey> create(String name, long mode) {
|
public Optional<JObjectKey> create(String name, long mode) {
|
||||||
return jObjectTxManager.executeTx(() -> {
|
return jObjectTxManager.executeTx(() -> {
|
||||||
Path path = Path.of(name);
|
Path path = Path.of(name);
|
||||||
var parent = getDirEntryW(path.getParent().toString());
|
var parent = getDirEntry(path.getParent().toString());
|
||||||
|
|
||||||
ensureDir(parent);
|
ensureDir(parent);
|
||||||
|
|
||||||
@@ -171,9 +189,14 @@ public class DhfsFileService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//FIXME: Slow..
|
/**
|
||||||
|
* Get the parent directory of a file or directory.
|
||||||
|
* @param ino the key of the file or directory
|
||||||
|
* @return the parent directory
|
||||||
|
*/
|
||||||
public Pair<String, JObjectKey> inoToParent(JObjectKey ino) {
|
public Pair<String, JObjectKey> inoToParent(JObjectKey ino) {
|
||||||
return jObjectTxManager.executeTx(() -> {
|
return jObjectTxManager.executeTx(() -> {
|
||||||
|
// FIXME: Slow
|
||||||
return getTree().findParent(w -> {
|
return getTree().findParent(w -> {
|
||||||
if (w.meta() instanceof JKleppmannTreeNodeMetaFile f)
|
if (w.meta() instanceof JKleppmannTreeNodeMetaFile f)
|
||||||
return f.fileIno().equals(ino);
|
return f.fileIno().equals(ino);
|
||||||
@@ -182,20 +205,31 @@ public class DhfsFileService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new directory with the given name and mode.
|
||||||
|
* @param name the name of the directory
|
||||||
|
* @param mode the mode of the directory
|
||||||
|
*/
|
||||||
public void mkdir(String name, long mode) {
|
public void mkdir(String name, long mode) {
|
||||||
jObjectTxManager.executeTx(() -> {
|
jObjectTxManager.executeTx(() -> {
|
||||||
Path path = Path.of(name);
|
Path path = Path.of(name);
|
||||||
var parent = getDirEntryW(path.getParent().toString());
|
var parent = getDirEntry(path.getParent().toString());
|
||||||
ensureDir(parent);
|
ensureDir(parent);
|
||||||
|
|
||||||
String dname = path.getFileName().toString();
|
String dname = path.getFileName().toString();
|
||||||
|
|
||||||
Log.debug("Creating directory " + name);
|
Log.debug("Creating directory " + name);
|
||||||
|
|
||||||
|
// TODO: No modes for directories yet
|
||||||
getTree().move(parent.key(), new JKleppmannTreeNodeMetaDirectory(dname), getTree().getNewNodeId());
|
getTree().move(parent.key(), new JKleppmannTreeNodeMetaDirectory(dname), getTree().getNewNodeId());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unlink a file or directory.
|
||||||
|
* @param name the name of the file or directory
|
||||||
|
* @throws DirectoryNotEmptyException if the directory is not empty and recursive delete is not allowed
|
||||||
|
*/
|
||||||
public void unlink(String name) {
|
public void unlink(String name) {
|
||||||
jObjectTxManager.executeTx(() -> {
|
jObjectTxManager.executeTx(() -> {
|
||||||
var node = getDirEntryOpt(name).orElse(null);
|
var node = getDirEntryOpt(name).orElse(null);
|
||||||
@@ -209,13 +243,19 @@ public class DhfsFileService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rename a file or directory.
|
||||||
|
* @param from the old name
|
||||||
|
* @param to the new name
|
||||||
|
* @return true if the rename was successful, false otherwise
|
||||||
|
*/
|
||||||
public Boolean rename(String from, String to) {
|
public Boolean rename(String from, String to) {
|
||||||
return jObjectTxManager.executeTx(() -> {
|
return jObjectTxManager.executeTx(() -> {
|
||||||
var node = getDirEntryW(from);
|
var node = getDirEntry(from);
|
||||||
JKleppmannTreeNodeMeta meta = node.meta();
|
JKleppmannTreeNodeMeta meta = node.meta();
|
||||||
|
|
||||||
var toPath = Path.of(to);
|
var toPath = Path.of(to);
|
||||||
var toDentry = getDirEntryW(toPath.getParent().toString());
|
var toDentry = getDirEntry(toPath.getParent().toString());
|
||||||
ensureDir(toDentry);
|
ensureDir(toDentry);
|
||||||
|
|
||||||
getTree().move(toDentry.key(), meta.withName(toPath.getFileName().toString()), node.key());
|
getTree().move(toDentry.key(), meta.withName(toPath.getFileName().toString()), node.key());
|
||||||
@@ -223,6 +263,12 @@ public class DhfsFileService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the mode of a file or directory.
|
||||||
|
* @param uuid the ID of the file or directory
|
||||||
|
* @param mode the new mode
|
||||||
|
* @return true if the mode was changed successfully, false otherwise
|
||||||
|
*/
|
||||||
public Boolean chmod(JObjectKey uuid, long mode) {
|
public Boolean chmod(JObjectKey uuid, long mode) {
|
||||||
return jObjectTxManager.executeTx(() -> {
|
return jObjectTxManager.executeTx(() -> {
|
||||||
var dent = curTx.get(JData.class, uuid).orElseThrow(() -> new StatusRuntimeExceptionNoStacktrace(Status.NOT_FOUND));
|
var dent = curTx.get(JData.class, uuid).orElseThrow(() -> new StatusRuntimeExceptionNoStacktrace(Status.NOT_FOUND));
|
||||||
@@ -243,9 +289,14 @@ public class DhfsFileService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the contents of a directory.
|
||||||
|
* @param name the path of the directory
|
||||||
|
* @return an iterable of the names of the files in the directory
|
||||||
|
*/
|
||||||
public Iterable<String> readDir(String name) {
|
public Iterable<String> readDir(String name) {
|
||||||
return jObjectTxManager.executeTx(() -> {
|
return jObjectTxManager.executeTx(() -> {
|
||||||
var found = getDirEntryW(name);
|
var found = getDirEntry(name);
|
||||||
|
|
||||||
if (!(found.meta() instanceof JKleppmannTreeNodeMetaDirectory md))
|
if (!(found.meta() instanceof JKleppmannTreeNodeMetaDirectory md))
|
||||||
throw new StatusRuntimeException(Status.INVALID_ARGUMENT);
|
throw new StatusRuntimeException(Status.INVALID_ARGUMENT);
|
||||||
@@ -254,6 +305,13 @@ public class DhfsFileService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the contents of a file.
|
||||||
|
* @param fileUuid the ID of the file
|
||||||
|
* @param offset the offset to start reading from
|
||||||
|
* @param length the number of bytes to read
|
||||||
|
* @return the contents of the file as a ByteString
|
||||||
|
*/
|
||||||
public ByteString read(JObjectKey fileUuid, long offset, int length) {
|
public ByteString read(JObjectKey fileUuid, long offset, int length) {
|
||||||
return jObjectTxManager.executeTx(() -> {
|
return jObjectTxManager.executeTx(() -> {
|
||||||
if (length < 0)
|
if (length < 0)
|
||||||
@@ -315,6 +373,11 @@ public class DhfsFileService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the size of a file.
|
||||||
|
* @param uuid the ID of the file
|
||||||
|
* @return the size of the file
|
||||||
|
*/
|
||||||
private ByteString readChunk(JObjectKey uuid) {
|
private ByteString readChunk(JObjectKey uuid) {
|
||||||
var chunkRead = remoteTx.getData(ChunkData.class, uuid).orElse(null);
|
var chunkRead = remoteTx.getData(ChunkData.class, uuid).orElse(null);
|
||||||
|
|
||||||
@@ -326,6 +389,11 @@ public class DhfsFileService {
|
|||||||
return chunkRead.data();
|
return chunkRead.data();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the size of a chunk.
|
||||||
|
* @param uuid the ID of the chunk
|
||||||
|
* @return the size of the chunk
|
||||||
|
*/
|
||||||
private int getChunkSize(JObjectKey uuid) {
|
private int getChunkSize(JObjectKey uuid) {
|
||||||
return readChunk(uuid).size();
|
return readChunk(uuid).size();
|
||||||
}
|
}
|
||||||
@@ -334,6 +402,13 @@ public class DhfsFileService {
|
|||||||
return num & -(1L << n);
|
return num & -(1L << n);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write data to a file.
|
||||||
|
* @param fileUuid the ID of the file
|
||||||
|
* @param offset the offset to write to
|
||||||
|
* @param data the data to write
|
||||||
|
* @return the number of bytes written
|
||||||
|
*/
|
||||||
public Long write(JObjectKey fileUuid, long offset, ByteString data) {
|
public Long write(JObjectKey fileUuid, long offset, ByteString data) {
|
||||||
return jObjectTxManager.executeTx(() -> {
|
return jObjectTxManager.executeTx(() -> {
|
||||||
if (offset < 0)
|
if (offset < 0)
|
||||||
@@ -436,6 +511,12 @@ public class DhfsFileService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Truncate a file to the given length.
|
||||||
|
* @param fileUuid the ID of the file
|
||||||
|
* @param length the new length of the file
|
||||||
|
* @return true if the truncate was successful, false otherwise
|
||||||
|
*/
|
||||||
public Boolean truncate(JObjectKey fileUuid, long length) {
|
public Boolean truncate(JObjectKey fileUuid, long length) {
|
||||||
return jObjectTxManager.executeTx(() -> {
|
return jObjectTxManager.executeTx(() -> {
|
||||||
if (length < 0)
|
if (length < 0)
|
||||||
@@ -525,6 +606,12 @@ public class DhfsFileService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fill the given range with zeroes.
|
||||||
|
* @param fillStart the start of the range
|
||||||
|
* @param length the end of the range
|
||||||
|
* @param newChunks the map to store the new chunks in
|
||||||
|
*/
|
||||||
private void fillZeros(long fillStart, long length, Map<Long, JObjectKey> newChunks) {
|
private void fillZeros(long fillStart, long length, Map<Long, JObjectKey> newChunks) {
|
||||||
long combinedSize = (length - fillStart);
|
long combinedSize = (length - fillStart);
|
||||||
|
|
||||||
@@ -560,12 +647,22 @@ public class DhfsFileService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the contents of a symlink.
|
||||||
|
* @param uuid the ID of the symlink
|
||||||
|
* @return the contents of the symlink as a string
|
||||||
|
*/
|
||||||
public String readlink(JObjectKey uuid) {
|
public String readlink(JObjectKey uuid) {
|
||||||
return jObjectTxManager.executeTx(() -> {
|
return jObjectTxManager.executeTx(() -> {
|
||||||
return readlinkBS(uuid).toStringUtf8();
|
return readlinkBS(uuid).toStringUtf8();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the contents of a symlink as a ByteString.
|
||||||
|
* @param uuid the ID of the symlink
|
||||||
|
* @return the contents of the symlink as a ByteString
|
||||||
|
*/
|
||||||
public ByteString readlinkBS(JObjectKey uuid) {
|
public ByteString readlinkBS(JObjectKey uuid) {
|
||||||
return jObjectTxManager.executeTx(() -> {
|
return jObjectTxManager.executeTx(() -> {
|
||||||
var fileOpt = remoteTx.getData(File.class, uuid).orElseThrow(() -> new StatusRuntimeException(Status.NOT_FOUND.withDescription("File not found when trying to readlink: " + uuid)));
|
var fileOpt = remoteTx.getData(File.class, uuid).orElseThrow(() -> new StatusRuntimeException(Status.NOT_FOUND.withDescription("File not found when trying to readlink: " + uuid)));
|
||||||
@@ -573,10 +670,16 @@ public class DhfsFileService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a symlink.
|
||||||
|
* @param oldpath the target of the symlink
|
||||||
|
* @param newpath the path of the symlink
|
||||||
|
* @return the key of the created symlink
|
||||||
|
*/
|
||||||
public JObjectKey symlink(String oldpath, String newpath) {
|
public JObjectKey symlink(String oldpath, String newpath) {
|
||||||
return jObjectTxManager.executeTx(() -> {
|
return jObjectTxManager.executeTx(() -> {
|
||||||
Path path = Path.of(newpath);
|
Path path = Path.of(newpath);
|
||||||
var parent = getDirEntryW(path.getParent().toString());
|
var parent = getDirEntry(path.getParent().toString());
|
||||||
|
|
||||||
ensureDir(parent);
|
ensureDir(parent);
|
||||||
|
|
||||||
@@ -595,6 +698,13 @@ public class DhfsFileService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the access and modification times of a file.
|
||||||
|
* @param fileUuid the ID of the file
|
||||||
|
* @param atimeMs the access time in milliseconds
|
||||||
|
* @param mtimeMs the modification time in milliseconds
|
||||||
|
* @return true if the times were set successfully, false otherwise
|
||||||
|
*/
|
||||||
public Boolean setTimes(JObjectKey fileUuid, long atimeMs, long mtimeMs) {
|
public Boolean setTimes(JObjectKey fileUuid, long atimeMs, long mtimeMs) {
|
||||||
return jObjectTxManager.executeTx(() -> {
|
return jObjectTxManager.executeTx(() -> {
|
||||||
var dent = curTx.get(JData.class, fileUuid).orElseThrow(() -> new StatusRuntimeExceptionNoStacktrace(Status.NOT_FOUND));
|
var dent = curTx.get(JData.class, fileUuid).orElseThrow(() -> new StatusRuntimeExceptionNoStacktrace(Status.NOT_FOUND));
|
||||||
@@ -616,6 +726,11 @@ public class DhfsFileService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the size of a file.
|
||||||
|
* @param fileUuid the ID of the file
|
||||||
|
* @return the size of the file
|
||||||
|
*/
|
||||||
public long size(JObjectKey fileUuid) {
|
public long size(JObjectKey fileUuid) {
|
||||||
return jObjectTxManager.executeTx(() -> {
|
return jObjectTxManager.executeTx(() -> {
|
||||||
long realSize = 0;
|
long realSize = 0;
|
||||||
@@ -635,6 +750,13 @@ public class DhfsFileService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write data to a file.
|
||||||
|
* @param fileUuid the ID of the file
|
||||||
|
* @param offset the offset to write to
|
||||||
|
* @param data the data to write
|
||||||
|
* @return the number of bytes written
|
||||||
|
*/
|
||||||
public Long write(JObjectKey fileUuid, long offset, byte[] data) {
|
public Long write(JObjectKey fileUuid, long offset, byte[] data) {
|
||||||
return write(fileUuid, offset, UnsafeByteOperations.unsafeWrap(data));
|
return write(fileUuid, offset, UnsafeByteOperations.unsafeWrap(data));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
package com.usatiuk.dhfsfs.service;
|
package com.usatiuk.dhfsfs.service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DirectoryNotEmptyException is thrown when a directory is not empty.
|
||||||
|
* This exception is used to indicate that a directory cannot be deleted
|
||||||
|
* because it contains files or subdirectories.
|
||||||
|
*/
|
||||||
public class DirectoryNotEmptyException extends RuntimeException {
|
public class DirectoryNotEmptyException extends RuntimeException {
|
||||||
@Override
|
@Override
|
||||||
public synchronized Throwable fillInStackTrace() {
|
public synchronized Throwable fillInStackTrace() {
|
||||||
|
|||||||
@@ -1,4 +1,11 @@
|
|||||||
package com.usatiuk.dhfsfs.service;
|
package com.usatiuk.dhfsfs.service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GetattrRes is a record that represents the result of a getattr operation.
|
||||||
|
* @param mtime File modification time
|
||||||
|
* @param ctime File creation time
|
||||||
|
* @param mode File mode
|
||||||
|
* @param type File type
|
||||||
|
*/
|
||||||
public record GetattrRes(long mtime, long ctime, long mode, GetattrType type) {
|
public record GetattrRes(long mtime, long ctime, long mode, GetattrType type) {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,6 @@
|
|||||||
<properties>
|
<properties>
|
||||||
<compiler-plugin.version>3.12.1</compiler-plugin.version>
|
<compiler-plugin.version>3.12.1</compiler-plugin.version>
|
||||||
<!--FIXME-->
|
<!--FIXME-->
|
||||||
<maven.compiler.release></maven.compiler.release>
|
|
||||||
<maven.compiler.source>21</maven.compiler.source>
|
<maven.compiler.source>21</maven.compiler.source>
|
||||||
<maven.compiler.target>21</maven.compiler.target>
|
<maven.compiler.target>21</maven.compiler.target>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
@@ -87,6 +86,19 @@
|
|||||||
|
|
||||||
<build>
|
<build>
|
||||||
<plugins>
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-javadoc-plugin</artifactId>
|
||||||
|
<version>3.11.2</version>
|
||||||
|
<configuration>
|
||||||
|
<additionalOptions>
|
||||||
|
--add-exports java.base/sun.nio.ch=ALL-UNNAMED
|
||||||
|
--add-exports java.base/jdk.internal.access=ALL-UNNAMED
|
||||||
|
--add-opens=java.base/java.nio=ALL-UNNAMED
|
||||||
|
--enable-preview
|
||||||
|
</additionalOptions>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>${quarkus.platform.group-id}</groupId>
|
<groupId>${quarkus.platform.group-id}</groupId>
|
||||||
<artifactId>quarkus-maven-plugin</artifactId>
|
<artifactId>quarkus-maven-plugin</artifactId>
|
||||||
|
|||||||
Reference in New Issue
Block a user