diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 86a6526..d862b39 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -18,6 +18,10 @@ jobs: analyze: name: Analyze runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write strategy: fail-fast: false @@ -30,11 +34,11 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - # Initializes the CodeQL tools for scanning. + # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -44,8 +48,22 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v2 + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + cache: maven + server-id: github # Value of the distributionManagement/repository/id field of the pom.xml + settings-path: ${{ github.workspace }} # location for the settings.xml file + + - name: Build with Maven + run: | + mkdir -p $FUSE_MOUNT_POINT + mvn -B package --file pom.xml -s $GITHUB_WORKSPACE/settings.xml + env: + GITHUB_TOKEN: ${{ github.token }} + FUSE_MOUNT_POINT: tmp/fuse # โ„น๏ธ Command-line programs to run using the OS shell. # ๐Ÿ“š https://git.io/JvXDl @@ -59,4 +77,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index a5199b9..7f80423 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -1,7 +1,7 @@ # This workflow will build a Java project with Maven # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven -name: Java CI with Maven +name: Java CI on: push: @@ -16,18 +16,25 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Check w/o SNAPSHOT when "bump version" if: ${{ contains(github.event.head_commit.message, 'bump version') }} run: grep "" pom.xml | head -1 | grep -v SNAPSHOT - - name: Set up JDK 1.8 - uses: actions/setup-java@v3 + - name: Set up JDK 17 + uses: actions/setup-java@v4 with: - java-version: '8' + java-version: '17' distribution: 'temurin' cache: maven + server-id: github # Value of the distributionManagement/repository/id field of the pom.xml + settings-path: ${{ github.workspace }} # location for the settings.xml file - name: Build with Maven - run: mvn -B package --file pom.xml + run: | + mkdir -p $FUSE_MOUNT_POINT + mvn -B package --file pom.xml + env: + GITHUB_TOKEN: ${{ github.token }} + FUSE_MOUNT_POINT: tmp/fuse diff --git a/.gitignore b/.gitignore index a9a5aec..bdbe015 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ tmp +local.properties diff --git a/README.md b/README.md deleted file mode 100644 index 115f68e..0000000 --- a/README.md +++ /dev/null @@ -1,22 +0,0 @@ -[![Release](https://jitpack.io/v/umjammer/vavi-nio-file.svg)](https://jitpack.io/#umjammer/vavi-nio-file) -[![Java CI with Maven](https://github.com/umjammer/vavi-nio-file/actions/workflows/maven.yml/badge.svg)](https://github.com/umjammer/vavi-nio-file/actions) -[![CodeQL](https://github.com/umjammer/vavi-nio-file/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/umjammer/vavi-nio-file/actions/workflows/codeql-analysis.yml) -![Java](https://img.shields.io/badge/Java-8-b07219) -[![Parent](https://img.shields.io/badge/Parent-vavi--apps--fuse-pink)](https://github.com/umjammer/vavi-apps-fuse) - -# vavi-nio-file - -java nio file basics - - * cache for file system - * utilities - * channels for filesystems - * input/output streams for upload/download - * output engine input stream โœญ - * base test case - -## TODO - - * JSR-107 Cache Specification - * https://github.com/jsr107/jsr107spec - * https://github.com/ben-manes/caffeine \ No newline at end of file diff --git a/jitpack.yml b/jitpack.yml new file mode 100644 index 0000000..efde7bf --- /dev/null +++ b/jitpack.yml @@ -0,0 +1,2 @@ +jdk: + - openjdk17 diff --git a/local.properties.sample b/local.properties.sample new file mode 100644 index 0000000..500a6b7 --- /dev/null +++ b/local.properties.sample @@ -0,0 +1 @@ +fuse.mountPoint=/Users/you/mnt/fuse \ No newline at end of file diff --git a/pom.xml b/pom.xml index 2a09c14..84c69b3 100644 --- a/pom.xml +++ b/pom.xml @@ -1,36 +1,86 @@ - + + + 4.0.0 vavi - vavi-nio-file - 0.0.15 + vavi-nio-file-fuse + 0.0.16 - vavi-nio-file - - https://github.com/umjammer/vavi-nio-file/issues - + vavi-nio-file-fuse + https://github.com/umjammer/vavi-nio-file-fuse - https://github.com/umjammer/vavi-nio-file + https://github.com/umjammer/vavi-nio-file-fuse - https://github.com/umjammer/vavi-nio-file - java nio file basics + + https://github.com/umjammer/vavi-nio-file-fuse/issues + + + + + local + + + ${basedir}/local.properties + + + + + + org.codehaus.mojo + properties-maven-plugin + 1.1.0 + + + initialize + + read-project-properties + + + + ${basedir}/local.properties + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.2.2 + + + **/Main4 + + + ${fuse.mountPoint} + + + -Djava.util.logging.config.file=${project.build.testOutputDirectory}/logging.properties + -Djna.library.path=/usr/local/lib + + + + + + + org.apache.maven.plugins maven-compiler-plugin - 3.10.1 + 3.11.0 - 1.8 - 1.8 - UTF-8 + 17 org.apache.maven.plugins maven-jar-plugin - 3.2.2 + 3.3.0 @@ -39,15 +89,6 @@ - - org.apache.maven.plugins - maven-surefire-plugin - 3.0.0-M7 - - -Djava.util.logging.config.file=${project.build.testOutputDirectory}/logging.properties - false - - @@ -63,7 +104,7 @@ org.junit junit-bom - 5.9.1 + 5.10.1 pom import @@ -71,10 +112,32 @@ + + com.github.umjammer + javafs + 0.1.7v + + + com.github.umjammer + fuse-jna + 1.0.5v + + + com.github.serceman + jnr-fuse + 0.5.7 + + com.github.umjammer vavi-commons - 1.1.8 + 1.1.10 + + + + com.github.umjammer + vavi-nio-file-base + 0.0.14v @@ -92,22 +155,17 @@ junit-platform-commons test - - - com.rainerhahnekamp - sneakythrow - 1.2.0 + org.junit.jupiter + junit-jupiter-params test + com.google.jimfs jimfs - 1.2 + 1.3.0 test - \ No newline at end of file + diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..031a597 --- /dev/null +++ b/readme.md @@ -0,0 +1,50 @@ +[![Release](https://jitpack.io/v/umjammer/vavi-nio-file-fuse.svg)](https://jitpack.io/#umjammer/vavi-nio-file-fuse) +[![Java CI](https://github.com/umjammer/vavi-nio-file-fuse/actions/workflows/maven.yml/badge.svg)](https://github.com/umjammer/vavi-nio-file-fuse/actions/workflows/maven.yml) +[![CodeQL](https://github.com/umjammer/vavi-nio-file-fuse/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/umjammer/vavi-nio-file-fuse/actions/workflows/codeql-analysis.yml) +![Java](https://img.shields.io/badge/Java-17-b07219) + +# vavi-nio-file-fuse + +integrated fuse filesystem mounter. + +this is the api, implementation is provided as SPI. + +## Providers + +| fs | list | upload | download | copy | move | rm | mkdir | cache | watch | library | +|--------------------|------|--------|----------|------|------|----|-------|-------|-------|---------| +| javafs | โœ… | โœ… | โœ… | โœ… | โœ… | โœ… | โœ… | - | | [javafs](https://github.com/umjammer/javafs) | +| fuse-jna | โœ… | โœ… | โœ… | โœ… | โœ… | โœ… | โœ… | - | | [fuse-jna](https://github.com/EtiennePerot/fuse-jna) | +| jnr-fuse | โœ… | โœ… | โœ… | โœ… | โœ… | โœ… | โœ… | - | | [jnr-fuse](https://github.com/SerCeMan/jnr-fuse) | + +## Install + +### maven + + * https://jitpack.io/#umjammer/vavi-nio-file-fuse + +### jdk argument + + * `-Djna.library.path=/usr/local/lib` + +## Usage + +```java + URI uri = URI.create("googledrive:///?id=you@gmail.com"); + FileSystems fs = FileSystems.newFileSystem(uri, Collections.emptyMap()); + + Fuse.getFuse().mount(fs, "/your/mout/point", Collections.emptyMap()); +``` + +## Workaround + + * if the test goes wrong, update macfuse and reboot the mac + +## TODO + + * ~~https://github.com/cryptomator/fuse-nio-adapter~~ + * spotlight + * https://stackoverflow.com/a/2335565 + * https://wiki.samba.org/index.php/Spotlight_with_Elasticsearch_Backend + * https://gitlab.com/samba-team/samba/-/blob/master/source3/rpcclient/cmd_spotlight.c + * `Path#toFile()` UnsupportedOperationException ... mount fs as fuse then `toFile` \ No newline at end of file diff --git a/src/main/java/ru/serce/jnrfuse/utils/MountUtils.java b/src/main/java/ru/serce/jnrfuse/utils/MountUtils.java new file mode 100644 index 0000000..13c9f34 --- /dev/null +++ b/src/main/java/ru/serce/jnrfuse/utils/MountUtils.java @@ -0,0 +1,33 @@ +package ru.serce.jnrfuse.utils; + +import java.io.IOException; +import java.nio.file.Path; + +import ru.serce.jnrfuse.FuseException; + + +/** + * ad-hoc replacement + */ +public class MountUtils { + + /** + * Perform/force an umount at the provided Path + */ + public static void umount(Path mountPoint) { + String mountPath = mountPoint.toAbsolutePath().toString(); + try { + new ProcessBuilder("fusermount", "-u", "-z", mountPath).start(); + } catch (IOException e) { + try { + new ProcessBuilder("umount", "-f", mountPath).start().waitFor(); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + throw new FuseException("Unable to umount FS", e); + } catch (IOException ioe) { + ioe.addSuppressed(e); + throw new FuseException("Unable to umount FS", ioe); + } + } + } +} diff --git a/src/main/java/vavi/net/fuse/Fuse.java b/src/main/java/vavi/net/fuse/Fuse.java new file mode 100644 index 0000000..6a9aa51 --- /dev/null +++ b/src/main/java/vavi/net/fuse/Fuse.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2020 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package vavi.net.fuse; + +import java.io.Closeable; +import java.io.IOException; +import java.nio.file.FileSystem; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.ServiceLoader; + + +/** + * Fuse. + * + * @author Naohide Sano (umjammer) + * @version 0.00 2020/05/29 umjammer initial version
+ */ +public interface Fuse extends Closeable { + + /** */ + String ENV_SINGLE_THREAD = "single_thread"; + + /** should be non-blocking */ + void mount(FileSystem fs, String mountPoint, Map env) throws IOException; + + /** */ + ServiceLoader serviceLoader = ServiceLoader.load(FuseProvider.class); + + /** */ + static Fuse getFuse() { + String className = System.getProperty("vavi.net.fuse.FuseProvider.class", "vavi.net.fuse.fusejna.FuseJnaFuseProvider"); + for (FuseProvider provider : serviceLoader) { + if (provider.getClass().getName().equals(className)) { + return provider.getFuse(); + } + } + throw new NoSuchElementException(className); + } + + /** TODO location */ + static boolean isEnabled(String key, Map map) { + return map.containsKey(key) && (map.get(key) == null || (boolean) map.get(key)); + } +} + +/* */ diff --git a/src/main/java/vavi/net/fuse/FuseProvider.java b/src/main/java/vavi/net/fuse/FuseProvider.java new file mode 100644 index 0000000..3c51724 --- /dev/null +++ b/src/main/java/vavi/net/fuse/FuseProvider.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2020 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package vavi.net.fuse; + + +/** + * Factory. + * + * @author Naohide Sano (umjammer) + * @version 0.00 2020/06/03 umjammer initial version
+ */ +public interface FuseProvider { + + Fuse getFuse(); +} + +/* */ diff --git a/src/main/java/vavi/net/fuse/fusejna/FuseJnaFuse.java b/src/main/java/vavi/net/fuse/fusejna/FuseJnaFuse.java new file mode 100644 index 0000000..4e72e01 --- /dev/null +++ b/src/main/java/vavi/net/fuse/fusejna/FuseJnaFuse.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2020 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package vavi.net.fuse.fusejna; + +import java.io.IOException; +import java.nio.file.FileSystem; +import java.nio.file.Paths; +import java.nio.file.attribute.PosixFilePermission; +import java.util.EnumSet; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; + +import vavi.net.fuse.Fuse; +import vavi.util.Debug; + +import co.paralleluniverse.fuse.TypeMode; +import net.fusejna.FuseException; +import net.fusejna.FuseFilesystem; + + +/** + * JnaFuseFuse. (jna-fuse engine) + * + * @author Naohide Sano (umjammer) + * @version 0.00 2020/05/29 umjammer initial version
+ */ +public class FuseJnaFuse implements Fuse { + + /** key for env, no need to specify value */ + public static final String ENV_IGNORE_APPLE_DOUBLE = JavaNioFileFS.ENV_IGNORE_APPLE_DOUBLE; + + /** TODO utility delegate */ + static boolean isEnabled(String key, Map map) { + return Fuse.isEnabled(key, map); + } + + /** */ + private FuseFilesystem fuse; + + @Override + public void mount(FileSystem fs, String mountPoint, Map env) throws IOException { + try { + if (env.containsKey(ENV_SINGLE_THREAD) && (Boolean) env.get(ENV_SINGLE_THREAD)) { + fuse = new SingleThreadJavaNioFileFS(fs, env); +Debug.println("use single thread"); + } else { + fuse = new JavaNioFileFS(fs, env); + } + fuse.mount(Paths.get(mountPoint).toFile(), false); + Runtime.getRuntime().addShutdownHook(new Thread(() -> { try { close(); } catch (Exception e) { e.printStackTrace(); }})); + } catch (FuseException e) { + throw new IOException(e); + } + } + + @Override + public void close() throws IOException { + try { + if (fuse != null) { +Debug.println("unmount..."); + fuse.unmount(); + fuse = null; +Debug.println("unmount done"); + } + } catch (FuseException e) { +Debug.println(Level.WARNING, "unmount: " + e); + throw new IOException(e); + } + } + + /** */ + static boolean[] permissionsToMode(Set permissions) { + boolean[] mode = new boolean[9]; + for (PosixFilePermission px : permissions) { + switch (px) { + case OWNER_READ: mode[0] = true; break; + case OWNER_WRITE: mode[1] = true; break; + case OWNER_EXECUTE: mode[2] = true; break; + case GROUP_READ: mode[3] = true; break; + case GROUP_WRITE: mode[4] = true; break; + case GROUP_EXECUTE: mode[5] = true; break; + case OTHERS_READ: mode[6] = true; break; + case OTHERS_WRITE: mode[7] = true; break; + case OTHERS_EXECUTE: mode[8] = true; break; + } + } + return mode; + } + + /** */ + static Set modeToPermissions(long mode) { + final EnumSet permissions = EnumSet.noneOf(PosixFilePermission.class); + if ((mode & TypeMode.S_IRUSR) != 0) + permissions.add(PosixFilePermission.OWNER_READ); + if ((mode & TypeMode.S_IWUSR) != 0) + permissions.add(PosixFilePermission.OWNER_WRITE); + if ((mode & TypeMode.S_IXUSR) != 0) + permissions.add(PosixFilePermission.OWNER_EXECUTE); + if ((mode & TypeMode.S_IRGRP) != 0) + permissions.add(PosixFilePermission.GROUP_READ); + if ((mode & TypeMode.S_IWGRP) != 0) + permissions.add(PosixFilePermission.GROUP_WRITE); + if ((mode & TypeMode.S_IXGRP) != 0) + permissions.add(PosixFilePermission.GROUP_EXECUTE); + if ((mode & TypeMode.S_IROTH) != 0) + permissions.add(PosixFilePermission.OTHERS_READ); + if ((mode & TypeMode.S_IWOTH) != 0) + permissions.add(PosixFilePermission.OTHERS_WRITE); + if ((mode & TypeMode.S_IXOTH) != 0) + permissions.add(PosixFilePermission.OTHERS_EXECUTE); + return permissions; + } +} + +/* */ diff --git a/src/main/java/vavi/net/fuse/fusejna/FuseJnaFuseProvider.java b/src/main/java/vavi/net/fuse/fusejna/FuseJnaFuseProvider.java new file mode 100644 index 0000000..648c6ac --- /dev/null +++ b/src/main/java/vavi/net/fuse/fusejna/FuseJnaFuseProvider.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2020 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package vavi.net.fuse.fusejna; + +import vavi.net.fuse.Fuse; +import vavi.net.fuse.FuseProvider; + + +/** + * FuseJnaFuseProvider. + * + * @author Naohide Sano (umjammer) + * @version 0.00 2020/06/03 umjammer initial version
+ */ +public class FuseJnaFuseProvider implements FuseProvider { + + @Override + public Fuse getFuse() { + return new FuseJnaFuse(); + } +} + +/* */ diff --git a/src/main/java/vavi/net/fuse/fusejna/JavaNioFileFS.java b/src/main/java/vavi/net/fuse/fusejna/JavaNioFileFS.java new file mode 100644 index 0000000..0feef46 --- /dev/null +++ b/src/main/java/vavi/net/fuse/fusejna/JavaNioFileFS.java @@ -0,0 +1,424 @@ +/* + * Copyright (c) 2016 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package vavi.net.fuse.fusejna; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.Channel; +import java.nio.channels.NonWritableChannelException; +import java.nio.channels.SeekableByteChannel; +import java.nio.file.AccessDeniedException; +import java.nio.file.FileStore; +import java.nio.file.FileSystem; +import java.nio.file.LinkOption; +import java.nio.file.NoSuchFileException; +import java.nio.file.OpenOption; +import java.nio.file.StandardOpenOption; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.PosixFileAttributeView; +import java.nio.file.attribute.PosixFileAttributes; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; +import java.util.logging.Level; + +import vavi.net.fuse.Fuse; +import vavi.nio.file.Util; +import vavi.util.Debug; + +import jnr.constants.platform.Errno; +import net.fusejna.DirectoryFiller; +import net.fusejna.ErrorCodes; +import net.fusejna.StructFuseFileInfo.FileInfoWrapper; +import net.fusejna.StructStat.StatWrapper; +import net.fusejna.StructStatvfs.StatvfsWrapper; +import net.fusejna.types.TypeMode.ModeWrapper; +import net.fusejna.types.TypeMode.NodeType; +import net.fusejna.util.FuseFilesystemAdapterAssumeImplemented; + + +/** + * JavaNioFileFS. (fuse-jna) + * + * @author Naohide Sano (umjammer) + * @version 0.00 2016/02/29 umjammer initial version
+ */ +class JavaNioFileFS extends FuseFilesystemAdapterAssumeImplemented { + + /** */ + protected transient FileSystem fileSystem; + + /** key for env, no need to specify value */ + static final String ENV_IGNORE_APPLE_DOUBLE = "noappledouble"; + + /** */ + private final AtomicLong fileHandle = new AtomicLong(0); + + /** */ + private final ConcurrentMap fileHandles = new ConcurrentHashMap<>(); + + protected boolean ignoreAppleDouble; + + /** + * @param fileSystem a file system to wrap by fuse + */ + public JavaNioFileFS(FileSystem fileSystem, Map env) { + this.fileSystem = fileSystem; + ignoreAppleDouble = FuseJnaFuse.isEnabled(ENV_IGNORE_APPLE_DOUBLE, env); +Debug.println(Level.FINE, "ENV_IGNORE_APPLE_DOUBLE: " + ignoreAppleDouble); + } + + @Override + public int access(final String path, final int access) { +Debug.println(Level.FINER, "access: " + path); + try { + // TODO access + fileSystem.provider().checkAccess(fileSystem.getPath(path)); + return 0; + } catch (NoSuchFileException e) { + return -ErrorCodes.ENOENT(); + } catch (AccessDeniedException e) { +Debug.println(e); + return -ErrorCodes.EACCES(); + } catch (IOException e) { +Debug.printStackTrace(e); + return -ErrorCodes.EACCES(); + } + } + + @Override + public int create(final String path, final ModeWrapper mode, final FileInfoWrapper info) { +Debug.println(Level.FINE, "create: " + path); + try { + Set options = new HashSet<>(); + options.add(StandardOpenOption.WRITE); + options.add(StandardOpenOption.CREATE_NEW); + SeekableByteChannel channel = fileSystem.provider().newByteChannel(fileSystem.getPath(path), options); + long fh = fileHandle.incrementAndGet(); + fileHandles.put(fh, channel); + info.fh(fh); + + return 0; + } catch (IOException e) { +Debug.printStackTrace(e); + return -ErrorCodes.EIO(); + } + } + + @Override + public int getattr(final String path, final StatWrapper stat) { +Debug.println(Level.FINER, "getattr: " + path); + try { + BasicFileAttributes attributes = + fileSystem.provider().readAttributes(fileSystem.getPath(path), BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS); + + if (attributes instanceof PosixFileAttributes) { + boolean[] m = FuseJnaFuse.permissionsToMode(((PosixFileAttributes) attributes).permissions()); + if (attributes.isDirectory()) { + stat.setMode(NodeType.DIRECTORY, m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8]) + .setAllTimesSec(attributes.lastModifiedTime().to(TimeUnit.SECONDS)); + } else { + stat.setMode(NodeType.FILE, m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8]) + .setAllTimesSec(attributes.lastModifiedTime().to(TimeUnit.SECONDS)) + .size(attributes.size()); + } + } else { + if (attributes.isDirectory()) { + stat.setMode(NodeType.DIRECTORY, true, true, true, true, false, true, true, false, true) + .setAllTimesSec(attributes.lastModifiedTime().to(TimeUnit.SECONDS)); + } else { + stat.setMode(NodeType.FILE, true, true, false, true, false, false, true, false, false) + .setAllTimesSec(attributes.lastModifiedTime().to(TimeUnit.SECONDS)) + .size(attributes.size()); + } + } + return 0; + } catch (NoSuchFileException e) { + if (e.getMessage().startsWith("ignore apple double file:")) { +Debug.println(Level.FINE, e.getMessage()); + return 0; + } else { + if (ignoreAppleDouble) { + if (Util.isAppleDouble(path)) { +Debug.println(Level.FINER, e.getMessage()); + } else { +Debug.println(Level.FINE, e); + } + } + return -ErrorCodes.ENOENT(); + } + } catch (IOException e) { +Debug.printStackTrace(e); + return -ErrorCodes.EIO(); + } + } + + @Override + public int fgetattr(final String path, final StatWrapper stat, final FileInfoWrapper info) + { +Debug.println(Level.FINE, "fgetattr: " + path); + return getattr(path, stat); + } + + @Override + public int mkdir(final String path, final ModeWrapper mode) { +Debug.println(Level.FINE, "mkdir: " + path); + try { + fileSystem.provider().createDirectory(fileSystem.getPath(path)); + return 0; + } catch (IOException e) { +Debug.printStackTrace(e); + return -ErrorCodes.EIO(); + } + } + + @Override + public int open(final String path, final FileInfoWrapper info) { +Debug.println(Level.FINE, "open: " + path); + try { + Set options = new HashSet<>(); + options.add(StandardOpenOption.READ); + SeekableByteChannel channel = fileSystem.provider().newByteChannel(fileSystem.getPath(path), options); + long fh = fileHandle.incrementAndGet(); + fileHandles.put(fh, channel); + info.fh(fh); + + return 0; + } catch (IOException e) { +Debug.printStackTrace(e); + return -ErrorCodes.EIO(); + } + } + + /** why not defined? */ + private static final int O_NONBLOCK = 04000; + + @Override + public int read(final String path, final ByteBuffer buffer, final long size, final long offset, final FileInfoWrapper info) { +Debug.println(Level.FINE, "read: " + path + ", " + offset + ", " + size + ", " + info.fh()); + try { + if (fileHandles.containsKey(info.fh())) { + SeekableByteChannel channel = fileHandles.get(info.fh()); + if (info.nonseekable()) { + assert offset == channel.position(); + } else { + channel.position(offset); + } + int n = channel.read(buffer); + if (n > 0) { + if ((info.flags() & O_NONBLOCK) != 0) { + assert n <= 0 || n == size; + } else { + int c; + while (n < size) { + if ((c = channel.read(buffer)) <= 0) + break; + n += c; + } + } +Debug.println(Level.FINE, "read: " + n); + return n; + } else { +Debug.println(Level.FINE, "read: 0"); + return 0; // we did not read any bytes + } + } else { + return -ErrorCodes.EEXIST(); + } + } catch (IOException e) { +Debug.printStackTrace(e); + return -ErrorCodes.EIO(); + } + } + + @Override + public int readdir(final String path, final DirectoryFiller filler) { +Debug.println(Level.FINE, "readdir: " + path); + try { + fileSystem.provider().newDirectoryStream(fileSystem.getPath(path), p -> true) + .forEach(p -> { + try { + filler.add(Util.toFilenameString(p)); + } catch (IOException e) { + throw new IllegalStateException(e); + } + }); + return 0; + } catch (IOException e) { +Debug.printStackTrace(e); + return -ErrorCodes.EIO(); + } + } + + @Override + public int rename(final String path, final String newName) { +Debug.println(Level.FINE, "rename: " + path); + try { + fileSystem.provider().move(fileSystem.getPath(path), fileSystem.getPath(newName)); + return 0; + } catch (IOException e) { +Debug.printStackTrace(e); + return -ErrorCodes.EIO(); + } + } + + @Override + public int rmdir(final String path) { +Debug.println(Level.FINE, "rmdir: " + path); + try { + fileSystem.provider().delete(fileSystem.getPath(path)); + return 0; + } catch (IOException e) { +Debug.printStackTrace(e); + return -ErrorCodes.EIO(); + } + } + + @Override + public int truncate(final String path, final long offset) { +Debug.println(Level.FINE, "truncate: " + path); + // TODO + return -ErrorCodes.ENOSYS(); + } + + @Override + public int unlink(final String path) { +Debug.println(Level.FINE, "unlink: " + path); + try { + fileSystem.provider().delete(fileSystem.getPath(path)); + return 0; + } catch (IOException e) { +Debug.printStackTrace(e); + return -ErrorCodes.EIO(); + } + } + + @Override + public int write(final String path, + final ByteBuffer buf, + final long size, + final long offset, + final FileInfoWrapper info) { +Debug.println(Level.FINE, "write: " + path + ", " + offset + ", " + size + ", " + info.fh()); + try { + if (fileHandles.containsKey(info.fh())) { + SeekableByteChannel channel = fileHandles.get(info.fh()); + if (!info.append() && !info.nonseekable()) { + try { // TODO ad-hoc + channel.position(offset); + } catch (IOException e) { + if (e.getMessage().contains("@vavi")) { + long o = Long.parseLong(e.getMessage().substring(9, e.getMessage().length() - 1)); + if (offset > o) { + Debug.println(Level.SEVERE, "write: skip bad position: " + offset); + throw new IOException("cannot skip last bytes send", e); + } else { + Debug.println(Level.WARNING, "write: correct bad position: " + offset + " -> " + o); + return Math.min((int) (o - offset), (int) size); + } + } else { + throw e; + } + } + } + int n = channel.write(buf); + if (n > 0) { + if ((info.flags() & O_NONBLOCK) != 0) { + assert n <= 0 || n == size; + } else { + int c; + while (n < size) { + if ((c = channel.write(buf)) <= 0) { + break; + } + n += c; + } + } + } + return n; + } else { + return -ErrorCodes.EEXIST(); + } + } catch (NonWritableChannelException e) { +Debug.println(Level.FINER, "NonWritableChannelException: unmounting?"); + return -ErrorCodes.EIO(); + } catch (IOException e) { +Debug.printStackTrace(e); + return -ErrorCodes.EIO(); + } + } + + @Override + public int statfs(final String path, final StatvfsWrapper stat) { +Debug.println(Level.FINER, "statfs: " + path); + try { + FileStore fileStore = fileSystem.getFileStores().iterator().next(); +//Debug.println("total: " + fileStore.getTotalSpace()); +//Debug.println("free: " + fileStore.getUsableSpace()); + + long blockSize = 512; + + long total = fileStore.getTotalSpace() / blockSize; + long free = fileStore.getUsableSpace() / blockSize; + long used = total - free; + + stat.bavail(used); + stat.bfree(free); + stat.blocks(total); + stat.bsize(blockSize); + stat.favail(-1); + stat.ffree(-1); + stat.files(-1); + stat.frsize(1); + + return 0; + } catch (IOException e) { +Debug.printStackTrace(e); + return -ErrorCodes.EIO(); + } + } + + @Override + public int release(final String path, final FileInfoWrapper info) { +Debug.println(Level.FINE, "release: " + path); + try { + if (fileHandles.containsKey(info.fh())) { + Channel channel = fileHandles.get(info.fh()); + channel.close(); + return 0; + } else { + return -ErrorCodes.EEXIST(); + } + } catch (IOException e) { +Debug.println(e); + return -ErrorCodes.EIO(); + } finally { + fileHandles.remove(info.fh()); + } + } + + @Override + public int chmod(String path, ModeWrapper mode) { +Debug.println(Level.FINE, "chmod: " + path); + try { + if (fileSystem.provider().getFileStore(fileSystem.getPath(path)).supportsFileAttributeView(PosixFileAttributeView.class)) { + PosixFileAttributeView attrs = fileSystem.provider().getFileAttributeView(fileSystem.getPath(path), PosixFileAttributeView.class); + attrs.setPermissions(FuseJnaFuse.modeToPermissions(mode.mode())); + return 0; + } else { + return -Errno.EAFNOSUPPORT.ordinal(); + } + } catch (Exception e) { +Debug.printStackTrace(e); + return -ErrorCodes.EIO(); + } + } +} diff --git a/src/main/java/vavi/net/fuse/fusejna/SingleThreadJavaNioFileFS.java b/src/main/java/vavi/net/fuse/fusejna/SingleThreadJavaNioFileFS.java new file mode 100644 index 0000000..32ba7ec --- /dev/null +++ b/src/main/java/vavi/net/fuse/fusejna/SingleThreadJavaNioFileFS.java @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2016 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package vavi.net.fuse.fusejna; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.file.FileSystem; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +import net.fusejna.DirectoryFiller; +import net.fusejna.StructFuseFileInfo.FileInfoWrapper; +import net.fusejna.StructStat.StatWrapper; +import net.fusejna.types.TypeMode.ModeWrapper; + + +/** + * SingleThreadJavaNioFileFS. (fuse-jna) + * + * @author Naohide Sano (umjammer) + * @version 0.00 2016/02/29 umjammer initial version
+ */ +class SingleThreadJavaNioFileFS extends JavaNioFileFS { + + /** */ + private ExecutorService singleService = Executors.newSingleThreadExecutor(); + + /** */ + private ExecutorService multiService = Executors.newCachedThreadPool(); + + /** + * @param fileSystem + */ + public SingleThreadJavaNioFileFS(FileSystem fileSystem, Map env) throws IOException { + super(fileSystem, env); + } + + @Override + public int access(String path, int access) { + Future f = multiService.submit(() -> super.access(path, access)); + try { + return f.get(); + } catch (InterruptedException | ExecutionException e) { + throw new IllegalStateException(e); + } + } + + @Override + public int getattr(final String path, final StatWrapper stat) { + Future f = multiService.submit(() -> super.getattr(path, stat)); + try { + return f.get(); + } catch (InterruptedException | ExecutionException e) { + throw new IllegalStateException(e); + } + } + + @Override + public int create(final String path, final ModeWrapper mode, final FileInfoWrapper info) { + Future f = singleService.submit(() -> super.create(path, mode, info)); + try { + return f.get(); + } catch (InterruptedException | ExecutionException e) { + throw new IllegalStateException(e); + } + } + + @Override + public int open(final String path, final FileInfoWrapper info) { + Future f = singleService.submit(() -> super.open(path, info)); + try { + return f.get(); + } catch (InterruptedException | ExecutionException e) { + throw new IllegalStateException(e); + } + } + + @Override + public int read(final String path, final ByteBuffer buf, final long size, final long offset, final FileInfoWrapper info) { + Future f = singleService.submit(() -> super.read(path, buf, size, offset, info)); + try { + return f.get(); + } catch (InterruptedException | ExecutionException e) { + throw new IllegalStateException(e); + } + } + + @Override + public int write(final String path, + final ByteBuffer buf, + final long size, + final long offset, + final FileInfoWrapper info) { + Future f = singleService.submit(() -> super.write(path, buf, size, offset, info)); + try { + return f.get(); + } catch (InterruptedException | ExecutionException e) { + throw new IllegalStateException(e); + } + } + + @Override + public int release(final String path, final FileInfoWrapper info) { + Future f = singleService.submit(() -> super.release(path, info)); + try { + return f.get(); + } catch (InterruptedException | ExecutionException e) { + throw new IllegalStateException(e); + } + } + + @Override + public int chmod(String path, ModeWrapper mode) { + Future f = singleService.submit(() -> super.chmod(path, mode)); + try { + return f.get(); + } catch (InterruptedException | ExecutionException e) { + throw new IllegalStateException(e); + } + } + + @Override + public int mkdir(final String path, final ModeWrapper mode) { + Future f = singleService.submit(() -> super.mkdir(path, mode)); + try { + return f.get(); + } catch (InterruptedException | ExecutionException e) { + throw new IllegalStateException(e); + } + } + + @Override + public int rmdir(final String path) { + Future f = singleService.submit(() -> super.rmdir(path)); + try { + return f.get(); + } catch (InterruptedException | ExecutionException e) { + throw new IllegalStateException(e); + } + } + + @Override + public int readdir(final String path, final DirectoryFiller filler) { + Future f = singleService.submit(() -> super.readdir(path, filler)); + try { + return f.get(); + } catch (InterruptedException | ExecutionException e) { + throw new IllegalStateException(e); + } + } + + @Override + public int rename(final String path, final String newName) { + Future f = singleService.submit(() -> super.rename(path, newName)); + try { + return f.get(); + } catch (InterruptedException | ExecutionException e) { + throw new IllegalStateException(e); + } + } + + @Override + public int truncate(final String path, final long offset) { + Future f = singleService.submit(() -> super.truncate(path, offset)); + try { + return f.get(); + } catch (InterruptedException | ExecutionException e) { + throw new IllegalStateException(e); + } + } + + @Override + public int unlink(final String path) { + Future f = singleService.submit(() -> super.unlink(path)); + try { + return f.get(); + } catch (InterruptedException | ExecutionException e) { + throw new IllegalStateException(e); + } + } +} diff --git a/src/main/java/vavi/net/fuse/javafs/JavaFSFuse.java b/src/main/java/vavi/net/fuse/javafs/JavaFSFuse.java new file mode 100644 index 0000000..1d9d923 --- /dev/null +++ b/src/main/java/vavi/net/fuse/javafs/JavaFSFuse.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2020 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package vavi.net.fuse.javafs; + +import java.io.IOException; +import java.nio.file.FileSystem; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; + +import vavi.net.fuse.Fuse; +import vavi.util.Debug; + +import co.paralleluniverse.javafs.JavaFS; + + +/** + * JavaFSFuse. (javafs engine) + * + * @author Naohide Sano (umjammer) + * @version 0.00 2020/05/29 umjammer initial version
+ */ +public class JavaFSFuse implements Fuse { + + public static final String ENV_READ_ONLY = "readOnly"; + + public static final String ENV_DEBUG = "debug"; + + /** */ + private String mountPoint; + + @Override + public void mount(FileSystem fs, String mountPoint, Map env) throws IOException { + this.mountPoint = mountPoint; + boolean readOnly = false; + boolean debug = false; + Map env_ = null; + if (env != null) { + if (env.containsKey(ENV_READ_ONLY)) { + readOnly = Boolean.parseBoolean(String.valueOf(env.get(ENV_READ_ONLY))); + env.remove(ENV_READ_ONLY); + } + if (env.containsKey(ENV_DEBUG)) { + debug = Boolean.parseBoolean(String.valueOf(env.get(ENV_DEBUG))); + env.remove(ENV_DEBUG); + } + env_ = new HashMap<>(); + for (Map.Entry e : env.entrySet()) { + env_.put(e.getKey(), e.getValue() == null ? null : String.valueOf(e.getValue())); + } + } +//Debug.println("debug: " + debug); +//Debug.println("readonly: " + debug); + JavaFS.mount(fs, Paths.get(mountPoint), readOnly, debug, env_); + Runtime.getRuntime().addShutdownHook(new Thread(() -> { try { close(); } catch (IOException e) { e.printStackTrace(); }})); + } + + @Override + public void close() throws IOException { + try { + if (mountPoint != null) { +Debug.println("umount..."); + JavaFS.unmount(Paths.get(mountPoint)); + mountPoint = null; +Debug.println("umount done"); + } + } catch (IllegalStateException e) { + if (e.getMessage().contains("Tried to unmount a filesystem which is not mounted")) { +Debug.println("already umount"); + } else { + throw e; + } + } catch (IOException e) { +Debug.println(Level.WARNING, "umount: " + e); + throw e; + } + } +} + +/* */ diff --git a/src/main/java/vavi/net/fuse/javafs/JavaFSFuseProvider.java b/src/main/java/vavi/net/fuse/javafs/JavaFSFuseProvider.java new file mode 100644 index 0000000..ea712cb --- /dev/null +++ b/src/main/java/vavi/net/fuse/javafs/JavaFSFuseProvider.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2020 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package vavi.net.fuse.javafs; + +import vavi.net.fuse.Fuse; +import vavi.net.fuse.FuseProvider; + + +/** + * JavaFSFuseProvider. + * + * @author Naohide Sano (umjammer) + * @version 0.00 2020/06/03 umjammer initial version
+ */ +public class JavaFSFuseProvider implements FuseProvider { + + @Override + public Fuse getFuse() { + return new JavaFSFuse(); + } +} + +/* */ diff --git a/src/main/java/vavi/net/fuse/jnrfuse/JavaNioFileFS.java b/src/main/java/vavi/net/fuse/jnrfuse/JavaNioFileFS.java new file mode 100644 index 0000000..5e0d0ea --- /dev/null +++ b/src/main/java/vavi/net/fuse/jnrfuse/JavaNioFileFS.java @@ -0,0 +1,415 @@ +/* + * Copyright (c) 2016 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package vavi.net.fuse.jnrfuse; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.SeekableByteChannel; +import java.nio.file.FileStore; +import java.nio.file.FileSystem; +import java.nio.file.LinkOption; +import java.nio.file.NoSuchFileException; +import java.nio.file.OpenOption; +import java.nio.file.StandardOpenOption; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.PosixFileAttributeView; +import java.nio.file.attribute.PosixFileAttributes; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; +import java.util.logging.Level; + +import jnr.constants.platform.Errno; +import jnr.ffi.Pointer; +import jnr.ffi.types.mode_t; +import jnr.ffi.types.off_t; +import jnr.ffi.types.size_t; +import ru.serce.jnrfuse.ErrorCodes; +import ru.serce.jnrfuse.FuseFillDir; +import ru.serce.jnrfuse.FuseStubFS; +import ru.serce.jnrfuse.struct.FileStat; +import ru.serce.jnrfuse.struct.Flock; +import ru.serce.jnrfuse.struct.FuseFileInfo; +import ru.serce.jnrfuse.struct.Statvfs; +import vavi.nio.file.Util; +import vavi.util.Debug; + + +/** + * JavaNioFileFS. (jnr-fuse) + * + * @author Naohide Sano (umjammer) + * @version 0.00 2016/02/29 umjammer initial version
+ */ +class JavaNioFileFS extends FuseStubFS { + + private static final int BUFFER_SIZE = 0x10000; + + /** */ + private transient FileSystem fileSystem; + + /** key for env, no need to specify value */ + static final String ENV_IGNORE_APPLE_DOUBLE = "noappledouble"; + + /** */ + private final AtomicLong fileHandle = new AtomicLong(0); + + /** */ + private static final ConcurrentMap fileHandles = new ConcurrentHashMap<>(); + + protected boolean ignoreAppleDouble; + + /** + * @param fileSystem a file system to wrap by fuse + */ + public JavaNioFileFS(FileSystem fileSystem, Map env) { + this.fileSystem = fileSystem; + ignoreAppleDouble = JnrFuseFuse.isEnabled(ENV_IGNORE_APPLE_DOUBLE, env); +Debug.println(Level.FINE, "ENV_IGNORE_APPLE_DOUBLE: " + ignoreAppleDouble); + } + + @Override + public int access(String path, int access) { +Debug.println(Level.FINEST, "access: " + path); + try { + // TODO access + fileSystem.provider().checkAccess(fileSystem.getPath(path)); + return 0; + } catch (NoSuchFileException e) { +Debug.println(e); + return -ErrorCodes.ENOENT(); + } catch (IOException e) { +Debug.println(e); + return -ErrorCodes.EACCES(); + } + } + + @Override + public int create(String path, @mode_t long mode, FuseFileInfo info) { +Debug.println(Level.FINE, "create: " + path); + try { + Set options = new HashSet<>(); + options.add(StandardOpenOption.WRITE); + options.add(StandardOpenOption.CREATE_NEW); + SeekableByteChannel channel = fileSystem.provider().newByteChannel(fileSystem.getPath(path), options); + long fh = fileHandle.incrementAndGet(); + fileHandles.put(fh, channel); + info.fh.set(fh); + + return 0; + } catch (IOException e) { +Debug.printStackTrace(e); + return -ErrorCodes.EIO(); + } + } + + @Override + public int getattr(String path, FileStat stat) { +Debug.println(Level.FINEST, "getattr: " + path); + try { + BasicFileAttributes attributes = + fileSystem.provider().readAttributes(fileSystem.getPath(path), BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS); + + if (attributes instanceof PosixFileAttributes) { + long mode = JnrFuseFuse.permissionsToMode(((PosixFileAttributes) attributes).permissions()); + if (attributes.isDirectory()) { + stat.st_mode.set(FileStat.S_IFDIR | mode); + stat.st_mtim.tv_sec.set(attributes.lastModifiedTime().to(TimeUnit.SECONDS)); + stat.st_ctim.tv_sec.set(attributes.creationTime().to(TimeUnit.SECONDS)); + } else { + stat.st_mode.set(FileStat.S_IFREG | mode); + stat.st_mtim.tv_sec.set(attributes.lastModifiedTime().to(TimeUnit.SECONDS)); + stat.st_ctim.tv_sec.set(attributes.creationTime().to(TimeUnit.SECONDS)); + stat.st_size.set(attributes.size()); + } + } else { + if (attributes.isDirectory()) { + stat.st_mode.set(FileStat.S_IFDIR | 0755); + stat.st_mtim.tv_sec.set(attributes.lastModifiedTime().to(TimeUnit.SECONDS)); + stat.st_ctim.tv_sec.set(attributes.creationTime().to(TimeUnit.SECONDS)); + } else { + stat.st_mode.set(FileStat.S_IFREG | 0644); + stat.st_mtim.tv_sec.set(attributes.lastModifiedTime().to(TimeUnit.SECONDS)); + stat.st_ctim.tv_sec.set(attributes.creationTime().to(TimeUnit.SECONDS)); + stat.st_size.set(attributes.size()); + } + } + return 0; + } catch (NoSuchFileException e) { + if (e.getMessage().startsWith("ignore apple double file:")) { +Debug.println(Level.FINEST, e.getMessage()); + return 0; + } else { + if (ignoreAppleDouble) { + if (Util.isAppleDouble(path)) { +Debug.println(Level.FINEST, e.getMessage()); + } else { +Debug.println(Level.FINE, e); + } + } + return -ErrorCodes.ENOENT(); + } + } catch (IOException e) { +Debug.printStackTrace(e); + return -ErrorCodes.EIO(); + } + } + + @Override + public int fgetattr(String path, FileStat stat, FuseFileInfo info) + { +Debug.println(Level.FINE, "fgetattr: " + path); + return getattr(path, stat); + } + + @Override + public int mkdir(String path, @mode_t long mode) { +Debug.println(Level.FINE, "mkdir: " + path); + try { + fileSystem.provider().createDirectory(fileSystem.getPath(path)); + return 0; + } catch (IOException e) { +Debug.printStackTrace(e); + return -ErrorCodes.EIO(); + } + } + + @Override + public int open(String path, FuseFileInfo info) { + try { + Set options = new HashSet<>(); + options.add(StandardOpenOption.READ); + SeekableByteChannel channel = fileSystem.provider().newByteChannel(fileSystem.getPath(path), options); + long fh = fileHandle.incrementAndGet(); + fileHandles.put(fh, channel); + info.fh.set(fh); +Debug.println(Level.FINE, "open: " + path + ", fh: " + fh); + + return 0; + } catch (IOException e) { +Debug.printStackTrace(e); + return -ErrorCodes.EIO(); + } + } + + @Override + public int read(String path, Pointer buf, long size, long offset, FuseFileInfo info) { +Debug.println(Level.FINE, "read: " + path + ", " + offset + ", " + size + ", fh: " + info.fh.get()); + try { + if (fileHandles.containsKey(info.fh.get())) { + SeekableByteChannel channel = fileHandles.get(info.fh.get()); + channel.position(offset); + ByteBuffer bb = ByteBuffer.allocate(BUFFER_SIZE); + long pos = 0; +Debug.printf(Level.FINER, "Attempting to read %d-%d:", offset, offset + size); + do { + bb.clear(); + bb.limit((int) Math.min(bb.capacity(), size - pos)); + int read = channel.read(bb); + if (read == -1) { +Debug.println(Level.FINER, "Reached EOF"); + return (int) pos; // reached EOF TODO: wtf cast + } else { +Debug.printf(Level.FINER, "Reading %d-%d", offset + pos, offset + pos + read); + buf.put(pos, bb.array(), 0, read); + pos += read; + } + } while (pos < size); + return (int) pos; + } else { +Debug.println(Level.FINE, "read: no fh: " + path); + return -ErrorCodes.EEXIST(); + } + } catch (IOException e) { +Debug.printStackTrace(e); + return -ErrorCodes.EIO(); + } + } + + @Override + public int readdir(String path, Pointer buf, FuseFillDir filler, @off_t long offset, FuseFileInfo info) { +Debug.println(Level.FINER, "readdir: " + path); + try { + fileSystem.provider().newDirectoryStream(fileSystem.getPath(path), p -> true) + .forEach(p -> { +Debug.println(Level.FINER, "p: " + p); + try { + filler.apply(buf, Util.toFilenameString(p), null, 0); + } catch (IOException e) { + throw new IllegalStateException(e); + } + }); + return 0; + } catch (IOException e) { +Debug.printStackTrace(e); + return -ErrorCodes.EIO(); + } + } + + @Override + public int rename(String path, String newName) { +Debug.println(Level.FINE, "rename: " + path); + try { + fileSystem.provider().move(fileSystem.getPath(path), fileSystem.getPath(newName)); + return 0; + } catch (IOException e) { +Debug.printStackTrace(e); + return -ErrorCodes.EIO(); + } + } + + @Override + public int rmdir(String path) { +Debug.println(Level.FINE, "rmdir: " + path); + try { + fileSystem.provider().delete(fileSystem.getPath(path)); + return 0; + } catch (IOException e) { +Debug.printStackTrace(e); + return -ErrorCodes.EIO(); + } + } + + @Override + public int truncate(String path, long offset) { +Debug.println(Level.FINE, "truncate: " + path); + // TODO + return -ErrorCodes.ENOSYS(); + } + + @Override + public int unlink(String path) { +Debug.println(Level.FINE, "unlink: " + path); + try { + fileSystem.provider().delete(fileSystem.getPath(path)); + return 0; + } catch (IOException e) { +Debug.printStackTrace(e); + return -ErrorCodes.EIO(); + } + } + + @Override + public int write(String path, Pointer buf, @size_t long size, @off_t long offset, FuseFileInfo info) { +Debug.println(Level.FINE, "write: " + path + ", " + offset + ", " + size + ", fh: " + info.fh.get()); + try { + if (fileHandles.containsKey(info.fh.get())) { + ByteBuffer bb = ByteBuffer.allocate(BUFFER_SIZE); + long written = 0; + SeekableByteChannel channel = fileHandles.get(info.fh.get()); +try { // TODO ad-hoc + channel.position(offset); +} catch (IOException e) { + if (e.getMessage().contains("@vavi")) { + long o = Long.parseLong(e.getMessage().substring(9, e.getMessage().length() - 1)); + if (offset > o) { + Debug.println(Level.SEVERE, "write: skip bad position: " + offset); + throw new IOException("cannot skip last bytes send", e); + } else { + Debug.println(Level.WARNING, "write: correct bad position: " + offset + " -> " + o); + return Math.min((int) (o - offset), (int) size); + } + } else { + throw e; + } +} + do { + long remaining = size - written; + bb.clear(); + int len = (int) Math.min(remaining, bb.capacity()); + buf.get(written, bb.array(), 0, len); + bb.limit(len); + int r = channel.write(bb); + written += r; + } while (written < size); + return (int) written; + } else { + return -ErrorCodes.EEXIST(); + } + } catch (IOException e) { +Debug.printStackTrace(e); + return -ErrorCodes.EIO(); + } + } + + @Override + public int statfs(String path, Statvfs stbuf) { +Debug.println(Level.FINEST, "statfs: " + path); + try { + FileStore fileStore = fileSystem.getFileStores().iterator().next(); +//Debug.println("total: " + fileStore.getTotalSpace()); +//Debug.println("free: " + fileStore.getUsableSpace()); + + long blockSize = 512; + + long total = fileStore.getTotalSpace() / blockSize; + long free = fileStore.getUsableSpace() / blockSize; + long used = total - free; + + stbuf.f_bavail.set(used); + stbuf.f_bfree.set(free); + stbuf.f_blocks.set(total); + stbuf.f_bsize.set(blockSize); + stbuf.f_favail.set(-1); + stbuf.f_ffree.set(-1); + stbuf.f_files.set(-1); + stbuf.f_frsize.set(1); + + return 0; + } catch (IOException e) { +Debug.printStackTrace(e); + return -ErrorCodes.EIO(); + } + } + + @Override + public int chmod(String path, @mode_t long mode) { +Debug.println(Level.FINE, "chmod: " + path); + try { + if (fileSystem.provider().getFileStore(fileSystem.getPath(path)).supportsFileAttributeView(PosixFileAttributeView.class)) { + PosixFileAttributeView attrs = fileSystem.provider().getFileAttributeView(fileSystem.getPath(path), PosixFileAttributeView.class); + attrs.setPermissions(JnrFuseFuse.modeToPermissions(mode)); + return 0; + } else { + return -Errno.EAFNOSUPPORT.ordinal(); + } + } catch (Exception e) { +Debug.printStackTrace(e); + return -ErrorCodes.EIO(); + } + } + + @Override + public int release(String path, FuseFileInfo info) { +Debug.println(Level.FINE, "release: " + path + ", fh: " + info.fh.get()); + try { + if (fileHandles.containsKey(info.fh.get())) { + SeekableByteChannel channel = fileHandles.get(info.fh.get()); + channel.close(); + return 0; + } else { +Debug.println(Level.FINE, "release: no fh: " + path); + return -ErrorCodes.EEXIST(); + } + } catch (IOException e) { +Debug.printStackTrace(e); + return -ErrorCodes.EIO(); + } finally { + fileHandles.remove(info.fh.get()); + } + } + + @Override + public int lock(String path, FuseFileInfo info, int cmd, Flock flock) { +Debug.println(Level.FINE, "lock: " + path + ", fh: " + info.fh.get()); + return 0; + } +} diff --git a/src/main/java/vavi/net/fuse/jnrfuse/JnrFuseFuse.java b/src/main/java/vavi/net/fuse/jnrfuse/JnrFuseFuse.java new file mode 100644 index 0000000..403e3d3 --- /dev/null +++ b/src/main/java/vavi/net/fuse/jnrfuse/JnrFuseFuse.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2020 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package vavi.net.fuse.jnrfuse; + +import java.io.IOException; +import java.nio.file.FileSystem; +import java.nio.file.Paths; +import java.nio.file.attribute.PosixFilePermission; +import java.util.EnumSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import co.paralleluniverse.fuse.TypeMode; +import ru.serce.jnrfuse.FuseStubFS; +import vavi.net.fuse.Fuse; +import vavi.util.Debug; + + +/** + * JnrFuseFuse. (jnr-fuse engine) + * + * @author Naohide Sano (umjammer) + * @version 0.00 2020/05/29 umjammer initial version
+ */ +public class JnrFuseFuse implements Fuse { + + /** key for env, no need to specify value */ + public static final String ENV_IGNORE_APPLE_DOUBLE = JavaNioFileFS.ENV_IGNORE_APPLE_DOUBLE; + + /** TODO utility delegate */ + static boolean isEnabled(String key, Map map) { + return Fuse.isEnabled(key, map); + } + + /** */ + private FuseStubFS fuse; + + /** non-daemon thread */ + private ExecutorService es = Executors.newSingleThreadExecutor(); + + @Override + public void mount(FileSystem fs, String mountPoint, Map env) throws IOException { + if (env.containsKey(ENV_SINGLE_THREAD) && (Boolean) env.get(ENV_SINGLE_THREAD)) { + fuse = new SingleThreadJavaNioFileFS(fs, env); +Debug.println("use single thread"); + } else { + fuse = new JavaNioFileFS(fs, env); + } + es.submit(() -> { + // jnrfuse non-blocking thread is daemon + // so make mount blocking and make own non-daemon thread + fuse.mount(Paths.get(mountPoint), true); + }); + Runtime.getRuntime().addShutdownHook(new Thread(() -> { try { close(); } catch (IOException e) { e.printStackTrace(); }})); + } + + @Override + public void close() throws IOException { + if (fuse != null) { +Debug.println("unmount..."); + fuse.umount(); + fuse = null; + es.shutdown(); +Debug.println("unmount done"); + } + } + + /** */ + static long permissionsToMode(Set permissions) { + long mode = 0; + for (PosixFilePermission px : permissions) { + switch (px) { + case OWNER_READ: mode |= TypeMode.S_IRUSR; break; + case OWNER_WRITE: mode |= TypeMode.S_IWUSR; break; + case OWNER_EXECUTE: mode |= TypeMode.S_IXUSR; break; + case GROUP_READ: mode |= TypeMode.S_IRGRP; break; + case GROUP_WRITE: mode |= TypeMode.S_IWGRP; break; + case GROUP_EXECUTE: mode |= TypeMode.S_IXGRP; break; + case OTHERS_READ: mode |= TypeMode.S_IROTH; break; + case OTHERS_WRITE: mode |= TypeMode.S_IWOTH; break; + case OTHERS_EXECUTE: mode |= TypeMode.S_IXOTH; break; + } + } + return mode; + } + + /** */ + static Set modeToPermissions(long mode) { + EnumSet permissions = EnumSet.noneOf(PosixFilePermission.class); + if ((mode & TypeMode.S_IRUSR) != 0) + permissions.add(PosixFilePermission.OWNER_READ); + if ((mode & TypeMode.S_IWUSR) != 0) + permissions.add(PosixFilePermission.OWNER_WRITE); + if ((mode & TypeMode.S_IXUSR) != 0) + permissions.add(PosixFilePermission.OWNER_EXECUTE); + if ((mode & TypeMode.S_IRGRP) != 0) + permissions.add(PosixFilePermission.GROUP_READ); + if ((mode & TypeMode.S_IWGRP) != 0) + permissions.add(PosixFilePermission.GROUP_WRITE); + if ((mode & TypeMode.S_IXGRP) != 0) + permissions.add(PosixFilePermission.GROUP_EXECUTE); + if ((mode & TypeMode.S_IROTH) != 0) + permissions.add(PosixFilePermission.OTHERS_READ); + if ((mode & TypeMode.S_IWOTH) != 0) + permissions.add(PosixFilePermission.OTHERS_WRITE); + if ((mode & TypeMode.S_IXOTH) != 0) + permissions.add(PosixFilePermission.OTHERS_EXECUTE); + return permissions; + } +} + +/* */ diff --git a/src/main/java/vavi/net/fuse/jnrfuse/JnrFuseFuseProvider.java b/src/main/java/vavi/net/fuse/jnrfuse/JnrFuseFuseProvider.java new file mode 100644 index 0000000..094a85d --- /dev/null +++ b/src/main/java/vavi/net/fuse/jnrfuse/JnrFuseFuseProvider.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2020 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package vavi.net.fuse.jnrfuse; + +import vavi.net.fuse.Fuse; +import vavi.net.fuse.FuseProvider; + + +/** + * JnrFuseFuseProvider. + * + * @author Naohide Sano (umjammer) + * @version 0.00 2020/06/03 umjammer initial version
+ */ +public class JnrFuseFuseProvider implements FuseProvider { + + @Override + public Fuse getFuse() { + return new JnrFuseFuse(); + } +} + +/* */ diff --git a/src/main/java/vavi/net/fuse/jnrfuse/SingleThreadJavaNioFileFS.java b/src/main/java/vavi/net/fuse/jnrfuse/SingleThreadJavaNioFileFS.java new file mode 100644 index 0000000..d4d32e1 --- /dev/null +++ b/src/main/java/vavi/net/fuse/jnrfuse/SingleThreadJavaNioFileFS.java @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2016 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package vavi.net.fuse.jnrfuse; + +import java.io.IOException; +import java.nio.file.FileSystem; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +import jnr.ffi.Pointer; +import jnr.ffi.types.off_t; +import ru.serce.jnrfuse.FuseFillDir; +import ru.serce.jnrfuse.struct.FileStat; +import ru.serce.jnrfuse.struct.FuseFileInfo; + + +/** + * SingleThreadJavaNioFileFS. (jnr-fuse) + * + * @author Naohide Sano (umjammer) + * @version 0.00 2016/02/29 umjammer initial version
+ */ +class SingleThreadJavaNioFileFS extends JavaNioFileFS { + + /** */ + private ExecutorService singleService = Executors.newSingleThreadExecutor(); + + /** */ + private ExecutorService multiService = Executors.newCachedThreadPool(); + + /** + * @param fileSystem + */ + public SingleThreadJavaNioFileFS(FileSystem fileSystem, Map env) throws IOException { + super(fileSystem, env); + } + + @Override + public int access(String path, int access) { + Future f = multiService.submit(() -> { + return super.access(path, access); + }); + try { + return f.get(); + } catch (InterruptedException | ExecutionException e) { + throw new IllegalStateException(e); + } + } + + @Override + public int getattr(final String path, final FileStat stat) { + Future f = multiService.submit(() -> super.getattr(path, stat)); + try { + return f.get(); + } catch (InterruptedException | ExecutionException e) { + throw new IllegalStateException(e); + } + } + + @Override + public int create(final String path, final long mode, final FuseFileInfo info) { + Future f = singleService.submit(() -> super.create(path, mode, info)); + try { + return f.get(); + } catch (InterruptedException | ExecutionException e) { + throw new IllegalStateException(e); + } + } + + @Override + public int open(final String path, final FuseFileInfo info) { + Future f = singleService.submit(() -> super.open(path, info)); + try { + return f.get(); + } catch (InterruptedException | ExecutionException e) { + throw new IllegalStateException(e); + } + } + + @Override + public int read(final String path, final Pointer buf, final long size, final long offset, final FuseFileInfo info) { + Future f = singleService.submit(() -> super.read(path, buf, size, offset, info)); + try { + return f.get(); + } catch (InterruptedException | ExecutionException e) { + throw new IllegalStateException(e); + } + } + + @Override + public int write(final String path, + final Pointer buf, + final long size, + final long offset, + final FuseFileInfo info) { + Future f = singleService.submit(() -> super.write(path, buf, size, offset, info)); + try { + return f.get(); + } catch (InterruptedException | ExecutionException e) { + throw new IllegalStateException(e); + } + } + + @Override + public int release(final String path, final FuseFileInfo info) { + Future f = singleService.submit(() -> super.release(path, info)); + try { + return f.get(); + } catch (InterruptedException | ExecutionException e) { + throw new IllegalStateException(e); + } + } + + @Override + public int chmod(String path, long mode) { + Future f = singleService.submit(() -> super.chmod(path, mode)); + try { + return f.get(); + } catch (InterruptedException | ExecutionException e) { + throw new IllegalStateException(e); + } + } + + @Override + public int mkdir(final String path, final long mode) { + Future f = singleService.submit(() -> super.mkdir(path, mode)); + try { + return f.get(); + } catch (InterruptedException | ExecutionException e) { + throw new IllegalStateException(e); + } + } + + @Override + public int rmdir(final String path) { + Future f = singleService.submit(() -> super.rmdir(path)); + try { + return f.get(); + } catch (InterruptedException | ExecutionException e) { + throw new IllegalStateException(e); + } + } + + @Override + public int readdir(final String path, Pointer buf, final FuseFillDir filler, @off_t long offset, FuseFileInfo info) { + Future f = singleService.submit(() -> super.readdir(path, buf, filler, offset, info)); + try { + return f.get(); + } catch (InterruptedException | ExecutionException e) { + throw new IllegalStateException(e); + } + } + + @Override + public int rename(final String path, final String newName) { + Future f = singleService.submit(() -> super.rename(path, newName)); + try { + return f.get(); + } catch (InterruptedException | ExecutionException e) { + throw new IllegalStateException(e); + } + } + + @Override + public int truncate(final String path, final long offset) { + Future f = singleService.submit(() -> super.truncate(path, offset)); + try { + return f.get(); + } catch (InterruptedException | ExecutionException e) { + throw new IllegalStateException(e); + } + } + + @Override + public int unlink(final String path) { + Future f = singleService.submit(() -> super.unlink(path)); + try { + return f.get(); + } catch (InterruptedException | ExecutionException e) { + throw new IllegalStateException(e); + } + } +} diff --git a/src/main/java/vavi/nio/file/Cache.java b/src/main/java/vavi/nio/file/Cache.java deleted file mode 100644 index 4c46114..0000000 --- a/src/main/java/vavi/nio/file/Cache.java +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright (c) 2017 by Naohide Sano, All rights reserved. - * - * Programmed by Naohide Sano - */ - -package vavi.nio.file; - -import java.io.IOException; -import java.nio.file.NoSuchFileException; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Function; -import java.util.stream.Collectors; - - -/** - * Cache. - * - * @author Naohide Sano (umjammer) - * @version 0.00 2017/03/16 umjammer initial version
- */ -public abstract class Cache { - /** <{@link Path}, {@link T}> */ - protected Map entryCache = new ConcurrentHashMap<>(); // TODO refresh - - /** <{@link Path}, {@link List}> */ - protected Map> folderCache = new ConcurrentHashMap<>(); // TODO refresh - - /** allow duplicated name in the same directory or not, e.g. google drive allows it. */ - private boolean allowDuplicatedName = false; - - /** sets allow duplicated name in the same directory or not */ - public void setAllowDuplicatedName(boolean allowDuplicatedName) { - this.allowDuplicatedName = allowDuplicatedName; - } - - /** There is metadata or not. */ - public boolean containsFile(Path path) { - return entryCache.containsKey(path.toAbsolutePath()); - } - - /** raw operation for the cache */ - public T getFile(Path path) { - return entryCache.get(path.toAbsolutePath()); - } - - /** raw operation for the cache */ - public T putFile(Path path, T entry) { -//System.err.println("CACHE.0: " + path); - return entryCache.put(path.toAbsolutePath(), entry); - } - - /** There are children's metadata or not. */ - public boolean containsFolder(Path path) { - return folderCache.containsKey(path.toAbsolutePath()); - } - - /** Gets children path. */ - public List getFolder(Path path) { - return folderCache.get(path.toAbsolutePath()); - } - - /** - * @return -1 if path does not exist - */ - public int getChildCount(Path path) { - return containsFolder(path) ? getFolder(path).size() : -1; - } - - /** raw operation for the folder cache */ - public List putFolder(Path path, List children) { - return folderCache.put(path.toAbsolutePath(), children.stream().map(Path::toAbsolutePath).collect(Collectors.toList())); - } - - /** parent folder cache will be modified */ - public void addEntry(Path path, T entry) { -//System.err.println("CACHE.1: " + path); - entryCache.put(path.toAbsolutePath(), entry); - // parent - Path parentPath = path.toAbsolutePath().getParent(); - List bros = folderCache.computeIfAbsent(parentPath.toAbsolutePath(), k -> new ArrayList<>()); - if (!bros.contains(path.toAbsolutePath()) || allowDuplicatedName) { -//System.err.println("DIR CACHE.1: " + path); - bros.add(path.toAbsolutePath()); - } - } - - /** parent folder cache will be modified */ - public void removeEntry(Path path) { - entryCache.remove(path.toAbsolutePath()); - // parent - Path parentPath = path.toAbsolutePath().getParent(); - List bros = folderCache.get(parentPath.toAbsolutePath()); - if (bros != null) { - bros.remove(path.toAbsolutePath()); - } - } - - /** for folder */ - public void moveEntry(Path source, Path target, T entry) { - List children = getFolder(source); - if (children != null) { - folderCache.remove(source.toAbsolutePath()); - } - removeEntry(source); - addEntry(target, entry); - if (children != null) { - putFolder(target, changeParent(children, target)); -//getFolder(target).forEach(System.err::println); - } - } - - /** move folder */ - private List changeParent(List children, Path parent) { - return children.stream().map(p -> parent.resolve(p.getFileName())).collect(Collectors.toList()); - } - - /** - * query for cache - * @throws NoSuchFileException must be thrown when the path is not found. - */ - public abstract T getEntry(Path path) throws IOException; - - /** - * @throws NoSuchFileException is thrown when the path is not found. - */ - public boolean existsEntry(Path path) throws IOException { - try { - getEntry(path); - return true; - } catch (NoSuchFileException e) { - return false; - } - } - - /** - * query for opposite direction - * uses {@link Object#equals(Object)} for comparison - * @throws NoSuchElementException when not found - */ - public Path getEntry(T target) { - for (Map.Entry e : entryCache.entrySet()) { - if (e.getValue().equals(target)) { - return e.getKey(); - } - } - throw new NoSuchElementException(target.toString()); - } - - /** - * query for opposite direction - * @param query is used for comparison - * @throws NoSuchElementException when not found - */ - public Path getEntry(Function query) { - for (Map.Entry e : entryCache.entrySet()) { - if (query.apply(e.getValue())) { - return e.getKey(); - } - } - throw new NoSuchElementException(query.toString()); - } -} - -/* */ diff --git a/src/main/java/vavi/nio/file/UploadMonitor.java b/src/main/java/vavi/nio/file/UploadMonitor.java deleted file mode 100644 index 7c5066c..0000000 --- a/src/main/java/vavi/nio/file/UploadMonitor.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2017 by Naohide Sano, All rights reserved. - * - * Programmed by Naohide Sano - */ - -package vavi.nio.file; - -import java.nio.file.Path; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - - -/** - * UploadMonitor. - * - * TODO {@link java.util.concurrent.locks.ReentrantReadWriteLock}??? - * - * @author Naohide Sano (umjammer) - * @version 0.00 2017/03/19 umjammer initial version
- */ -public class UploadMonitor { - - /** */ - private Map uploadFlags = new ConcurrentHashMap<>(); - - /** */ - public void start(Path path, T entry) { - uploadFlags.put(path, entry); - } - - /** */ - public void finish(Path path) { - uploadFlags.remove(path); - } - - /** */ - public boolean isUploading(Path path) { - return uploadFlags.containsKey(path); - } - - /** */ - public T entry(Path path) { - return uploadFlags.get(path); - } -} - -/* */ diff --git a/src/main/java/vavi/nio/file/Util.java b/src/main/java/vavi/nio/file/Util.java deleted file mode 100644 index 92f599a..0000000 --- a/src/main/java/vavi/nio/file/Util.java +++ /dev/null @@ -1,509 +0,0 @@ -/* - * Copyright (c) 2017 by Naohide Sano, All rights reserved. - * - * Programmed by Naohide Sano - */ - -package vavi.nio.file; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.FilterInputStream; -import java.io.FilterOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.nio.channels.Channels; -import java.nio.channels.NonReadableChannelException; -import java.nio.channels.NonWritableChannelException; -import java.nio.channels.ReadableByteChannel; -import java.nio.channels.SeekableByteChannel; -import java.nio.channels.WritableByteChannel; -import java.nio.file.DirectoryStream; -import java.nio.file.OpenOption; -import java.nio.file.Path; -import java.nio.file.StandardOpenOption; -import java.text.Normalizer; -import java.text.Normalizer.Form; -import java.util.Iterator; -import java.util.List; -import java.util.Set; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.logging.Level; -import java.util.stream.Collectors; - -import vavi.io.Seekable; -import vavi.util.Debug; - - -/** - * Util. - * - * @author Naohide Sano (umjammer) - * @version 0.00 2017/03/19 umjammer initial version
- */ -public interface Util { - - /** to NFC string */ - static String toPathString(Path path) throws IOException { - return toNormalizedString(path.toRealPath().toString()); - } - - /** to NFC string */ - static String toFilenameString(Path path) throws IOException { - return toNormalizedString(path.toRealPath().getFileName().toString()); - } - - /** to NFC string */ - static String toNormalizedString(String string) throws IOException { - return Normalizer.normalize(string, Form.NFC); - } - - /** - * @see "ignoreAppleDouble" - */ - static boolean isAppleDouble(Path path) throws IOException { -//System.err.println("path.toRealPath(): " + path.toRealPath()); -//System.err.println("path.getFileName(): " + path.getFileName()); - return isAppleDouble(path.getFileName().toString()); - } - - /** - * TODO out source - * - * @see "ignoreAppleDouble" - */ - static boolean isAppleDouble(String filename) { - return filename.startsWith("._") || - filename.equals(".DS_Store") || - filename.equals(".localized") || - filename.equals(".hidden"); - } - - /** - * @return nullable - */ - static T getOneOfOptions(Class clazz, Set options) { - if (options != null && options.stream().anyMatch(clazz::isInstance)) { - return clazz.cast(options.stream().filter(clazz::isInstance).findFirst().get()); - } else { - return null; - } - } - - /** - * @see java.nio.file.Files#newDirectoryStream(Path, java.nio.file.DirectoryStream.Filter) - */ - static DirectoryStream newDirectoryStream(final List list, - final DirectoryStream.Filter filter) { - List filtered = filter != null ? list.stream().filter(p -> { - try { - return filter.accept(p); - } catch (IOException e) { - throw new IllegalStateException(e); - } - }).collect(Collectors.toList()) : list; - - return new DirectoryStream() { - private final AtomicBoolean alreadyOpen = new AtomicBoolean(false); - - @Override - public Iterator iterator() { - // required by the contract - if (alreadyOpen.getAndSet(true)) { - throw new IllegalStateException("already open"); - } - return filtered.iterator(); - } - - @Override - public void close() throws IOException { - } - }; - } - - /** - * @see java.nio.file.Files#newByteChannel(Path, Set, java.nio.file.attribute.FileAttribute...) - */ - abstract class SeekableByteChannelForWriting implements SeekableByteChannel { - OutputStream out; - protected long written; - private WritableByteChannel wbc; - - public SeekableByteChannelForWriting(OutputStream out) throws IOException { - this.out = out; - this.wbc = Channels.newChannel(out); - this.written = getLeftOver(); - } - - /** */ - protected abstract long getLeftOver() throws IOException; - - @Override - public boolean isOpen() { - return wbc.isOpen(); - } - - @Override - public long position() throws IOException { - if (out instanceof Seekable) { - // see com.github.fge.filesystem.driver.DoubleCachedFileSystemDriver#downloadEntry - written = ((Seekable) out).position(); -Debug.println(Level.FINE, "SeekableByteChannelForWriting: get position by vavi.io.Seekable: " + written); - } else if (wbc instanceof SeekableByteChannel) { - written = ((SeekableByteChannel) wbc).position(); -Debug.println(Level.FINE, "SeekableByteChannelForWriting: get position by java.nio.channels.SeekableByteChannel: " + written); - } else { -Debug.println(Level.WARNING, "SeekableByteChannelForWriting: get position: " + written + ", " + out.getClass().getName()); - } - - return written; - } - - @Override - public SeekableByteChannel position(long pos) throws IOException { - if (out instanceof Seekable) { - // see com.github.fge.filesystem.driver.DoubleCachedFileSystemDriver#downloadEntry -Debug.println(Level.FINE, "SeekableByteChannelForWriting: set position by vavi.io.Seekable: " + pos); - ((Seekable) out).position(pos); - } else if (wbc instanceof SeekableByteChannel) { -Debug.println(Level.FINE, "SeekableByteChannelForWriting: set position by java.nio.channels.SeekableByteChannel: " + pos); - ((SeekableByteChannel) wbc).position(pos); - } else { -Debug.println(Level.WARNING, "SeekableByteChannelForWriting: set position: " + pos + ", " + out.getClass().getName()); - } - - written = pos; - return this; - } - - @Override - public int read(ByteBuffer dst) throws IOException { - throw new NonReadableChannelException(); - } - - @Override - public SeekableByteChannel truncate(long size) throws IOException { -Debug.println(Level.WARNING, "SeekableByteChannelForWriting: truncate: WIP"); -Debug.println(Level.FINE, "SeekableByteChannelForWriting: truncate: " + size + ", " + written + ", " + out.getClass().getName()); - // TODO implement correctly - - if (written > size) { - written = size; - } - - return this; - } - - @Override - public int write(ByteBuffer src) throws IOException { - int n = wbc.write(src); -Debug.println(Level.FINE, "SeekableByteChannelForWriting: write: " + n + "/" + written + " -> " + (written + n)); - written += n; - return n; - } - - @Override - public long size() throws IOException { -Debug.println(Level.FINE, "SeekableByteChannelForWriting: size: " + written); - return written; - } - - @Override - public void close() throws IOException { -Debug.println(Level.FINE, "SeekableByteChannelForWriting: close"); - wbc.close(); - } - } - - /** - * @see java.nio.file.Files#newByteChannel(Path, Set, java.nio.file.attribute.FileAttribute...) - */ - abstract class SeekableByteChannelForReading implements SeekableByteChannel { - private long read = 0; - private ReadableByteChannel rbc; - private long size; - InputStream in; - - public SeekableByteChannelForReading(InputStream in) throws IOException { - this.in = in; - this.rbc = Channels.newChannel(in); - this.size = getSize(); - } - - /** */ - protected abstract long getSize() throws IOException; - - @Override - public boolean isOpen() { - return rbc.isOpen(); - } - - @Override - public long position() throws IOException { - if (in instanceof Seekable) { - // see com.github.fge.filesystem.driver.DoubleCachedFileSystemDriver#downloadEntry - read = ((Seekable) in).position(); -Debug.println(Level.FINE, "SeekableByteChannelForReading: get position by vavi.io.Seekable: " + read); - } else if (rbc instanceof SeekableByteChannel) { - read = ((SeekableByteChannel) rbc).position(); -Debug.println(Level.FINE, "SeekableByteChannelForReading: get position by java.nio.channels.SeekableByteChannel: " + read); - } else { -Debug.println(Level.WARNING, "SeekableByteChannelForReading: get position: non seekable input: " + read + ", " + in.getClass().getName()); - } - return read; - } - - @Override - public SeekableByteChannel position(long pos) throws IOException { - if (in instanceof Seekable) { - // see com.github.fge.filesystem.driver.DoubleCachedFileSystemDriver#downloadEntry -Debug.println(Level.FINE, "SeekableByteChannelForReading: set position by vavi.io.Seekable: " + pos); - ((Seekable) in).position(pos); - } else if (rbc instanceof SeekableByteChannel) { -Debug.println(Level.FINE, "SeekableByteChannelForReading: set position by java.nio.channels.SeekableByteChannel: " + pos); - ((SeekableByteChannel) rbc).position(pos); - } else { -Debug.println(Level.WARNING, "SeekableByteChannelForReading: set position: non seekable input: " + pos + ", " + in.getClass().getName()); - } - - read = pos; - return this; - } - - @Override - public int read(ByteBuffer dst) throws IOException { - int n = rbc.read(dst); - if (n > 0) { -Debug.println(Level.FINER, "SeekableByteChannelForReading: read: " + n + "/" + read + " -> " + (read + n)); - read += n; - } - return n; - } - - @Override - public SeekableByteChannel truncate(long size) throws IOException { - throw new NonWritableChannelException(); - } - - @Override - public int write(ByteBuffer src) throws IOException { - throw new NonWritableChannelException(); - } - - @Override - public long size() throws IOException { - return size; - } - - @Override - public void close() throws IOException { - rbc.close(); - } - } - - /** - * TODO - *
    - *
  • StandardOpenOption.WRITE - *
  • StandardOpenOption.CREATE_NEW - *
  • StandardOpenOption.CREATE - *
  • StandardOpenOption.APPEND - *
- */ - static boolean isWriting(Set options) { - return options.contains(StandardOpenOption.WRITE) || - options.contains(StandardOpenOption.CREATE_NEW) || - options.contains(StandardOpenOption.CREATE) || - options.contains(StandardOpenOption.APPEND); - } - - /** - * @see java.nio.file.Files#newInputStream(Path, OpenOption...) - */ - abstract class InputStreamForDownloading extends FilterInputStream { - private AtomicBoolean closed = new AtomicBoolean(); - - private boolean closeOnCloseInternal = true; - - public InputStreamForDownloading(InputStream is) { - super(is); - } - - public InputStreamForDownloading(InputStream is, boolean closeOnCloseInternal) { - super(is); - this.closeOnCloseInternal = closeOnCloseInternal; - } - - @Override - public void close() throws IOException { - if (closed.getAndSet(true)) { -Debug.printf(Level.FINE, "Skip double close of stream %s", this); - return; - } - - if (closeOnCloseInternal) { - in.close(); - } - - onClosed(); - } - - protected abstract void onClosed() throws IOException; - } - - /** - * TODO limited under 2GB - * - * @see java.nio.file.Files#newOutputStream(Path, OpenOption...) - */ - abstract class OutputStreamForUploading extends FilterOutputStream { - private AtomicBoolean closed = new AtomicBoolean(); - - private boolean closeOnCloseInternal = true; - - public OutputStreamForUploading() { - super(new ByteArrayOutputStream()); - } - - public OutputStreamForUploading(OutputStream os) { - super(os); - } - - public OutputStreamForUploading(OutputStream os, boolean closeOnCloseInternal) { - super(os); - this.closeOnCloseInternal = closeOnCloseInternal; - } - - @Override - public void close() throws IOException { - if (closed.getAndSet(true)) { -Debug.printf(Level.FINE, "Skip double close of stream %s", this); - return; - } - - if (closeOnCloseInternal) { - out.close(); - } - - onClosed(); - } - - protected InputStream getInputStream() { - if (ByteArrayOutputStream.class.isInstance(out)) { - // TODO engine - return new ByteArrayInputStream(ByteArrayOutputStream.class.cast(out).toByteArray()); - } else { - throw new IllegalStateException("out is not ByteArrayOutputStream: " + out.getClass().getName()); - } - } - - protected abstract void onClosed() throws IOException; - } - - /** - * @see java.nio.file.Files#newOutputStream(Path, OpenOption...) - */ - abstract class StealingOutputStreamForUploading extends OutputStreamForUploading { - // TODO pool - private ExecutorService executor = Executors.newSingleThreadExecutor(); - private Future future; - private CountDownLatch latch1 = new CountDownLatch(1); - private CountDownLatch latch2 = new CountDownLatch(1); - private CountDownLatch latch3 = new CountDownLatch(1); - - /** */ - public StealingOutputStreamForUploading() { - super(null, false); - } - - /** */ - protected void setOutputStream(OutputStream os) { - out = os; - latch1.countDown(); - try { latch2.await(); } catch (InterruptedException e) { throw new IllegalStateException(e); } - } - - /** must call {@link #setOutputStream(OutputStream)} */ - protected abstract T upload() throws IOException; - - /** set #out */ - private void init() { - future = executor.submit(() -> { - try { - T newEntry = upload(); - latch3.countDown(); - return newEntry; - } catch (IOException e) { - throw new IllegalStateException(e); - } - }); - try { latch1.await(); } catch (InterruptedException e) { throw new IllegalStateException(e); } - } - - @Override - public void write(int b) throws IOException { - try { - super.write(b); - } catch (NullPointerException e) { - init(); - super.write(b); - } - } - - /** */ - protected abstract void onClosed(T newEntry); - - @Override - protected void onClosed() throws IOException { - try { - latch2.countDown(); - latch3.await(); - out.close(); - - onClosed(future.get()); - - executor.shutdown(); - } catch (InterruptedException | ExecutionException e) { - throw new IllegalStateException(e); - } - } - } - - /** */ - int BUFFER_SIZE = 4 * 1024 * 1024; - - /** - * @see #BUFFER_SIZE - */ - static void transfer(InputStream is, OutputStream os) throws IOException { - WritableByteChannel wbc = Channels.newChannel(os); - ReadableByteChannel rbc = Channels.newChannel(is); - ByteBuffer buffer = ByteBuffer.allocateDirect(BUFFER_SIZE); - while (rbc.read(buffer) != -1 || buffer.position() > 0) { - buffer.flip(); - wbc.write(buffer); - buffer.compact(); - } - } - - /** - * @see #BUFFER_SIZE - */ - static void transfer(SeekableByteChannel in, SeekableByteChannel out) throws IOException { - ByteBuffer buffer = ByteBuffer.allocateDirect(BUFFER_SIZE); - while (in.read(buffer) != -1 || buffer.position() > 0) { - buffer.flip(); - out.write(buffer); - buffer.compact(); - } - } -} - -/* */ diff --git a/src/main/resources/META-INF/services/vavi.net.fuse.FuseProvider b/src/main/resources/META-INF/services/vavi.net.fuse.FuseProvider new file mode 100644 index 0000000..3873a6f --- /dev/null +++ b/src/main/resources/META-INF/services/vavi.net.fuse.FuseProvider @@ -0,0 +1,3 @@ +vavi.net.fuse.fusejna.FuseJnaFuseProvider +vavi.net.fuse.javafs.JavaFSFuseProvider +vavi.net.fuse.jnrfuse.JnrFuseFuseProvider \ No newline at end of file diff --git a/src/test/java/Main2.java b/src/test/java/Main2.java new file mode 100644 index 0000000..d185621 --- /dev/null +++ b/src/test/java/Main2.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2017 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +import java.nio.file.FileSystem; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import com.google.common.jimfs.Configuration; +import com.google.common.jimfs.Jimfs; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import vavi.net.fuse.Base; +import vavi.net.fuse.Fuse; +import vavi.util.Debug; + + +/** + * Test2. (jimfs, fuse) + *

+ * + * @author Naohide Sano (umjammer) + * @version 0.00 2017/03/19 umjammer initial version
+ */ +public class Main2 { + + static { + System.setProperty("vavi.util.logging.VaviFormatter.extraClassMethod", + "co\\.paralleluniverse\\.fuse\\.LoggedFuseFilesystem#log"); + } + + FileSystem fs; + String mountPoint; + Map options; + + @BeforeEach + public void before() throws Exception { + + mountPoint = System.getenv("FUSE_MOUNT_POINT"); +Debug.println("mountPoint: " + mountPoint); + + fs = Jimfs.newFileSystem(Configuration.unix()); + + options = new HashMap<>(); + options.put("fsname", "jimfs_fs" + "@" + System.currentTimeMillis()); + options.put("noappledouble", null); + //options.put("noapplexattr", null); + options.put(vavi.net.fuse.javafs.JavaFSFuse.ENV_DEBUG, false); + options.put(vavi.net.fuse.javafs.JavaFSFuse.ENV_READ_ONLY, false); + } + + @ParameterizedTest + @EnabledIfEnvironmentVariable(named = "FUSE_MOUNT_POINT", matches = ".+") + @ValueSource(strings = { + "vavi.net.fuse.javafs.JavaFSFuseProvider", + "vavi.net.fuse.jnrfuse.JnrFuseFuseProvider", + "vavi.net.fuse.fusejna.FuseJnaFuseProvider", + }) + public void test01(String providerClassName) throws Exception { + System.setProperty("vavi.net.fuse.FuseProvider.class", providerClassName); +System.err.println("--------------------------- " + providerClassName + " ---------------------------"); + + Base.testLargeFile(fs, mountPoint, options); + + fs.close(); + } + + /** + * @param args none + */ + public static void main(String[] args) throws Exception { + Main2 app = new Main2(); + app.before(); + + Fuse.getFuse().mount(app.fs, app.mountPoint, app.options); + } +} + +/* */ diff --git a/src/test/java/Main4.java b/src/test/java/Main4.java new file mode 100644 index 0000000..6132b32 --- /dev/null +++ b/src/test/java/Main4.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2017 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +import java.nio.file.FileSystem; +import java.util.HashMap; +import java.util.Map; + +import com.google.common.jimfs.Configuration; +import com.google.common.jimfs.Jimfs; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import vavi.net.fuse.Base; +import vavi.net.fuse.Fuse; +import vavi.util.Debug; + + +/** + * Test4. (jimfs, fuse) + *

+ * upload + *

    + *
  • create + *
  • write + *
  • chmod + *
  • chmod + *
  • chmod + *
  • flush + *
  • lock + *
  • release + *
+ * download + *
    + *
  • open + *
  • read + *
  • flush + *
  • lock + *
  • release + *
+ * + * TODO read is not called because of cache??? + * + * @author Naohide Sano (umjammer) + * @version 0.00 2017/03/19 umjammer initial version
+ */ +public class Main4 { + + static { + System.setProperty("vavi.util.logging.VaviFormatter.extraClassMethod", + "co\\.paralleluniverse\\.fuse\\.LoggedFuseFilesystem#log"); + } + + FileSystem fs; + String mountPoint; + Map options; + + @BeforeEach + public void before() throws Exception { + + mountPoint = System.getenv("FUSE_MOUNT_POINT"); +Debug.println("mountPoint: " + mountPoint); + + fs = Jimfs.newFileSystem(Configuration.unix()); + + options = new HashMap<>(); + options.put("fsname", "jimfs_fs" + "@" + System.currentTimeMillis()); + options.put("noappledouble", null); + //options.put("noapplexattr", null); + options.put(vavi.net.fuse.javafs.JavaFSFuse.ENV_DEBUG, false); + options.put(vavi.net.fuse.javafs.JavaFSFuse.ENV_READ_ONLY, false); + } + + @ParameterizedTest + @EnabledIfEnvironmentVariable(named = "FUSE_MOUNT_POINT", matches = ".+") + @ValueSource(strings = { + "vavi.net.fuse.javafs.JavaFSFuseProvider", + "vavi.net.fuse.jnrfuse.JnrFuseFuseProvider", + "vavi.net.fuse.fusejna.FuseJnaFuseProvider", + }) + public void test01(String providerClassName) throws Exception { + System.setProperty("vavi.net.fuse.FuseProvider.class", providerClassName); +System.err.println("--------------------------- " + providerClassName + " ---------------------------"); + + Base.testFuse(fs, mountPoint, options); + + fs.close(); + + Thread.sleep(333); + } + + /** + * @param args none + */ + public static void main(String[] args) throws Exception { + Main4 app = new Main4(); + app.before(); + + System.setProperty("vavi.net.fuse.FuseProvider.class", "vavi.net.fuse.javafs.JavaFSFuseProvider"); +// System.setProperty("vavi.net.fuse.FuseProvider.class", "vavi.net.fuse.fusejna.FuseJnaFuseProvider"); +// System.setProperty("vavi.net.fuse.FuseProvider.class", "vavi.net.fuse.jnrfuse.JnrFuseFuseProvider"); + + app.options.put("allow_other", null); + + Fuse.getFuse().mount(app.fs, app.mountPoint, app.options); + } +} + +/* */ diff --git a/src/test/java/Test01.java b/src/test/java/Test01.java deleted file mode 100644 index 0d59b5a..0000000 --- a/src/test/java/Test01.java +++ /dev/null @@ -1,330 +0,0 @@ -/* - * Copyright (c) 2019 by Naohide Sano, All rights reserved. - * - * Programmed by Naohide Sano - */ - -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.nio.channels.SeekableByteChannel; -import java.nio.file.FileSystem; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardOpenOption; -import java.text.Normalizer; -import java.text.Normalizer.Form; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.IntStream; - -import com.google.common.jimfs.Configuration; -import com.google.common.jimfs.Jimfs; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledOnOs; -import org.junit.jupiter.api.condition.OS; -import vavi.io.Seekable; -import vavi.nio.file.Base; -import vavi.nio.file.Util; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - - -/** - * Test01. - * - * @author Naohide Sano (umjammer) - * @version 0.00 2019/07/11 umjammer initial version
- */ -class Test01 { - - static Path tmp = Paths.get("tmp"); - - @BeforeAll - static void setup() throws Exception { - if (!Files.exists(tmp)) { - Files.createDirectory(tmp); - } - } - - @Test - void test() { - Path p = Paths.get("/aaa/bbb/ccc/ddd"); - for (int i = 0; i < p.getNameCount(); i++) { - System.err.println("name : " + p.getName(i)); - System.err.println("sub path : " + p.subpath(0, i + 1)); - System.err.println("name parent : " + p.getName(i).getParent()); - System.err.println("sub parent : " + p.subpath(0, i + 1).getParent()); - } - - assertNull(p.getName(0).getParent()); - assertNull(p.getName(1).getParent()); - assertNull(p.getName(3).getParent()); - - assertNull(p.subpath(0, 1).getParent()); - assertEquals("aaa", p.subpath(0, 2).getParent().toString()); - } - - @Test - @EnabledOnOs(OS.MAC) - void test01() throws Exception { - Path path = Paths.get("src/main/java/vavi/nio/file/Util.java"); - assertEquals("Util.java", Util.toFilenameString(path)); - - String test1 = "src/test/resources/ใƒ‘ใƒณใƒ€.txt"; - String test2 = Normalizer.normalize(test1, Form.NFD); - assertNotEquals(test1, test2); - String test3 = Normalizer.normalize(test2, Form.NFC); - assertEquals(test1, test3); - - Path path1 = Paths.get(test1); - assertTrue(Files.exists(path1)); - - Path path2 = Paths.get(test2); // converted by file system - assertTrue(Files.exists(path2)); - } - - static final String nfd1 = "ใ‹ใ‚™ใใ‚™ใใ‚™ใ‘ใ‚™ใ“ใ‚™ใ•ใ‚™ใ—ใ‚™ใ™ใ‚™ใ›ใ‚™ใใ‚™ใŸใ‚™ใกใ‚™ใคใ‚™ใฆใ‚™ใจใ‚™ใฏใ‚™ใฒใ‚™ใตใ‚™ใธใ‚™ใปใ‚™ใ‚ใ‚™ใƒฝใ‚™ใฏใ‚šใฒใ‚šใตใ‚šใธใ‚šใปใ‚šใ†ใ‚™"; - static final String nfd2 = "ใ‚ซใ‚™ใ‚ญใ‚™ใ‚ฏใ‚™ใ‚ฑใ‚™ใ‚ณใ‚™ใ‚ตใ‚™ใ‚ทใ‚™ใ‚นใ‚™ใ‚ปใ‚™ใ‚ฝใ‚™ใ‚ฟใ‚™ใƒใ‚™ใƒ„ใ‚™ใƒ†ใ‚™ใƒˆใ‚™ใƒใ‚™ใƒ’ใ‚™ใƒ•ใ‚™ใƒ˜ใ‚™ใƒ›ใ‚™ใ‚ใ‚™ใƒฝใ‚™ใƒใ‚šใƒ’ใ‚šใƒ•ใ‚šใƒ˜ใ‚šใƒ›ใ‚šใ‚ฆใ‚™"; - static final String nfc1 = "ใŒใŽใใ’ใ”ใ–ใ˜ใšใœใžใ ใขใฅใงใฉใฐใณใถในใผใ‚žใƒพใฑใดใทใบใฝใ‚”"; - static final String nfc2 = "ใ‚ฌใ‚ฎใ‚ฐใ‚ฒใ‚ดใ‚ถใ‚ธใ‚บใ‚ผใ‚พใƒ€ใƒ‚ใƒ…ใƒ‡ใƒ‰ใƒใƒ“ใƒ–ใƒ™ใƒœใ‚žใƒพใƒ‘ใƒ”ใƒ—ใƒšใƒใƒด"; - - @Test - void test11() throws Exception { - String actual = Util.toNormalizedString(nfd1); - assertEquals(nfc1, actual); - actual = Util.toNormalizedString(nfd2); - assertEquals(nfc2, actual); - } - - @Test - void test02() throws Exception { - Base.testAll(Jimfs.newFileSystem(Configuration.unix())); - } - - @Test - void test04() throws Exception { - Base.testLargeFile(Jimfs.newFileSystem(Configuration.unix()), null); - } - - @Test - void test05() throws Exception { - Base.testMoveFolder(Jimfs.newFileSystem(Configuration.unix())); - } - - @Test - void test03() throws Exception { - Path src = Paths.get("src/test/resources/Hello.java"); - Path dst = Paths.get("tmp/Hello.java"); - if (Files.exists(dst)) { - Files.delete(dst); - } - InputStream is = Files.newInputStream(src); - OutputStream os = Files.newOutputStream(dst); - Util.transfer(is, os); - assertEquals(Files.size(src), Files.size(dst)); - Files.delete(dst); - assertFalse(Files.exists(dst)); - } - - @Test - void test06() throws Exception { - FileSystem fs = Jimfs.newFileSystem(Configuration.unix()); - Path dir = fs.getPath("test06"); - Files.createDirectory(dir); - Path src = Paths.get(getClass().getResource("Hello.java").toURI()); - Files.copy(src, dir.resolve(src.getFileName().toString())); - Base.removeTree(dir, false); - assertTrue(Files.exists(dir)); - Files.copy(src, dir.resolve(src.getFileName().toString())); - Base.removeTree(dir, true); - assertFalse(Files.exists(dir)); - fs.close(); - } - - static class SeekableByteArrayInputStream extends InputStream implements Seekable { - byte[] buf; - int pos; - - SeekableByteArrayInputStream(byte[] buf) { - this.buf = buf; - this.pos = 0; - } - - @Override - public int available() { - return buf.length - pos; - } - - @Override - public int read() { - if (pos >= buf.length) { - return -1; - } else { - return buf[pos++] & 0xff; - } - } - - @Override - public void position(long l) { - if (l < 0 || l >= buf.length) { - throw new IndexOutOfBoundsException(String.valueOf(l)); - } - pos = (int) l; - } - - @Override - public long position() { - return pos; - } - } - - static class SeekableByteArrayOutputStream extends OutputStream implements Seekable { - List buf; - int capacity; - int pos; - - SeekableByteArrayOutputStream(int capacity) { - this.capacity = capacity; - this.buf = new ArrayList<>(capacity); - this.pos = 0; - } - - @Override - public void write(int b) { - for (int i = buf.size(); i <= pos; i++) { - buf.add(i, (byte) 0); - } - buf.set(pos, (byte) b); - } - - @Override - public void position(long l) { - if (l < 0 || l >= capacity) { - throw new IndexOutOfBoundsException(String.valueOf(l)); - } - pos = (int) l; - } - - @Override - public long position() { - return pos; - } - - /** */ - public byte[] toByteArray() { - byte[] a = new byte[buf.size()]; - IntStream.range(0, buf.size()).forEach(i -> a[i] = buf.get(i)); - return a; - } - - /** */ - public String toString() { - return new String(toByteArray()); - } - } - - @Test - void test07() throws Exception { - byte[] b = new byte[256]; - for (int i = 0; i < 256; i++) { - b[i] = (byte) i; - } - InputStream is = new SeekableByteArrayInputStream(b); - SeekableByteChannel sbc = new Util.SeekableByteChannelForReading(is) { - @Override protected long getSize() { - return b.length; - } - }; - sbc.position(100); - byte[] rb = new byte[1]; - sbc.read(ByteBuffer.wrap(rb)); - assertEquals(100, rb[0]); - } - - @Test - void test07_1() throws Exception { - byte[] b = new byte[256]; - for (int i = 0; i < 256; i++) { - b[i] = (byte) i; - } - Path tmp = Test01.tmp.resolve("test07_1.dat"); - Files.write(tmp, b, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW); - FileInputStream fis = new FileInputStream(tmp.toFile()); - SeekableByteChannel sbc = new Util.SeekableByteChannelForReading(fis) { - @Override protected long getSize() { - return b.length; - } - }; - sbc.position(100); - byte[] rb = new byte[1]; - sbc.read(ByteBuffer.wrap(rb)); - assertEquals(100, rb[0]); - Files.delete(tmp); - } - - @Test - void test08() throws Exception { - int capacity = 256; - SeekableByteArrayOutputStream sbaos = new SeekableByteArrayOutputStream(capacity); - SeekableByteChannel sbc = new Util.SeekableByteChannelForWriting(sbaos) { - @Override - protected long getLeftOver() throws IOException { - return capacity - position(); - } - }; - - sbc.position(99); - byte[] rb = new byte[] { 100 }; - sbc.write(ByteBuffer.wrap(rb)); - - assertEquals(100, sbaos.toByteArray().length); - assertEquals(100, sbaos.toByteArray()[99]); - - sbc.position(49); - rb = new byte[] { 50 }; - sbc.write(ByteBuffer.wrap(rb)); - - assertEquals(100, sbaos.toByteArray().length); - assertEquals(50, sbaos.toByteArray()[49]); - } - - @Test - void test08_1() throws Exception { - Path tmp = Test01.tmp.resolve("test08_1.dat"); - FileOutputStream fos = new FileOutputStream(tmp.toFile()); - SeekableByteChannel sbc = new Util.SeekableByteChannelForWriting(fos) { - @Override - protected long getLeftOver() throws IOException { - return 256 - position(); - } - }; - - sbc.position(99); - byte[] rb = new byte[] { 100 }; - sbc.write(ByteBuffer.wrap(rb)); - - sbc.position(49); - rb = new byte[] { 50 }; - sbc.write(ByteBuffer.wrap(rb)); - - byte[] b = Files.readAllBytes(tmp); - - assertEquals(100, b.length); - assertEquals(100, b[99]); - assertEquals(50, b[49]); - - Files.delete(tmp); - } -} - -/* */ diff --git a/src/test/java/vavi/net/fuse/Base.java b/src/test/java/vavi/net/fuse/Base.java new file mode 100644 index 0000000..d32ef4e --- /dev/null +++ b/src/test/java/vavi/net/fuse/Base.java @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2020 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package vavi.net.fuse; + +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Map; +import java.util.Random; + +import vavi.util.Debug; +import vavix.util.Checksum; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + + +/** + * Base. + * + * @author Naohide Sano (umjammer) + * @version 0.00 2020/06/05 umjammer initial version
+ */ +public abstract class Base { + + /** */ + private static int exec(String... commandLine) throws Exception { + ProcessBuilder processBuilder = new ProcessBuilder(commandLine); + processBuilder.inheritIO(); + System.out.println("$ " + String.join(" ", commandLine)); + Process process = processBuilder.start(); + process.waitFor(); + return process.exitValue(); + } + + /** */ + public static void testFuse(FileSystem fs, String mountPoint, Map options) throws Exception { + Path local = null; + Path localTmp = null; + try (Fuse fuse = Fuse.getFuse()) { + fuse.mount(fs, mountPoint, options); + + Path localTmpDir = Paths.get("tmp"); + if (!Files.exists(localTmpDir)) { +Debug.println("[_mkdir] " + localTmpDir); + Files.createDirectories(localTmpDir); + } + + // local -> remote + local = Files.createTempFile(localTmpDir, "vavifuse-1-", ".tmp"); + byte[] bytes = new byte[5 * 1024 * 1024 + 12345]; + Random random = new Random(System.currentTimeMillis()); + random.nextBytes(bytes); + Files.write(local, bytes); + + Path remoteDir = Paths.get(mountPoint, "VAVI_FUSE_TEST4"); + if (!Files.exists(remoteDir)) { +Debug.println("[mkdir] " + remoteDir); + assertEquals(0, exec("/bin/mkdir", remoteDir.toString())); + } +Debug.println("[_rm] " + remoteDir + "/*"); + Files.list(remoteDir).forEach(p -> { +Debug.println("{_rm} " + p); + try { + assertEquals(0, exec("/bin/rm", p.toString())); + } catch (Exception e) { + throw new IllegalStateException(e); + } + }); + Path remote = remoteDir.resolve(local.getFileName()); + if (Files.exists(remote)) { +Debug.println("[rm] " + remote); + assertEquals(0, exec("/bin/rm", remote.toString())); + } + +Debug.println("[cp] " + local + " " + remote); + // TODO cp returns 1 but copying is succeed + assertTrue(List.of(0, 1).contains(exec("/bin/cp", local.toString(), remote.toString()))); + assertEquals(0, exec("/bin/ls", "-l", remote.toString())); + assertEquals(0, exec("/bin/ls", "-l", local.toString())); + assertTrue(Files.exists(remote)); + assertEquals(Files.size(local), Files.size(remote)); + assertEquals(Checksum.getChecksum(local), Checksum.getChecksum(remote)); + + // remote -> local + localTmp = Files.createTempFile(localTmpDir, "vavifuse-2-", ".tmp"); + if (Files.exists(localTmp)) { +Debug.println("[_rm] " + localTmp); + assertEquals(0, exec("/bin/rm", localTmp.toString())); + } + +Debug.println("[cp] " + remote + " " + localTmp); + assertEquals(0, exec("/bin/cp", remote.toString(), localTmp.toString())); + assertEquals(0, exec("/bin/ls", "-l", remoteDir.toString())); + assertEquals(0, exec("/bin/ls", "-l", localTmp.toString())); + assertTrue(Files.exists(localTmp)); + assertEquals(Files.size(remote), Files.size(localTmp)); + assertEquals(Checksum.getChecksum(remote), Checksum.getChecksum(localTmp)); + + // clean up +Debug.println("[rm] " + remote); + assertEquals(0, exec("/bin/rm", remote.toString())); +Debug.println("[_rm] " + remoteDir + "/*"); + Files.list(remoteDir).forEach(p -> { + try { +Debug.println("{_rm} " + p); + assertEquals(0, exec("/bin/rm", p.toString())); + } catch (Exception e) { + throw new IllegalStateException(e); + } + }); +Debug.println("[rmdir] " + remoteDir); + assertEquals(0, exec("/bin/rmdir", remoteDir.toString())); + assertFalse(Files.exists(remote)); + assertFalse(Files.exists(remoteDir)); + } finally { + if (localTmp != null && Files.exists(localTmp)) { + try { +Debug.println("[_rm] " + localTmp); + assertEquals(0, exec("/bin/rm", localTmp.toString())); + } catch (Exception e) { + e.printStackTrace(); + } + } + if (local != null && Files.exists(local)) { +Debug.println("[_rm] " + local); + assertEquals(0, exec("/bin/rm", local.toString())); + } + } + } + + /** */ + public static void testLargeFile(FileSystem fs, String mountPoint, Map options) throws Exception { + try (Fuse fuse = Fuse.getFuse()) { + fuse.mount(fs, mountPoint, options); + + Path tmpDir = Paths.get("tmp"); + if (!Files.exists(tmpDir)) { +System.out.println("[mkdir -p] " + tmpDir); + Files.createDirectories(tmpDir); + } + Path source = Files.createTempFile(tmpDir, "vavifuse-1-", ".tmp"); + byte[] bytes = new byte[50 * 1024 * 1024 + 12345]; + Random random = new Random(System.currentTimeMillis()); + random.nextBytes(bytes); +System.out.println("[create random file] " + bytes.length); + Files.write(source, bytes); + + Path dir = Paths.get(mountPoint,"VAVIFUSE_FS_TEST_L"); + Path target = dir.resolve(source.getFileName()); + if (Files.exists(dir)) { +System.out.println("[rm -rf] " + dir); + assertEquals(0, exec("/bin/rm", "-rf", dir.toString())); + } + +System.out.println("[mkdir] " + dir); + assertEquals(0, exec("/bin/mkdir", dir.toString())); + +System.out.println("[cp(1)] " + source + " " + target); + assertEquals(0, exec("/bin/cp", source.toString(), target.toString())); + assertTrue(Files.exists(target)); + assertEquals(Files.size(source), Files.size(target)); + assertEquals(Checksum.getChecksum(source), Checksum.getChecksum(target)); + + Path source2 = source.getParent().resolve(source.getFileName().toString().replace("vavifuse-1-", "vavifuse-2-")); + +System.out.println("[cp(2)] " + target + " " + source2); + assertEquals(0, exec("/bin/cp", target.toString(), source2.toString())); + assertTrue(Files.exists(target)); + assertEquals(Files.size(source2), Files.size(target)); + assertEquals(Checksum.getChecksum(source2), Checksum.getChecksum(target)); + + Path target2 = target.getParent().resolve(target.getFileName().toString().replace("vavifuse-1-", "vavifuse-2-")); + +System.out.println("[cp(3)] " + target + " " + target2); + assertEquals(0, exec("/bin/cp", target.toString(), target2.toString())); + assertTrue(Files.exists(target)); + assertEquals(Files.size(target), Files.size(target2)); + assertEquals(Checksum.getChecksum(target), Checksum.getChecksum(target2)); + +System.out.println("[rm] " + source2); + assertEquals(0, exec("/bin/rm", source2.toString())); +System.out.println("[rm] " + target); + assertEquals(0, exec("/bin/rm", target.toString())); +System.out.println("[rm] " + target2); + assertEquals(0, exec("/bin/rm", target2.toString())); + +System.out.println("[rm -rf] " + dir); + assertEquals(0, exec("/bin/rm", "-rf", dir.toString())); + + if (Files.exists(source)) { +System.out.println("[rm] " + source); + Files.delete(source); + } + } + } +} + +/* */ diff --git a/src/test/java/vavi/nio/file/Base.java b/src/test/java/vavi/nio/file/Base.java deleted file mode 100644 index e4bd083..0000000 --- a/src/test/java/vavi/nio/file/Base.java +++ /dev/null @@ -1,379 +0,0 @@ -/* - * Copyright (c) 2016 by Naohide Sano, All rights reserved. - * - * Programmed by Naohide Sano - */ - -package vavi.nio.file; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.UncheckedIOException; -import java.nio.channels.SeekableByteChannel; -import java.nio.file.FileSystem; -import java.nio.file.FileVisitResult; -import java.nio.file.Files; -import java.nio.file.OpenOption; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.SimpleFileVisitor; -import java.nio.file.StandardOpenOption; -import java.nio.file.attribute.BasicFileAttributes; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; -import java.util.Random; - -import vavix.util.Checksum; - -import static com.rainerhahnekamp.sneakythrow.Sneaky.sneaked; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - - -/** - * Base. - *

- * if you want to write sub class test of this base test, - * you should include this dependency in your pom.xml - *

- * <dependency>
- *   <groupId>com.rainerhahnekamp</groupId>
- *   <artifactId>sneakythrow</artifactId>
- *   <version>1.2.0</version>
- *   <scope>test</scope>
- * </dependency>
- * 
- * - * @author Naohide Sano (umjammer) - * @version 0.00 2016/03/xx umjammer initial version
- */ -public interface Base { - - /** - * prepare "src/test/resources/Hello.java" - */ - static void testAll(FileSystem fileSystem) throws Exception { - - Path src = Paths.get("src/test/resources" , "Hello.java"); - Path dir = fileSystem.getPath("/").resolve("VAVIFUSE_FS_TEST"); - -System.out.println("$ [list]: " + dir.getParent()); -Files.list(dir.getParent()).forEach(System.out::println); - long count = Files.list(dir.getParent()).count(); - - if (Files.exists(dir)) { - removeTree(dir); - count--; - } - -System.out.println("$ [createDirectory]: " + dir); - dir = Files.createDirectory(dir); - count++; -Thread.sleep(300); -System.out.println("$ [list]: " + dir.getParent()); -Files.list(dir.getParent()).forEach(System.out::println); - assertEquals(count, Files.list(dir.getParent()).count()); - - Path src2 = dir.resolve(src.getFileName().toString()); -System.out.println("$ [copy (upload)]: " + src + " " + src2); - src2 = Files.copy(src, src2); -Thread.sleep(300); -System.out.println("$ [list]: " + dir); -Files.list(dir).forEach(System.out::println); - assertEquals(1, Files.list(dir).count()); - assertEquals(Files.size(src), Files.size(src2)); - - Path src3 = dir.resolve(src2.getFileName().toString() + "_ใ‚ณใƒ”ใƒผ"); -System.out.println("$ [copy (internal)]: " + src2 + " " + src3); - src3 = Files.copy(src2, src3); // SPEC target should be file -Thread.sleep(300); -System.out.println("$ [list]: " + dir); -Files.list(dir).forEach(System.out::println); - assertEquals(2, Files.list(dir).count()); - - Path src3_2 = dir.resolve(src2.getFileName().toString() + "_ใ‚ณใƒ”ใƒผ2"); -System.out.println("$ [copy (internal)]: " + src2 + " " + src3_2); - src3_2 = Files.copy(src2, src3_2); -Thread.sleep(300); -System.out.println("$ [list]: " + dir); -Files.list(dir).forEach(System.out::println); - assertEquals(3, Files.list(dir).count()); - - Path src4 = dir.resolve(src2.getFileName().toString() + "_R"); -System.out.println("$ [rename (internal)]:" + src3 + " " + src4); - src4 = Files.move(src3, src4); -Thread.sleep(300); -System.out.println("$ [list]: " + dir); -Files.list(dir).forEach(System.out::println); - assertEquals(3, Files.list(dir).count()); - -System.out.println("$ [createDirectory]: " + dir.resolve("SUB_DIR")); - Path dir2 = Files.createDirectory(dir.resolve("SUB_DIR")); -Thread.sleep(300); -System.out.println("$ [list]: " + dir); -Files.list(dir).forEach(System.out::println); - assertEquals(4, Files.list(dir).count()); - - Path src5 = dir2.resolve(src4.getFileName()); -System.out.println("$ [move (internal)]:" + src4 + " " + src5); - src5 = Files.move(src4, src5); // SPEC: move returns target -Thread.sleep(1000); -System.out.println("$ [list]: " + dir2); -Files.list(dir2).forEach(System.out::println); - assertEquals(1, Files.list(dir2).count()); - - Path src6 = dir2.resolve("World.java"); -System.out.println("$ [move (internal)]:" + src3_2 + " " + src6); - src6 = Files.move(src3_2, src6); -Thread.sleep(1000); -System.out.println("$ [list]: " + dir2); -Files.list(dir2).forEach(System.out::println); - assertEquals(2, Files.list(dir2).count()); - - Path tmp = Paths.get("tmp"); - if (!Files.exists(tmp)) { -System.out.println("$ [*mkdir]: " + tmp); - Files.createDirectory(tmp); - } - - Path dst2 = Paths.get("tmp", "Hello.java"); - if (Files.exists(dst2)) { -System.out.println("$ [*rm]: " + dst2); - Files.delete(dst2); - } -System.out.println("$ [copy (download)]: " + src6 + " " + dst2); - dst2 = Files.copy(src6, dst2); -Thread.sleep(300); -System.out.println("$ [list]: " + dst2.getParent()); -Files.list(dst2.getParent()).forEach(System.out::println); - assertTrue(Files.exists(dst2)); - assertEquals(Files.size(dst2), Files.size(src6)); - -System.out.println("$ [delete file]: " + src2); - Files.delete(src2); -Thread.sleep(300); -System.out.println("$ [list]: " + dir); -Files.list(dir).forEach(System.out::println); - assertEquals(1, Files.list(dir).count()); - -System.out.println("$ [delete file]:" + src5); - Files.delete(src5); -Thread.sleep(300); -System.out.println("$ [list]: " + dir2); -Files.list(dir2).forEach(System.out::println); - assertEquals(1, Files.list(dir2).count()); - -System.out.println("$ [delete file]:" + src6); - Files.delete(src6); -Thread.sleep(300); -System.out.println("$ [list]: " + dir2); -Files.list(dir2).forEach(System.out::println); - assertEquals(0, Files.list(dir2).count()); - -System.out.println("$ [delete directory]: " + dir2); - Files.delete(dir2); -Thread.sleep(300); -System.out.println("$ [list]: " + dir); -Files.list(dir).forEach(System.out::println); - assertEquals(0, Files.list(dir).count()); - -System.out.println("$ [delete directory]: " + dir); - Files.delete(dir); -Thread.sleep(600); -System.out.println("$ [list]: " + dir.getParent()); -Files.list(dir.getParent()).forEach(System.out::println); - assertEquals(count - 1, Files.list(dir.getParent()).count()); - -System.out.println("$ [*rm]: " + dst2); - Files.delete(dst2); - assertFalse(Files.exists(dst2)); - } - - /** */ - static void testLargeFile(FileSystem fs, Class optionClass) throws Exception { - Path tmpDir = Paths.get("tmp"); - if (!Files.exists(tmpDir)) { - Files.createDirectories(tmpDir); - } - Path source = Files.createTempFile(tmpDir, "vavifuse-1-", ".tmp"); - byte[] bytes = new byte[5 * 1024 * 1024 + 12345]; - Random random = new Random(System.currentTimeMillis()); - random.nextBytes(bytes); - Files.write(source, bytes); - - Path dir = fs.getPath("/").resolve("VAVIFUSE_FS_TEST_L"); - Path target = dir.resolve(source.getFileName().toString()); - if (Files.exists(dir)) { - removeTree(dir, true); - } - -System.out.println("mkdir " + dir); - Files.createDirectory(dir); - - OpenOption option = optionClass != null ? optionClass.getDeclaredConstructor(Path.class).newInstance(source) : null; - -//System.out.println("cp " + source + " " + target); -// Files.copy(source, target, option); // our option isn't accepted by jdk - -System.out.println("cp(2) " + source + " " + target); - InputStream in = Files.newInputStream(source, StandardOpenOption.READ); - OutputStream out; - out = option != null ? Files.newOutputStream(target, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW, option) - : Files.newOutputStream(target, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW); - Util.transfer(in, out); - in.close(); - out.close(); - assertTrue(Files.exists(target)); - assertEquals(Files.size(source), Files.size(target)); - assertEquals(Checksum.getChecksum(source), Checksum.getChecksum(target)); - - Path source2 = source.getParent().resolve(source.getFileName().toString().replace("vavifuse-1-", "vavifuse-2-")); - -System.out.println("cp(3) " + target + " " + source2); - SeekableByteChannel inChannel = Files.newByteChannel(target, StandardOpenOption.READ); - SeekableByteChannel outChannel = Files.newByteChannel(source2, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW); - Util.transfer(inChannel, outChannel); - inChannel.close(); - outChannel.close(); - assertTrue(Files.exists(target)); - assertEquals(Files.size(source2), Files.size(target)); - assertEquals(Checksum.getChecksum(source2), Checksum.getChecksum(target)); - -System.out.println("rm " + source2); - Files.delete(source2); -System.out.println("rm " + target); - Files.delete(target); - -System.out.println("cp(3) " + source + " " + target); - inChannel = Files.newByteChannel(source, StandardOpenOption.READ); - outChannel = option != null ? Files.newByteChannel(target, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW, option) - : Files.newByteChannel(target, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW); - Util.transfer(inChannel, outChannel); - inChannel.close(); - outChannel.close(); - assertTrue(Files.exists(target)); - assertEquals(Files.size(source), Files.size(target)); - assertEquals(Checksum.getChecksum(source), Checksum.getChecksum(target)); - - removeTree(dir, true); - - if (Files.exists(source)) { -System.out.println("rm " + source); - Files.delete(source); - } - } - - /** - * prepare "src/test/resources/Hello.java" - */ - static void testMoveFolder(FileSystem fileSystem) throws Exception { - - Path src = Paths.get("src/test/resources" , "Hello.java"); - Path dir = fileSystem.getPath("/").resolve("VAVIFUSE_FS_TEST_F"); - - if (Files.exists(dir)) { - removeTree(dir); - } - - Path dir1 = dir.resolve("work1"); -System.out.println("$ [createDirectory]: " + dir1); - dir1 = Files.createDirectories(dir1); - - Path target = dir1.resolve(src.getFileName().toString()); -System.out.println("$ [copy (upload)]: " + src + " " + target); - target = Files.copy(src, target); - - Path target2 = target.resolveSibling(target.getFileName().toString() + "_ใ‚ณใƒ”ใƒผ"); -System.out.println("$ [copy (internal)]: " + target + " " + target2); - target2 = Files.copy(target, target2); - -System.out.println("$ [list]: " + dir1); -Files.list(dir1).forEach(System.out::println); - assertEquals(2, Files.list(dir1).count()); - - Path dir2 = dir.resolve("work2"); -System.out.println("$ [createDirectory]: " + dir2); - dir2 = Files.createDirectories(dir2); - - Path dir3 = dir2.resolve(dir1.getFileName()); -System.out.println("$ [move (folder, internal)]:" + dir1 + " " + dir3); - dir3 = Files.move(dir1, dir3); // SPEC: move returns target - -System.out.println("$ [list]: " + dir); -Files.list(dir).forEach(System.out::println); - assertEquals(1, Files.list(dir).count()); - -System.out.println("$ [list]: " + dir2); -Files.list(dir2).forEach(System.out::println); - assertEquals(1, Files.list(dir2).count()); - -System.out.println("$ [list]: " + dir3); -Files.list(dir3).forEach(System.out::println); - assertEquals(2, Files.list(dir3).count()); - - removeTree(dir); - } - - /** delete self */ - static void removeTree(Path dir) throws Exception { - removeTree(dir, true); - } - - /** - * @param deleteSelf remove self dir or not - * @see "https://www.baeldung.com/java-delete-directory" - */ - static void removeTree(Path dir, boolean deleteSelf) throws Exception { - Files.walk(dir) - .sorted(Comparator.reverseOrder()) - .forEach(sneaked(p -> { - if (!deleteSelf) { - if (p == dir) { - return; - } - } -System.out.printf("$ [*delete %s]: %s%n", Files.isDirectory(p) ? "dir" : "file", p); - Files.delete(p); -Thread.sleep(300); - })); - } - - /** - * prepare src/test/resources/Hello.java - */ - static void testDescription(FileSystem fileSystem) throws Exception { - - Path src = Paths.get("src/test/resources" , "Hello.java"); - Path dir = fileSystem.getPath("/").resolve("VAVIFUSE_FS_TEST_D"); - - try { - if (Files.exists(dir)) { - removeTree(dir); - } -System.out.println("$ [createDirectory]: " + dir); - dir = Files.createDirectories(dir); - - Path target = dir.resolve(src.getFileName().toString()); -System.out.println("$ [copy (upload)]: " + src + " " + target); - target = Files.copy(src, target); - - Object o = Files.getAttribute(target, "user:description"); -System.out.println("description: " + new String((byte[]) o)); - - String description = "่ชฌๆ˜Ž โ˜…"; - Files.setAttribute(target, "user:description", description.getBytes()); -System.out.println("description write: "); - - o = Files.getAttribute(target, "user:description"); -System.out.println("description: " + new String((byte[]) o)); - assertEquals(description, new String((byte[]) o)); - } finally { - removeTree(dir); - } - } -} - -/* */ diff --git a/src/test/java/vavi/nio/file/CacheTest.java b/src/test/java/vavi/nio/file/CacheTest.java deleted file mode 100644 index a522938..0000000 --- a/src/test/java/vavi/nio/file/CacheTest.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2022 by Naohide Sano, All rights reserved. - * - * Programmed by Naohide Sano - */ - -package vavi.nio.file; - -import java.io.IOException; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.List; -import java.util.Map; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; - - -/** - * CacheTest. - * - * @author Naohide Sano (nsano) - * @version 0.00 2022-07-20 nsano initial version
- */ -public class CacheTest { - - class HackedCache extends Cache { - @Override - public String getEntry(Path path) throws IOException { - return null; - } - public Map getEntryCache() { return entryCache; } - public Map> getFolderCache() { return folderCache; } - } - - @Test - void test1() { - HackedCache cache = new HackedCache(); - Path dir = Paths.get("/aaa"); - cache.addEntry(dir.resolve("bbb"), "bbb"); - cache.setAllowDuplicatedName(true); - cache.addEntry(dir.resolve("bbb"), "bbb"); - assertEquals(2, cache.getFolderCache().get(dir).size()); - cache.setAllowDuplicatedName(false); - cache.addEntry(dir.resolve("bbb"), "bbb"); - assertEquals(2, cache.getFolderCache().get(dir).size()); - cache.setAllowDuplicatedName(true); - cache.addEntry(dir.resolve("bbb"), "bbb"); - assertEquals(3, cache.getFolderCache().get(dir).size()); - } -} diff --git a/src/test/java/vavi/nio/file/EasyFS.java b/src/test/java/vavi/nio/file/EasyFS.java deleted file mode 100644 index d60c263..0000000 --- a/src/test/java/vavi/nio/file/EasyFS.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2022 by Naohide Sano, All rights reserved. - * - * Programmed by Naohide Sano - */ - -package vavi.nio.file; - -import java.io.IOException; -import java.util.List; -import java.util.function.Consumer; - - -/** - * EasyFS. - *

- * This is the interface that unifies each vendors' filesystem - * without java nio filesystem. you can extract code from your - * implementation of com.github.fge.filesystem.driver.FileSystemDriver easily. - * - * @author Naohide Sano (nsano) - * @version 0.00 2022-11-23 nsano initial version
- */ -public interface EasyFS { - - /** */ - boolean isFolder(T entry); - - /** */ - T getRootEntry() throws IOException; - - /** */ - List getDirectoryEntries(T dirEntry) throws IOException; - - /** */ - T renameEntry(T sourceEntry, String name) throws IOException; - - /** */ - void walk(T dirEntry, Consumer task) throws Exception; -} diff --git a/src/test/java/vavi/nio/file/UtilTest.java b/src/test/java/vavi/nio/file/UtilTest.java deleted file mode 100644 index de921f9..0000000 --- a/src/test/java/vavi/nio/file/UtilTest.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (c) 2022 by Naohide Sano, All rights reserved. - * - * Programmed by Naohide Sano - */ - -package vavi.nio.file; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.io.UncheckedIOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; - -import org.junit.jupiter.api.Test; -import vavi.util.Debug; -import vavix.util.Checksum; - -import static org.junit.jupiter.api.Assertions.assertEquals; - - -/** - * UtilTest. - * - * @author Naohide Sano (nsano) - * @version 0.00 2022-07-20 nsano initial version
- */ -public class UtilTest { - - class Uploader { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - Path p; - Uploader(Path p) { - this.p = p; - } - public void upload() throws IOException { - Files.copy(p, baos); - } - } - - @Test - void test1() throws Exception { - Path path = Paths.get("src/test/resources/Hello.java"); - OutputStream os = new Util.StealingOutputStreamForUploading() { - - @Override - protected ByteArrayOutputStream upload() throws IOException { - Uploader uploader = new Uploader(null); - setOutputStream(uploader.baos); - return uploader.baos; - } - - @Override - protected void onClosed(ByteArrayOutputStream baos) { - try { - assertEquals(Checksum.getChecksum(path), - Checksum.getChecksum(new ByteArrayInputStream(baos.toByteArray()))); -Debug.println("\n" + baos); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - }; - - Files.copy(path, os); - os.close(); - } -} diff --git a/src/test/resources/Hello.java b/src/test/resources/Hello.java deleted file mode 100644 index d90d7cb..0000000 --- a/src/test/resources/Hello.java +++ /dev/null @@ -1,5 +0,0 @@ -public class Hello { - public static void main(String[] args) throws Exception { - System.err.println("Hello World"); - } -} \ No newline at end of file diff --git a/src/test/resources/logging.properties b/src/test/resources/logging.properties index ff9ee71..febecce 100644 --- a/src/test/resources/logging.properties +++ b/src/test/resources/logging.properties @@ -3,4 +3,11 @@ handlers=java.util.logging.ConsoleHandler java.util.logging.ConsoleHandler.level=ALL java.util.logging.ConsoleHandler.formatter=vavi.util.logging.VaviFormatter -vavi.util.level=FINER +# +#vavi.util.level=FINER +# javafs +#co.paralleluniverse.fuse.FuseFilesystem.level=FINER +# jnrfuse +# no controllable logger? +# fusejna +#net.fusejna.level=FINER \ No newline at end of file diff --git "a/src/test/resources/\343\203\221\343\203\263\343\203\200.txt" "b/src/test/resources/\343\203\221\343\203\263\343\203\200.txt" deleted file mode 100644 index 7b55f1f..0000000 --- "a/src/test/resources/\343\203\221\343\203\263\343\203\200.txt" +++ /dev/null @@ -1 +0,0 @@ -๐Ÿผ \ No newline at end of file