diff --git a/build.gradle.kts b/build.gradle.kts index 45d2778..12fab2b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -16,6 +16,9 @@ tasks { subprojects { apply(plugin = "java-library") + group = "ca.bkaw.mch" + version = "0.1-SNAPSHOT" + java { toolchain { languageVersion.set(JavaLanguageVersion.of(17)) diff --git a/mch-fs/build.gradle.kts b/mch-fs/build.gradle.kts new file mode 100644 index 0000000..1a0f01f --- /dev/null +++ b/mch-fs/build.gradle.kts @@ -0,0 +1,20 @@ +plugins { + `maven-publish` + id("java") +} + +repositories { + mavenLocal() + mavenCentral() +} + +dependencies { + implementation(project(":mch")) + compileOnly("org.jetbrains:annotations:24.0.1") +} + +publishing { + publications.create("maven") { + from(components["java"]) + } +} \ No newline at end of file diff --git a/mch-fs/src/main/java/ca/bkaw/mch/fs/MchFileSystem.java b/mch-fs/src/main/java/ca/bkaw/mch/fs/MchFileSystem.java new file mode 100644 index 0000000..127c74c --- /dev/null +++ b/mch-fs/src/main/java/ca/bkaw/mch/fs/MchFileSystem.java @@ -0,0 +1,308 @@ +package ca.bkaw.mch.fs; + +import ca.bkaw.mch.chunk.RegionFileChunk; +import ca.bkaw.mch.object.Reference20; +import ca.bkaw.mch.object.blob.Blob; +import ca.bkaw.mch.object.dimension.Dimension; +import ca.bkaw.mch.object.tree.Tree; +import ca.bkaw.mch.object.world.World; +import ca.bkaw.mch.region.MchRegionFile; +import ca.bkaw.mch.region.RegionStorageVisitor; +import ca.bkaw.mch.region.mc.McRegionFileWriter; +import ca.bkaw.mch.repository.MchRepository; +import ca.bkaw.mch.repository.TrackedWorld; +import ca.bkaw.mch.util.Util; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.nio.file.DirectoryStream; +import java.nio.file.FileStore; +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.nio.file.PathMatcher; +import java.nio.file.ProviderMismatchException; +import java.nio.file.WatchService; +import java.nio.file.attribute.UserPrincipalLookupService; +import java.nio.file.spi.FileSystemProvider; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +public class MchFileSystem extends FileSystem { + private final MchFileSystemProvider provider; + private final MchRepository repository; + private TrackedWorld trackedWorld; + private World world; + private final Path root; + private Set restoredPaths; + + public MchFileSystem(MchFileSystemProvider provider, Path root, MchRepository repository, TrackedWorld trackedWorld, World world) throws IOException { + if (root.getFileSystem() instanceof MchFileSystem) { + throw new IllegalArgumentException("path must correspond to a non-mch file system."); + } + this.provider = provider; + this.root = root; + this.repository = repository; + this.trackedWorld = trackedWorld; + this.world = world; + this.restoredPaths = new HashSet<>(); + } + + /** + * Set the {@link World} object that this file system reads from. + * + * @param trackedWorld The tracked world. + * @param world The world snapshot. + */ + public void setWorld(TrackedWorld trackedWorld, World world) { + this.trackedWorld = trackedWorld; + this.world = world; + this.restoredPaths = new HashSet<>(); + } + + /** + * Restore a file from the mch repository, so it becomes available on the real file + * system. + * + * @param mchPath The mch path. + */ + public void restore(@NotNull MchPath mchPath) throws IOException { + // TODO Think about how to handle errors here. For example if a file does not + // exist, should this error or not do anything? + if (mchPath.getFileSystem() != this) { + throw new ProviderMismatchException(); + } + if (this.restoredPaths.contains(mchPath.path)) { + return; + } + System.out.println("DEBUG Restoring " + mchPath); + Path relative = this.root.relativize(mchPath.path.toAbsolutePath()); + + // TODO custom dimensions + String dimensionKey = switch (relative.getName(0).getFileName().toString()) { + case Util.NETHER_FOLDER -> Dimension.NETHER; + case Util.THE_END_FOLDER -> Dimension.THE_END; + default -> Dimension.OVERWORLD; + }; + int nameIndex = Dimension.OVERWORLD.equals(dimensionKey) ? 0 : 1; + + Reference20 dimensionRef = this.world.getDimension(dimensionKey); + if (dimensionRef == null) { + return; + } + Dimension dimension = dimensionRef.resolve(this.repository); + + if ("region".equals(relative.getName(nameIndex).getFileName().toString())) { + String fileName = relative.getFileName().toString(); + if ("region".equals(fileName)) { + // lazy but works + Files.createDirectories(mchPath.path); + this.restoredPaths.add(mchPath.path); + return; + } + System.out.println("fileName = " + fileName); + String str = fileName.substring("r.".length(), fileName.length() - ".mca".length()); + String[] split = str.split("\\."); + int regionX = Integer.parseInt(split[0]); + int regionZ = Integer.parseInt(split[1]); + Dimension.RegionFileReference regionFileRef = dimension.getRegionFile(regionX, regionZ); + + if (regionFileRef == null) { + return; + } + + Path regionStoragePath = RegionStorageVisitor.getPath( + this.repository, this.trackedWorld, dimensionKey, regionX, regionZ + ); + + Path mchRegionFilePath = MchRegionFile.getPath( + this.repository, this.trackedWorld, dimensionKey, regionX, regionZ + ); + + Path mcRegionFilePath = mchPath.path; + try (McRegionFileWriter regionFile = new McRegionFileWriter(mcRegionFilePath)) { + int[] chunkVersionNumbers = MchRegionFile.read(mchRegionFilePath, regionFileRef.getVersionNumber()); + + RegionStorageVisitor.visitReadOnly(regionStoragePath, chunk -> { + int chunkVersionNumber = chunkVersionNumbers[chunk.getIndex()]; + if (chunkVersionNumber != 0) { + RegionFileChunk restoredChunk = chunk.restore(chunkVersionNumber); + regionFile.writeChunk(restoredChunk.nbt(), restoredChunk.lastModified()); + } + }); + } + this.restoredPaths.add(mchPath.path); + return; + } + + // Non-region file. + Tree tree = dimension.getMiscellaneousFiles().resolve(this.repository); + for (int i = nameIndex; i < relative.getNameCount() - 1; i++) { + String name = relative.getName(i).toString(); + Reference20 subTreeRef = tree.getSubTrees().get(name); + if (subTreeRef == null) { + return; + } + tree = subTreeRef.resolve(this.repository); + } + String fileName = relative.getFileName().toString(); + if (tree.getSubTrees().containsKey(fileName)) { + Files.createDirectories(mchPath.path); + System.out.println("Creating: " + mchPath.path); + } else if (tree.getFiles().containsKey(fileName)) { + Tree.BlobReference blobRef = tree.getFiles().get(fileName); + Blob blob = blobRef.reference().resolve(this.repository); + Files.write(mchPath.path, blob.getBytes()); + } + this.restoredPaths.add(mchPath.path); + // else nothing to restore, do nothing. + } + + public DirectoryStream newDirectoryStream(@NotNull MchPath dir, DirectoryStream.Filter filter) throws IOException { + // TODO respect filter + if (dir.getFileSystem() != this) { + throw new ProviderMismatchException(); + } + System.out.println("DEBUG Listing " + dir); + // Thread.dumpStack(); + Path relative = this.root.relativize(dir.path.toAbsolutePath().normalize()); + + // TODO custom dimensions + String dimensionKey = switch (relative.getName(0).getFileName().toString()) { + case Util.NETHER_FOLDER -> Dimension.NETHER; + case Util.THE_END_FOLDER -> Dimension.THE_END; + default -> Dimension.OVERWORLD; + }; + int nameIndex = Dimension.OVERWORLD.equals(dimensionKey) ? 0 : 1; + + Reference20 dimensionRef = this.world.getDimension(dimensionKey); + if (dimensionRef == null) { + throw new NoSuchFileException(dir.toString()); + } + Dimension dimension = dimensionRef.resolve(this.repository); + + if ("region".equals(relative.getName(nameIndex).getFileName().toString())) { + if (relative.getNameCount() != nameIndex + 1) { + // No files in subdirectories of region. + return new DirectoryStream<>() { + @Override + public Iterator iterator() { + return Collections.emptyIterator(); + } + @Override + public void close() {} + }; + } + List list = dimension.getRegionFiles().stream().map((region) -> { + String fileName = Util.formatRegionFileName(region.getRegionX(), region.getRegionZ(), ".mca"); + return dir.resolve(fileName); + }).toList(); + + return new DirectoryStream<>() { + @Override + public Iterator iterator() { + return list.iterator(); + } + @Override + public void close() {} + }; + } + + // Non-region file. + Tree tree = dimension.getMiscellaneousFiles().resolve(this.repository); + for (int i = nameIndex; i < relative.getNameCount(); i++) { + String name = relative.getName(i).toString(); + if (name.isEmpty() || ".".equals(name)) { + continue; + } + Reference20 subTreeRef = tree.getSubTrees().get(name); + if (subTreeRef == null) { + System.out.println("i = " + i + " : " + name); + throw new NoSuchFileException(dir.toString()); + } + tree = subTreeRef.resolve(this.repository); + } + + List list = new ArrayList<>(tree.getFiles().size() + tree.getSubTrees().size()); + for (String name : tree.getFiles().keySet()) { + list.add(dir.resolve(name)); + } + for (String name : tree.getSubTrees().keySet()) { + list.add(dir.resolve(name)); + } + + return new DirectoryStream<>() { + @Override + public Iterator iterator() { + return list.iterator(); + } + @Override + public void close() {} + }; + } + + @Override + public Path getPath(@NotNull String first, String @NotNull ... more) { + return new MchPath(this, this.root.getFileSystem().getPath(first, more)); + } + + @Override + public Iterable getRootDirectories() { + return List.of(new MchPath(this, this.root)); + } + + @Override + public FileSystemProvider provider() { + return this.provider; + } + + @Override + public boolean isOpen() { + return true; + } + + @Override + public boolean isReadOnly() { + return false; + } + + @Override + public String getSeparator() { + return "/"; + } + + @Override + public void close() { + throw new UnsupportedOperationException(); + } + + @Override + public Iterable getFileStores() { + return null; + } + + @Override + public Set supportedFileAttributeViews() { + return null; + } + + @Override + public PathMatcher getPathMatcher(String syntaxAndPattern) { + throw new UnsupportedOperationException(); + } + + @Override + public UserPrincipalLookupService getUserPrincipalLookupService() { + throw new UnsupportedOperationException(); + } + + @Override + public WatchService newWatchService() { + throw new UnsupportedOperationException(); + } +} diff --git a/mch-fs/src/main/java/ca/bkaw/mch/fs/MchFileSystemProvider.java b/mch-fs/src/main/java/ca/bkaw/mch/fs/MchFileSystemProvider.java new file mode 100644 index 0000000..ab137da --- /dev/null +++ b/mch-fs/src/main/java/ca/bkaw/mch/fs/MchFileSystemProvider.java @@ -0,0 +1,206 @@ +package ca.bkaw.mch.fs; + +import ca.bkaw.mch.object.world.World; +import ca.bkaw.mch.repository.MchRepository; +import ca.bkaw.mch.repository.TrackedWorld; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.net.URI; +import java.nio.channels.FileChannel; +import java.nio.channels.SeekableByteChannel; +import java.nio.file.AccessMode; +import java.nio.file.CopyOption; +import java.nio.file.DirectoryStream; +import java.nio.file.FileStore; +import java.nio.file.FileSystem; +import java.nio.file.FileSystemAlreadyExistsException; +import java.nio.file.Files; +import java.nio.file.LinkOption; +import java.nio.file.OpenOption; +import java.nio.file.Path; +import java.nio.file.ProviderMismatchException; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.FileAttribute; +import java.nio.file.attribute.FileAttributeView; +import java.nio.file.spi.FileSystemProvider; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +public class MchFileSystemProvider extends FileSystemProvider { + public static final MchFileSystemProvider INSTANCE = new MchFileSystemProvider(); + + public static final String SCHEME = "mch"; + public static final String REPO_ENV_KEY = "mch_repository"; + public static final String WORLD_ENV_KEY = "mch_world"; + public static final String TRACKED_WORLD_ENV_KEY = "mch_tracked_world"; + + final Map fileSystems = new HashMap<>(); + + @Override + public String getScheme() { + return SCHEME; + } + + @Override + public FileSystem newFileSystem(URI uri, Map env) throws IOException { + synchronized (this.fileSystems) { + if (!(env.get(REPO_ENV_KEY) instanceof MchRepository repository)) { + throw new IllegalArgumentException("Please provide the mch repository as the \"" + REPO_ENV_KEY + "\" env key."); + } + if (!(env.get(WORLD_ENV_KEY) instanceof World world)) { + throw new IllegalArgumentException("Please provide the mch world object as the \"" + WORLD_ENV_KEY + "\" env key."); + } + if (!(env.get(TRACKED_WORLD_ENV_KEY) instanceof TrackedWorld trackedWorld)) { + throw new IllegalArgumentException("Please provide the tracked world as the \"" + TRACKED_WORLD_ENV_KEY + "\" env key."); + } + if (!SCHEME.equals(uri.getScheme())) { + throw new IllegalArgumentException("Expected \"" + SCHEME + "\" as the URI scheme."); + } + String path = uri.getPath(); + Path root = Path.of(path); + if (!Files.isDirectory(root)) { + throw new IllegalArgumentException("Please make sure the provided path is a directory: " + root); + } + + MchFileSystem fileSystem = this.fileSystems.get(path); + if (fileSystem != null) { + throw new FileSystemAlreadyExistsException(path); + } + fileSystem = new MchFileSystem(this, root, repository, trackedWorld, world); + this.fileSystems.put(path, fileSystem); + return fileSystem; + } + } + + @Override + public FileSystem getFileSystem(URI uri) { + return this.fileSystems.get(uri.getPath()); + } + + @Override + public Path getPath(@NotNull URI uri) { + throw new UnsupportedOperationException(); + } + + private @NotNull MchPath mchPath(@Nullable Path path) { + if (!(path instanceof MchPath mchPath)) { + throw new ProviderMismatchException(); + } + return mchPath; + } + + private void restore(@NotNull MchPath mchPath) throws IOException { + MchFileSystem fileSystem = mchPath.getFileSystem(); + fileSystem.restore(mchPath); + } + + @Override + public SeekableByteChannel newByteChannel(Path path, Set options, FileAttribute... attrs) throws IOException { + MchPath mchPath = this.mchPath(path); + this.restore(mchPath); + return Files.newByteChannel(mchPath.path, options, attrs); + } + + @Override + public FileChannel newFileChannel(Path path, Set options, FileAttribute... attrs) throws IOException { + MchPath mchPath = this.mchPath(path); + this.restore(mchPath); + return FileChannel.open(mchPath.path, options, attrs); + } + + @Override + public DirectoryStream newDirectoryStream(Path dir, DirectoryStream.Filter filter) throws IOException { + MchPath mchPath = this.mchPath(dir); + return mchPath.getFileSystem().newDirectoryStream(mchPath, filter); + } + + @Override + public void createDirectory(Path dir, FileAttribute... attrs) throws IOException { + MchPath mchPath = this.mchPath(dir); + Files.createDirectory(mchPath.path, attrs); + } + + @Override + public void delete(Path path) throws IOException { + MchPath mchPath = this.mchPath(path); + Files.delete(mchPath.path); + } + + @Override + public void copy(Path source, Path target, CopyOption... options) throws IOException { + MchPath mchSource = this.mchPath(source); + this.restore(mchSource); + MchPath mchTarget = this.mchPath(target); + Files.copy(mchSource.path, mchTarget.path, options); + } + + @Override + public void move(Path source, Path target, CopyOption... options) throws IOException { + MchPath mchSource = this.mchPath(source); + this.restore(mchSource); + MchPath mchTarget = this.mchPath(target); + Files.move(mchSource.path, mchTarget.path, options); + } + + @Override + public boolean isSameFile(Path path, Path path2) throws IOException { + // TODO maybe unsafe + MchPath mchPath = this.mchPath(path); + MchPath mchPath2 = this.mchPath(path2); + return Files.isSameFile(mchPath.path, mchPath2.path); + } + + @Override + public boolean isHidden(Path path) throws IOException { + MchPath mchPath = this.mchPath(path); + return Files.isHidden(mchPath); + } + + @Override + public FileStore getFileStore(Path path) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public void checkAccess(Path path, AccessMode... modes) throws IOException { + MchPath mchPath = this.mchPath(path); + this.restore(mchPath); + mchPath.path.getFileSystem().provider().checkAccess(mchPath.path, modes); + } + + @Override + public V getFileAttributeView(Path path, Class type, LinkOption... options) { + MchPath mchPath = this.mchPath(path); + try { + this.restore(mchPath); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + return Files.getFileAttributeView(mchPath.path, type, options); + } + + @Override + public A readAttributes(Path path, Class type, LinkOption... options) throws IOException { + MchPath mchPath = this.mchPath(path); + this.restore(mchPath); + return Files.readAttributes(mchPath.path, type, options); + } + + @Override + public Map readAttributes(Path path, String attributes, LinkOption... options) throws IOException { + MchPath mchPath = this.mchPath(path); + this.restore(mchPath); + return Files.readAttributes(mchPath.path, attributes, options); + } + + @Override + public void setAttribute(Path path, String attribute, Object value, LinkOption... options) throws IOException { + MchPath mchPath = this.mchPath(path); + this.restore(mchPath); + Files.setAttribute(mchPath.path, attribute, value, options); + } +} diff --git a/mch-fs/src/main/java/ca/bkaw/mch/fs/MchPath.java b/mch-fs/src/main/java/ca/bkaw/mch/fs/MchPath.java new file mode 100644 index 0000000..d97f0f9 --- /dev/null +++ b/mch-fs/src/main/java/ca/bkaw/mch/fs/MchPath.java @@ -0,0 +1,194 @@ +package ca.bkaw.mch.fs; + +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.nio.file.LinkOption; +import java.nio.file.Path; +import java.nio.file.WatchEvent; +import java.nio.file.WatchKey; +import java.nio.file.WatchService; + +/** + * A wrapper around a {@link Path} used by {@link MchFileSystem mch file systems}. + * + * @see MchFileSystem + */ +public class MchPath implements Path { + private final @NotNull MchFileSystem fileSystem; + final @NotNull Path path; + + public MchPath(@NotNull MchFileSystem fileSystem, @NotNull Path path) { + if (path instanceof MchPath) { + throw new IllegalArgumentException("path must correspond to a non-mch file system."); + } + this.fileSystem = fileSystem; + this.path = path; + } + + /** + * Wrap a {@link Path} in an {@link MchPath}. + * + * @param path The path. + * @return The wrapped path. + */ + @Contract("null -> null; !null -> !null") + private @Nullable MchPath wrap(@Nullable Path path) { + if (path == null) { + return null; + } + return new MchPath(this.fileSystem, path); + } + + /** + * Unwrap an {@link MchPath} to a normal path. If the path was already a normal path, it + * will be returned as-is. + * + * @param path The path, possibly an {@link MchPath}. + * @return The non-mch path. + */ + @Contract("!null -> !null; null -> null") + private @Nullable Path unwrap(@Nullable Path path) { + if (path == null) { + return null; + } + if (path instanceof MchPath mchPath) { + return mchPath.path; + } + // Not mch path, probably already unwrapped. + return path; + } + + @Override + public @NotNull MchFileSystem getFileSystem() { + return this.fileSystem; + } + + @Override + public boolean isAbsolute() { + return this.path.isAbsolute(); + } + + @Override + public Path getRoot() { + return wrap(this.path.getRoot()); + } + + @Override + public Path getFileName() { + return this.path.getFileName(); + } + + @Override + public Path getParent() { + return wrap(this.path.getParent()); + } + + @Override + public int getNameCount() { + return this.path.getNameCount(); + } + + @Override + public Path getName(int index) { + return this.path.getName(index); + } + + @Override + public Path subpath(int beginIndex, int endIndex) { + return this.path.subpath(beginIndex, endIndex); + } + + @Override + public boolean startsWith(@NotNull Path other) { + return this.path.startsWith(unwrap(other)); + } + + @Override + public boolean endsWith(@NotNull Path other) { + return this.path.endsWith(unwrap(other)); + } + + @Override + public Path normalize() { + return wrap(this.path.normalize()); + } + + @Override + public Path resolve(@NotNull Path other) { + return wrap(this.path.resolve(unwrap(other))); + } + @Override + public Path relativize(@NotNull Path other) { + return wrap(this.path.relativize(unwrap(other))); + } + + @Override + public URI toUri() { + // TODO possibly unsafe + System.out.println("Possible unsafe toUri call."); + return this.path.toUri(); + } + + @Override + public Path toAbsolutePath() { + return wrap(this.path.toAbsolutePath()); + } + + @Override + public Path toRealPath(LinkOption @NotNull ... options) throws IOException { + return wrap(this.path.toRealPath(options)); + } + + @Override + public WatchKey register(@NotNull WatchService watcher, WatchEvent.Kind @NotNull [] events, WatchEvent.Modifier... modifiers) throws IOException { + // TODO possibly unsafe + System.out.println("Possible unsafe watch register call."); + return this.path.register(watcher, events, modifiers); + } + + @Override + public int compareTo(@NotNull Path other) { + return this.path.compareTo(unwrap(other)); + } + + @NotNull + @Override + public File toFile() { + // TODO possibly unsafe. + System.out.println("Possible unsafe toFile call."); + Thread.dumpStack(); + // Known usages of File in game code: + // - PlayerDataStorage (can quite easily be mixin'd) + // - OldUsersConverter (can probably ignore) + // - DimensionDataStorage + // - Server icon (MinecraftServer) + return this.path.toFile(); + } + + @Override + public int hashCode() { + return this.path.hashCode(); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof MchPath mchPath)) { + return false; + } + if (!this.fileSystem.equals(mchPath.fileSystem)) return false; + return this.path.equals(mchPath.path); + } + + @Override + public String toString() { + return this.path.toString(); + } +} diff --git a/mch-fs/src/main/java/ca/bkaw/mch/fs/TestMain.java b/mch-fs/src/main/java/ca/bkaw/mch/fs/TestMain.java new file mode 100644 index 0000000..deac14f --- /dev/null +++ b/mch-fs/src/main/java/ca/bkaw/mch/fs/TestMain.java @@ -0,0 +1,61 @@ +package ca.bkaw.mch.fs; + +import ca.bkaw.mch.Sha1; +import ca.bkaw.mch.object.ObjectStorageTypes; +import ca.bkaw.mch.object.Reference20; +import ca.bkaw.mch.object.commit.Commit; +import ca.bkaw.mch.object.world.World; +import ca.bkaw.mch.object.worldcontainer.WorldContainer; +import ca.bkaw.mch.repository.MchRepository; +import ca.bkaw.mch.repository.TrackedWorld; + +import java.io.IOException; +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; + +public class TestMain { + public static void main(String[] args) throws IOException { + Path a = Path.of("/Users/Alvin/Documents/Programmering/mch-viewer-fabric-test/run/world/advancements/idontexist"); + + Files.newDirectoryStream(a).iterator().forEachRemaining((path) -> System.out.println(path)); + } + public static void main2(String[] args) throws IOException { + MchRepository repository = new MchRepository(Path.of("/Users/Alvin/Documents/mch/test2/mch")); + repository.readConfiguration(); + TrackedWorld trackedWorld = repository.getConfiguration().getTrackedWorld(Sha1.fromString("40a9e3d33e815f6da8c0054ba895b13280fe8683")); + + Reference20 commitRef = new Reference20<>(ObjectStorageTypes.COMMIT, Sha1.fromString("280742e4698392d1475de4dbe407917f440933d2")); + Commit commit = commitRef.resolve(repository); + + WorldContainer worldContainer = commit.getWorldContainer().resolve(repository); + + Reference20 worldRef = worldContainer.getWorld(trackedWorld.getId()); + World world = worldRef.resolve(repository); + + Path testPath = Path.of("run/mch-fs-test").toAbsolutePath(); + Files.createDirectories(testPath); + if (Files.list(testPath).count() > 0) { + System.err.println("Please empty " + testPath + " before running test."); + return; + } + + Map env = Map.of( + MchFileSystemProvider.REPO_ENV_KEY, repository, + MchFileSystemProvider.TRACKED_WORLD_ENV_KEY, trackedWorld, + MchFileSystemProvider.WORLD_ENV_KEY, world + ); + + URI uri = URI.create("mch:" + testPath); + + FileSystem fileSystem = FileSystems.newFileSystem(uri, env); + + Path path = fileSystem.getPath("."); + + System.out.println("Result:"); + System.out.println(Files.list(path).map(p -> p.toString()).toList()); + } +} diff --git a/mch-fs/src/main/resources/META-INF/DISABLED-services/java.nio.file.spi.FileSystemProvider b/mch-fs/src/main/resources/META-INF/DISABLED-services/java.nio.file.spi.FileSystemProvider new file mode 100644 index 0000000..0bd1c95 --- /dev/null +++ b/mch-fs/src/main/resources/META-INF/DISABLED-services/java.nio.file.spi.FileSystemProvider @@ -0,0 +1 @@ +ca.bkaw.mch.fs.MchFileSystemProvider \ No newline at end of file diff --git a/mch/src/main/java/ca/bkaw/mch/test/TestMain.java b/mch/src/main/java/ca/bkaw/mch/test/TestMain.java index 8e7aa1e..ee024fa 100644 --- a/mch/src/main/java/ca/bkaw/mch/test/TestMain.java +++ b/mch/src/main/java/ca/bkaw/mch/test/TestMain.java @@ -44,7 +44,14 @@ import java.util.zip.InflaterInputStream; public class TestMain { - public static void main(String[] args) throws IOException { + public static void main(String[] args) { + Path home = Path.of(System.getProperty("user.home")).toAbsolutePath(); + Path path = Path.of(".").toAbsolutePath(); + Path relative = home.relativize(path); + System.out.println("relative.getName(0) = " + relative.getName(0)); + } + + public static void main12(String[] args) throws IOException { McRegionFileReader reader = new McRegionFileReader(Path.of("/Users/Alvin/Downloads/r.-2.-4.mca")); System.out.println("reader.hasChunk(27, 27) = " + reader.hasChunk(27, 27)); System.out.println("reader.getChunkLastModified(27, 27) = " + reader.getChunkLastModified(27, 27)); diff --git a/settings.gradle.kts b/settings.gradle.kts index 3636b5a..fad7aa7 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,2 +1,3 @@ include("mch") include("mch-cli") +include("mch-fs")