diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 06ce1d70..fa1c383f 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -31,15 +31,6 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v2 - with: - # We must fetch at least the immediate parents so that if this is - # a pull request then we can checkout the head. - fetch-depth: 2 - - # If this run was triggered by a pull request event, then checkout - # the head of the pull request instead of the merge commit. - - run: git checkout HEAD^2 - if: ${{ github.event_name == 'pull_request' }} # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL @@ -51,21 +42,19 @@ jobs: # Prefix the list here with "+" to use these queries and those in the config file. # queries: ./path/to/local/query, your-org/your-repo/queries@main + - name: Set up JDK 1.8 + uses: actions/setup-java@v1 + with: + java-version: 1.8 + server-id: github # Value of the distributionManagement/repository/id field of the pom.xml + settings-path: ${{ github.workspace }} # location for the settings.xml file + # 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@v1 - - # ℹī¸ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl - - # ✏ī¸ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language - - #- run: | - # make bootstrap - # make release + run: mvn -B install --file pom.xml -s $GITHUB_WORKSPACE/settings.xml + env: + GITHUB_TOKEN: ${{ github.token }} - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index a915866c..15e2a1e3 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -20,5 +20,10 @@ jobs: uses: actions/setup-java@v1 with: java-version: 1.8 + 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 install --file pom.xml + run: mvn -B install --file pom.xml -s $GITHUB_WORKSPACE/settings.xml + env: + GITHUB_TOKEN: ${{ github.token }} diff --git a/.gitignore b/.gitignore index afbfab4a..a6fcca03 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ lib tmp .eclipse +target/ +local.properties diff --git a/README.md b/README.md index 59e3c116..6d3dcb48 100644 --- a/README.md +++ b/README.md @@ -2,30 +2,33 @@ # vavi-apps-fuse -fuse for java and many file systems. +🌏 mount the world! ## Status | fs | list | upload | download | copy | move | rm | mkdir | cache | watch | project | library | |--------------------|------|--------|----------|------|------|----|-------|-------|-------|---------|---------| -| google drive (v3) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | [sub module](vavi-nio-file-googledrive) | [google-api-services-drive](https://developers.google.com/api-client-library/java/) | -| one drive (v1) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | [sub module](vavi-nio-file-onedrive) | [OneDriveJavaSDK](https://github.com/umjammer/OneDriveJavaSDK) | -| one drive (graph) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | [sub module](vavi-nio-file-onedrive3) | [msgraph-sdk-java](https://github.com/microsoftgraph/msgraph-sdk-java) | -| one drive (graph) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | [sub module](vavi-nio-file-onedrive4) | [onedrive-java-client](https://github.com/iterate-ch/onedrive-java-client) | -| dropbox (v2) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | [java7-fs-dropbox](https://github.com/umjammer/java7-fs-dropbox) | | -| box (v2) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | [java7-fs-box](https://github.com/umjammer/java7-fs-box) | | -| vfs (sftp) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | [sub module](vavi-nio-file-vfs) | [commons-vfs2](https://commons.apache.org/proper/commons-vfs/), [jcifs](https://www.jcifs.org/), [jsch](http://www.jcraft.com/jsch/) | -| vfs (webdav ssh) | 🚧 | | | | | | | | | [sub module](vavi-nio-file-vfs) | [commons-vfs2-jackrabbit2](https://commons.apache.org/proper/commons-vfs/commons-vfs2-jackrabbit2/) | -| archive | ✅ | - | ✅ | - | - | - | - | - | | [sub module](vavi-nio-file-archive) | [vavi-util-archive](https://github.com/umjammer/vavi-util-archive) | -| hfs+ (dmg) | ✅ | | | | | | | | | [sub module](vavi-nio-file-hfs) | [hfsexplorer](https://github.com/umjammer/hfsexplorer) | -| [gathered](https://github.com/umjammer/vavi-apps-fuse/wiki/GatheredFileSystem) | ✅ | - | ✅ | - | - | - | - | - | | [sub module](vavi-nio-file-gathered) | - | +| google drive (v3) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | 🚧 | [sub module](vavi-nio-file-googledrive) | [google-api-services-drive](https://developers.google.com/api-client-library/java/) | +| one drive (v1) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | 🚧 | [sub module](vavi-nio-file-onedrive) | [OneDriveJavaSDK](https://github.com/umjammer/OneDriveJavaSDK) | +| one drive (graph) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | 🚧 | [sub module](vavi-nio-file-onedrive3) | [msgraph-sdk-java](https://github.com/microsoftgraph/msgraph-sdk-java) | +| one drive (graph) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | 🚧 | [sub module](vavi-nio-file-onedrive4) | [onedrive-java-client](https://github.com/iterate-ch/onedrive-java-client) | +| dropbox (v2) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | 🚧 | [java7-fs-dropbox](https://github.com/umjammer/java7-fs-dropbox) | | +| box (v2) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | 🚧 | [java7-fs-box](https://github.com/umjammer/java7-fs-box) | | +| vfs (sftp) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | [sub module](vavi-nio-file-vfs) | [commons-vfs2](https://commons.apache.org/proper/commons-vfs/), [jsch](http://www.jcraft.com/jsch/) | +| vfs (smb:cifs-ng) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | [sub module](vavi-nio-file-vfs) | [commons-vfs2-cifs](https://github.com/vbauer/commons-vfs2-cifs), [jcifs-ng](https://github.com/AgNO3/jcifs-ng) | +| vfs (smb:smbj) | ✅ | ✅ | ? | ✅ | đŸšĢ | ✅ | ✅ | - | | [sub module](vavi-nio-file-vfs) | [commons-vfs2-smb](https://github.com/mikhasd/commons-vfs2-smb) | +| vfs (webdav ssh) | 🚧 | | | | | | | | | [sub module](vavi-nio-file-vfs) | [commons-vfs2-jackrabbit2](https://commons.apache.org/proper/commons-vfs/commons-vfs2-jackrabbit2/) | +| vfs (smb:cifs) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | - | | [sub module](vavi-nio-file-vfs) | [commons-vfs2-sandbox](https://commons.apache.org/proper/commons-vfs/commons-vfs2-sandbox/), [jcifs-ng](https://github.com/AgNO3/jcifs-ng/) | +| archive | ✅ | - | ✅ | - | - | - | - | - | | [sub module](vavi-nio-file-archive) | [vavi-util-archive](https://github.com/umjammer/vavi-util-archive) | +| hfs+ (dmg) | ✅ | | ✅ | | | | | | | [sub module](vavi-nio-file-hfs) | [hfsexplorer](https://github.com/umjammer/hfsexplorer) | +| [gathered](https://github.com/umjammer/vavi-apps-fuse/wiki/GatheredFileSystem) | ✅ | - | ✅ | - | - | - | - | - | | [sub module](vavi-nio-file-gathered) | - | | cyberduck (webdav ssh) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | [vavi-nio-file-cyberduck](https://github.com/umjammer/vavi-nio-file-cyberduck) | [cyberduck.webdav](https://github.com/iterate-ch/cyberduck/) | | cyberduck (sftp) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | [vavi-nio-file-cyberduck](https://github.com/umjammer/vavi-nio-file-cyberduck) | [cyberduck.ssh](https://github.com/iterate-ch/cyberduck/) | -| discutils (vdi/ntfs) | ✅ | | ✅ | | | | | | | [vavi-nio-file-discutils](https://github.com/umjammer/vavi-nio-file-discutils) | | -| google play music | ✅ | - | ✅ | - | - | - | - | - | | [vavi-nio-file-googleplaymusic](https://github.com/umjammer/vavi-nio-file-googleplaymusic) | [gplaymusic](https://github.com/umjammer/gplaymusic) | -| fuse (javafs) | ✅ | ✅ | ✅ | ? | ? | ✅ | ✅ | - | | [sub module](vavi-net-fuse) | [javafs](https://github.com/umjammer/javafs) | -| fuse (fuse-jna) | ✅ | ✅ | ✅ | ? | ? | ✅ | ✅ | - | | [sub module](vavi-net-fuse) | [fuse-jna](https://github.com/EtiennePerot/fuse-jna) | -| fuse (jnr-fuse) | 🚧 | ✅ | ✅ | ? | ? | ✅ | ✅ | - | | [sub module](vavi-net-fuse) | [jnr-fuse](https://github.com/SerCeMan/jnr-fuse) | +| discutils (vdi/ntfs) | ✅ | | ✅ | | | | | | | [vavi-nio-file-discutils](https://github.com/umjammer/vavi-nio-file-discutils) | | +| ~~google play music~~ | ✅ | - | ✅ | - | - | - | - | - | | [vavi-nio-file-googleplaymusic](https://github.com/umjammer/vavi-nio-file-googleplaymusic) | [gplaymusic](https://github.com/umjammer/gplaymusic) | +| fuse (javafs) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | - | | [sub module](vavi-net-fuse) | [javafs](https://github.com/umjammer/javafs) | +| fuse (fuse-jna) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | - | | [sub module](vavi-net-fuse) | [fuse-jna](https://github.com/EtiennePerot/fuse-jna) | +| fuse (jnr-fuse) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | - | | [sub module](vavi-net-fuse) | [jnr-fuse](https://github.com/SerCeMan/jnr-fuse) | ## Installation @@ -42,9 +45,18 @@ fuse for java and many file systems. * [instruction wiki](https://github.com/umjammer/vavi-apps-fuse/wiki/Home#installation) +### fuse + + * install [macFUSE](https://osxfuse.github.io/) + * jvmarg (fuse-jna) + * `-Djna.library.path=/usr/local/lib` + ## How To * [how to replace authenticator](https://github.com/umjammer/vavi-apps-fuse/wiki/HowToReplaceAuthenticator) + * [OCR using google drive](https://github.com/umjammer/vavi-apps-fuse/blob/ade22cec00d1ca9a3ade45cf4061228a032e4a32/vavi-nio-file-sandbox/src/test/java/GoogleOCR.java) + * [remove older revisions on google drive](https://github.com/umjammer/vavi-apps-fuse/blob/9608a560f014d515ad95b45de0264dbe3f7c1d62/vavi-nio-file-googledrive/src/test/java/vavi/nio/file/googledrive/Main7.java) + * [write a description to a file on google drive or onedrive](https://github.com/umjammer/vavi-apps-fuse/blob/9608a560f014d515ad95b45de0264dbe3f7c1d62/vavi-nio-file-sandbox/src/test/java/Descriptor.java) ## References @@ -55,12 +67,13 @@ https://github.com/umjammer/vavi-apps-fuse/wiki/Libraries * ~~amazon~~ (only ios and android are supported now) * ~~adrive~~ (i was banned) * ~~flickr~~ (quit 1T service) - * ~~apache-commons-vfs~~ (wip) - * ~~vavi-util-archive~~ (wip) + * ~~apache-commons-vfs~~ (done) + * ~~vavi-util-archive~~ (done) * ~~shutdownHook~~ - * ~~https://github.com/unsound/hfsexplorer~~ (wip) - * ~~virtualbox vdi~~ ([wip](https://github.com/umjammer/vavi-nio-file-discutils)) - * mincraft nbt + * ~~https://github.com/unsound/hfsexplorer~~ (done) + * ~~virtualbox vdi~~ ([done](https://github.com/umjammer/vavi-nio-file-discutils)) + * ~~mincraft nbt~~ (deal in [tree view](https://github.com/umjammer/vavi-apps-treeview)) * ~~credential from uri~~ * ~~https://github.com/cryptomator/fuse-nio-adapter~~ + * https://github.com/mucommander/mucommander diff --git a/pom.xml b/pom.xml index ab0b7fd5..279ede48 100644 --- a/pom.xml +++ b/pom.xml @@ -1,20 +1,13 @@ - + + 4.0.0 vavi vavi-apps-fuse - 0.1.6 - - - 0.1.2 - com.github.umjammer - 0.0.12 - com.github.umjammer - 0.0.10v - + 0.1.7 + vavi-nio-file-commons vavi-nio-file-googledrive vavi-nio-file-amazondrive vavi-nio-file-flickr @@ -29,71 +22,19 @@ vavi-net-fuse + + com.github.umjammer.vavi-net-auth + 0.1.4 + com.github.umjammer + 0.0.13 + com.github.umjammer + 0.0.12v + + pom vavi-apps-fuse https://github.com/umjammer/vavi-apps-fuse - 0.1.0 - - separate into sub modules - -0.0.10 - - fix googledrive redundancy - use util transfer method - -0.0.9 - - fix gatheredfs name map update - -0.0.8 - - update dependencies - -0.0.7 - - catch up with vavi-net-auth - -0.0.6 - - improve google drive - add google drive ocr capability - gatheredfs alias - -0.0.5 - - relive fuse-jna as generic - make onedrive (cyberduck engine) work - make vfs (ssh engine) work - -0.0.4 - - separate nio base - separate oauth2 - -0.0.3 - - apache commons vfs - archive - hfs - -0.0.2 - - nio.file - - onedrive - googledrive (wip) - box - dropbox - - fuse-jna - - onedrive - -TODO - - apple photos.app - google automatic authentication (how to click button) - detect gdocs automatically + fuse for java and many file systems. https://github.com/umjammer/vavi-apps-fuse @@ -105,18 +46,19 @@ TODO + org.apache.maven.plugins maven-compiler-plugin + 3.9.0 1.8 1.8 utf-8 - 3.3 org.apache.maven.plugins maven-surefire-plugin - 2.22.0 + 3.0.0-M5 -Djava.util.logging.config.file=${project.build.testOutputDirectory}/logging.properties false @@ -155,27 +97,22 @@ TODO vavi - vavi-net-fuse - 0.1.6 + vavi-nio-file-commons + 0.1.7 - org.junit.jupiter - junit-jupiter-api - 5.3.0 - test - - - org.junit.jupiter - junit-jupiter-engine - 5.3.0 - test + vavi + vavi-net-fuse + 0.1.7 + - org.junit.platform - junit-platform-commons - 1.5.2 - test + org.junit + junit-bom + 5.8.1 + pom + import @@ -195,13 +132,13 @@ TODO vavi vavi-net-fuse - 0.1.6 + 0.1.7 test vavi vavi-net-fuse - 0.1.6 + 0.1.7 test-jar test diff --git a/vavi-net-fuse/local.properties.sample b/vavi-net-fuse/local.properties.sample new file mode 100644 index 00000000..1e95ddbe --- /dev/null +++ b/vavi-net-fuse/local.properties.sample @@ -0,0 +1 @@ +test.mountPoint=/Users/you/mnt/fuse \ No newline at end of file diff --git a/vavi-net-fuse/pom.xml b/vavi-net-fuse/pom.xml index 575492ce..28b183b8 100644 --- a/vavi-net-fuse/pom.xml +++ b/vavi-net-fuse/pom.xml @@ -7,7 +7,7 @@ vavi vavi-apps-fuse - 0.1.6 + 0.1.7 vavi-net-fuse @@ -21,6 +21,59 @@ https://github.com/umjammer/vavi-net-fuse/issues + + + local (not on CI) + + + ${basedir}/local.properties + + + env.JITPACK + !true + + + + + + org.codehaus.mojo + properties-maven-plugin + 1.0.0 + + + initialize + + read-project-properties + + + + ${basedir}/local.properties + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + **/Main4 + + + ${test.mountPoint} + + + -Djava.util.logging.config.file=${project.build.testOutputDirectory}/logging.properties + -Djna.library.path=/usr/local/lib + + + + + + + + @@ -42,28 +95,24 @@ com.github.umjammer javafs - 0.1.5v + 0.1.6v - + com.github.umjammer fuse-jna - 1.0.3v + 1.0.4v - com.github.serceman jnr-fuse - master-SNAPSHOT + 0.5.7 com.github.umjammer vavi-commons - 1.1.3 + 1.1.6 @@ -89,7 +138,6 @@ org.junit.jupiter junit-jupiter-params - 5.3.0 test diff --git a/vavi-net-fuse/readme.md b/vavi-net-fuse/readme.md new file mode 100644 index 00000000..04471f48 --- /dev/null +++ b/vavi-net-fuse/readme.md @@ -0,0 +1,13 @@ +# vavi-net-fuse + +## 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) | + +## TODO + + * ~~https://github.com/cryptomator/fuse-nio-adapter~~ \ No newline at end of file diff --git a/vavi-net-fuse/src/main/java/vavi/net/fuse/fusejna/JavaNioFileFS.java b/vavi-net-fuse/src/main/java/vavi/net/fuse/fusejna/JavaNioFileFS.java index 371ad533..c552be66 100644 --- a/vavi-net-fuse/src/main/java/vavi/net/fuse/fusejna/JavaNioFileFS.java +++ b/vavi-net-fuse/src/main/java/vavi/net/fuse/fusejna/JavaNioFileFS.java @@ -90,7 +90,7 @@ public int access(final String path, final int access) { @Override public int create(final String path, final ModeWrapper mode, final FileInfoWrapper info) { -Debug.println("create: " + path); +Debug.println(Level.FINE, "create: " + path); try { Set options = new HashSet<>(); options.add(StandardOpenOption.WRITE); @@ -136,8 +136,13 @@ public int getattr(final String path, final StatWrapper stat) { } return 0; } catch (NoSuchFileException e) { + if (e.getMessage().startsWith("ignore apple double file:")) { +Debug.println(Level.FINE, e.getMessage()); + return 0; + } else { Debug.println(e); - return -ErrorCodes.ENOENT(); + return -ErrorCodes.ENOENT(); + } } catch (IOException e) { Debug.printStackTrace(e); return -ErrorCodes.EIO(); @@ -153,7 +158,7 @@ public int fgetattr(final String path, final StatWrapper stat, final FileInfoWra @Override public int mkdir(final String path, final ModeWrapper mode) { -Debug.println("mkdir: " + path); +Debug.println(Level.FINE, "mkdir: " + path); try { fileSystem.provider().createDirectory(fileSystem.getPath(path)); return 0; @@ -165,7 +170,7 @@ public int mkdir(final String path, final ModeWrapper mode) { @Override public int open(final String path, final FileInfoWrapper info) { -Debug.println("open: " + path); +Debug.println(Level.FINE, "open: " + path); try { Set options = new HashSet<>(); options.add(StandardOpenOption.READ); @@ -186,7 +191,7 @@ public int open(final String path, final FileInfoWrapper info) { @Override public int read(final String path, final ByteBuffer buffer, final long size, final long offset, final FileInfoWrapper info) { -Debug.println("read: " + path + ", " + offset + ", " + size + ", " + info.fh()); +Debug.println(Level.FINE, "read: " + path + ", " + offset + ", " + size + ", " + info.fh()); try { if (fileHandles.containsKey(info.fh())) { SeekableByteChannel channel = fileHandles.get(info.fh()); @@ -207,10 +212,10 @@ public int read(final String path, final ByteBuffer buffer, final long size, fin n += c; } } -Debug.println("read: " + n); +Debug.println(Level.FINE, "read: " + n); return n; } else { -Debug.println("read: 0"); +Debug.println(Level.FINE, "read: 0"); return 0; // we did not read any bytes } } else { @@ -224,7 +229,7 @@ public int read(final String path, final ByteBuffer buffer, final long size, fin @Override public int readdir(final String path, final DirectoryFiller filler) { -Debug.println("readdir: " + path); +Debug.println(Level.FINE, "readdir: " + path); try { fileSystem.provider().newDirectoryStream(fileSystem.getPath(path), p -> true) .forEach(p -> { @@ -243,7 +248,7 @@ public int readdir(final String path, final DirectoryFiller filler) { @Override public int rename(final String path, final String newName) { -Debug.println("rename: " + path); +Debug.println(Level.FINE, "rename: " + path); try { fileSystem.provider().move(fileSystem.getPath(path), fileSystem.getPath(newName)); return 0; @@ -255,7 +260,7 @@ public int rename(final String path, final String newName) { @Override public int rmdir(final String path) { -Debug.println("rmdir: " + path); +Debug.println(Level.FINE, "rmdir: " + path); try { fileSystem.provider().delete(fileSystem.getPath(path)); return 0; @@ -267,14 +272,14 @@ public int rmdir(final String path) { @Override public int truncate(final String path, final long offset) { -Debug.println("truncate: " + path); +Debug.println(Level.FINE, "truncate: " + path); // TODO return -ErrorCodes.ENOSYS(); } @Override public int unlink(final String path) { -Debug.println("unlink: " + path); +Debug.println(Level.FINE, "unlink: " + path); try { fileSystem.provider().delete(fileSystem.getPath(path)); return 0; @@ -290,7 +295,7 @@ public int write(final String path, final long size, final long offset, final FileInfoWrapper info) { -Debug.println("write: " + path + ", " + offset + ", " + size + ", " + info.fh()); +Debug.println(Level.FINE, "write: " + path + ", " + offset + ", " + size + ", " + info.fh()); try { if (fileHandles.containsKey(info.fh())) { SeekableByteChannel channel = fileHandles.get(info.fh()); @@ -368,7 +373,7 @@ public int statfs(final String path, final StatvfsWrapper stat) { @Override public int release(final String path, final FileInfoWrapper info) { -Debug.println("release: " + path); +Debug.println(Level.FINE, "release: " + path); try { if (fileHandles.containsKey(info.fh())) { Channel channel = fileHandles.get(info.fh()); @@ -387,7 +392,7 @@ public int release(final String path, final FileInfoWrapper info) { @Override public int chmod(String path, ModeWrapper mode) { -Debug.println("chmod: " + path); +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); diff --git a/vavi-net-fuse/src/main/java/vavi/net/fuse/fusejna/SingleThreadJavaNioFileFS.java b/vavi-net-fuse/src/main/java/vavi/net/fuse/fusejna/SingleThreadJavaNioFileFS.java index b8e9ed2e..318a65bd 100644 --- a/vavi-net-fuse/src/main/java/vavi/net/fuse/fusejna/SingleThreadJavaNioFileFS.java +++ b/vavi-net-fuse/src/main/java/vavi/net/fuse/fusejna/SingleThreadJavaNioFileFS.java @@ -22,7 +22,7 @@ /** - * OneThreadJavaNioFileFS. (fuse-jna) + * SingleThreadJavaNioFileFS. (fuse-jna) * * @author Naohide Sano (umjammer) * @version 0.00 2016/02/29 umjammer initial version
diff --git a/vavi-net-fuse/src/main/java/vavi/net/fuse/javafs/JavaFSFuse.java b/vavi-net-fuse/src/main/java/vavi/net/fuse/javafs/JavaFSFuse.java index e02f74ca..e137e5a5 100644 --- a/vavi-net-fuse/src/main/java/vavi/net/fuse/javafs/JavaFSFuse.java +++ b/vavi-net-fuse/src/main/java/vavi/net/fuse/javafs/JavaFSFuse.java @@ -69,6 +69,12 @@ public void close() throws IOException { 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/vavi-net-fuse/src/main/java/vavi/net/fuse/jnrfuse/JavaNioFileFS.java b/vavi-net-fuse/src/main/java/vavi/net/fuse/jnrfuse/JavaNioFileFS.java index 4ca079f6..f5bcafa1 100644 --- a/vavi-net-fuse/src/main/java/vavi/net/fuse/jnrfuse/JavaNioFileFS.java +++ b/vavi-net-fuse/src/main/java/vavi/net/fuse/jnrfuse/JavaNioFileFS.java @@ -96,7 +96,7 @@ public int access(String path, int access) { @Override public int create(String path, @mode_t long mode, FuseFileInfo info) { -Debug.println("create: " + path); +Debug.println(Level.FINE, "create: " + path); try { Set options = new HashSet<>(); options.add(StandardOpenOption.WRITE); @@ -124,26 +124,35 @@ public int getattr(String path, FileStat stat) { long mode = JnrFuseFuse.permissionsToMode(PosixFileAttributes.class.cast(attributes).permissions()); if (attributes.isDirectory()) { stat.st_mode.set(FileStat.S_IFDIR | mode); - stat.st_atim.tv_sec.set(attributes.lastModifiedTime().to(TimeUnit.SECONDS)); + 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_atim.tv_sec.set(attributes.lastModifiedTime().to(TimeUnit.SECONDS)); + 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_atim.tv_sec.set(attributes.lastModifiedTime().to(TimeUnit.SECONDS)); + 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_atim.tv_sec.set(attributes.lastModifiedTime().to(TimeUnit.SECONDS)); + 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.FINE, e.getMessage()); + return 0; + } else { Debug.println(e); - return -ErrorCodes.ENOENT(); + return -ErrorCodes.ENOENT(); + } } catch (IOException e) { Debug.printStackTrace(e); return -ErrorCodes.EIO(); @@ -159,7 +168,7 @@ public int fgetattr(String path, FileStat stat, FuseFileInfo info) @Override public int mkdir(String path, @mode_t long mode) { -Debug.println("mkdir: " + path); +Debug.println(Level.FINE, "mkdir: " + path); try { fileSystem.provider().createDirectory(fileSystem.getPath(path)); return 0; @@ -171,7 +180,7 @@ public int mkdir(String path, @mode_t long mode) { @Override public int open(String path, FuseFileInfo info) { -Debug.println("open: " + path); +Debug.println(Level.FINE, "open: " + path); try { Set options = new HashSet<>(); options.add(StandardOpenOption.READ); @@ -189,22 +198,22 @@ public int open(String path, FuseFileInfo info) { @Override public int read(String path, Pointer buf, long size, long offset, FuseFileInfo info) { -Debug.println("read: " + path + ", " + offset + ", " + size + ", " + info.fh.get()); +Debug.println(Level.FINE, "read: " + path + ", " + offset + ", " + size + ", " + info.fh.get()); try { if (fileHandles.containsKey(info.fh.get())) { SeekableByteChannel channel = fileHandles.get(info.fh.get()); ByteBuffer bb = ByteBuffer.allocate(BUFFER_SIZE); long pos = 0; -Debug.printf("Attempting to read %d-%d:", offset, offset + size); +Debug.printf(Level.FINE, "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("Reached EOF"); +Debug.println(Level.FINE, "Reached EOF"); return (int) pos; // reached EOF TODO: wtf cast } else { -Debug.printf("Reading %d-%d", offset + pos, offset + pos + read); +Debug.printf(Level.FINE, "Reading %d-%d", offset + pos, offset + pos + read); buf.put(pos, bb.array(), 0, read); pos += read; } @@ -219,13 +228,13 @@ public int read(String path, Pointer buf, long size, long offset, FuseFileInfo i } } - // TODO https://github.com/SerCeMan/jnr-fuse/issues/72 @Override public int readdir(String path, Pointer buf, FuseFillDir filler, @off_t long offset, FuseFileInfo info) { -Debug.println("readdir: " + path); +Debug.println(Level.FINE, "readdir: " + path); try { fileSystem.provider().newDirectoryStream(fileSystem.getPath(path), p -> true) .forEach(p -> { +Debug.println(Level.FINE, "p: " + p); try { filler.apply(buf, Util.toFilenameString(p), null, 0); } catch (IOException e) { @@ -241,7 +250,7 @@ public int readdir(String path, Pointer buf, FuseFillDir filler, @off_t long off @Override public int rename(String path, String newName) { -Debug.println("rename: " + path); +Debug.println(Level.FINE, "rename: " + path); try { fileSystem.provider().move(fileSystem.getPath(path), fileSystem.getPath(newName)); return 0; @@ -253,7 +262,7 @@ public int rename(String path, String newName) { @Override public int rmdir(String path) { -Debug.println("rmdir: " + path); +Debug.println(Level.FINE, "rmdir: " + path); try { fileSystem.provider().delete(fileSystem.getPath(path)); return 0; @@ -265,14 +274,14 @@ public int rmdir(String path) { @Override public int truncate(String path, long offset) { -Debug.println("truncate: " + path); +Debug.println(Level.FINE, "truncate: " + path); // TODO return -ErrorCodes.ENOSYS(); } @Override public int unlink(String path) { -Debug.println("unlink: " + path); +Debug.println(Level.FINE, "unlink: " + path); try { fileSystem.provider().delete(fileSystem.getPath(path)); return 0; @@ -284,7 +293,7 @@ public int unlink(String path) { @Override public int write(String path, Pointer buf, @size_t long size, @off_t long offset, FuseFileInfo info) { -Debug.println("write: " + path + ", " + offset + ", " + size + ", " + info.fh.get()); +Debug.println(Level.FINE, "write: " + path + ", " + offset + ", " + size + ", " + info.fh.get()); try { if (fileHandles.containsKey(info.fh.get())) { ByteBuffer bb = ByteBuffer.allocate(BUFFER_SIZE); @@ -357,7 +366,7 @@ public int statfs(String path, Statvfs stbuf) { @Override public int chmod(String path, @mode_t long mode) { -Debug.println("chmod: " + path); +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); @@ -375,7 +384,7 @@ public int chmod(String path, @mode_t long mode) { @Override public int release(String path, FuseFileInfo info) { -Debug.println("release: " + path); +Debug.println(Level.FINE, "release: " + path); try { if (fileHandles.containsKey(info.fh.get())) { Channel channel = fileHandles.get(info.fh.get()); @@ -394,7 +403,7 @@ public int release(String path, FuseFileInfo info) { @Override public int lock(String path, FuseFileInfo fi, int cmd, Flock flock) { -Debug.println("lock: " + path); +Debug.println(Level.FINE, "lock: " + path); return 0; } } diff --git a/vavi-net-fuse/src/main/java/vavi/net/fuse/jnrfuse/SingleThreadJavaNioFileFS.java b/vavi-net-fuse/src/main/java/vavi/net/fuse/jnrfuse/SingleThreadJavaNioFileFS.java index dc282f4e..d74e411c 100644 --- a/vavi-net-fuse/src/main/java/vavi/net/fuse/jnrfuse/SingleThreadJavaNioFileFS.java +++ b/vavi-net-fuse/src/main/java/vavi/net/fuse/jnrfuse/SingleThreadJavaNioFileFS.java @@ -22,7 +22,7 @@ /** - * OneThreadJavaNioFileFS. (jnr-fuse) + * SingleThreadJavaNioFileFS. (jnr-fuse) * * @author Naohide Sano (umjammer) * @version 0.00 2016/02/29 umjammer initial version
diff --git a/vavi-net-fuse/src/test/java/Main4.java b/vavi-net-fuse/src/test/java/Main4.java index 517e0558..5b698e12 100644 --- a/vavi-net-fuse/src/test/java/Main4.java +++ b/vavi-net-fuse/src/test/java/Main4.java @@ -53,13 +53,16 @@ @DisabledIfEnvironmentVariable(named = "GITHUB_WORKFLOW", matches = ".*") 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 { - System.setProperty("vavi.util.logging.VaviFormatter.extraClassMethod", "co\\.paralleluniverse\\.fuse\\.LoggedFuseFilesystem#log"); mountPoint = System.getenv("TEST4_MOUNT_POINT"); Debug.println("mountPoint: " + mountPoint); @@ -82,6 +85,7 @@ public void before() throws Exception { }) public void test01(String providerClassName) throws Exception { System.setProperty("vavi.net.fuse.FuseProvider.class", providerClassName); +System.err.println("--------------------------- " + providerClassName + " ---------------------------"); Base.testFuse(fs, mountPoint, options); diff --git a/vavi-net-fuse/src/test/java/vavi/net/fuse/Base.java b/vavi-net-fuse/src/test/java/vavi/net/fuse/Base.java index 110c5d86..5579e43d 100644 --- a/vavi-net-fuse/src/test/java/vavi/net/fuse/Base.java +++ b/vavi-net-fuse/src/test/java/vavi/net/fuse/Base.java @@ -29,11 +29,7 @@ * @author Naohide Sano (umjammer) * @version 0.00 2020/06/05 umjammer initial version
*/ -public class Base { - - /** */ - private Base() { - } +public abstract class Base { /** */ private static int exec(String... commandLine) throws Exception { diff --git a/vavi-nio-file-amazondrive/pom.xml b/vavi-nio-file-amazondrive/pom.xml index ace5be15..a1966e40 100644 --- a/vavi-nio-file-amazondrive/pom.xml +++ b/vavi-nio-file-amazondrive/pom.xml @@ -5,7 +5,7 @@ vavi vavi-apps-fuse - 0.1.6 + 0.1.7 vavi-nio-file-amazondrive @@ -19,6 +19,13 @@ https://github.com/umjammer/vavi-nio-file-amazondrive/issues + + + jitpack.io + https://jitpack.io + + + @@ -32,7 +39,7 @@ - com.github.umjammer.vavi-net-auth + ${vavi-net-auth.groupId} vavi-net-auth-amazon ${vavi-net-auth.version} diff --git a/vavi-nio-file-amazondrive/src/main/java/vavi/nio/file/acd/AcdFileAttributesFactory.java b/vavi-nio-file-amazondrive/src/main/java/vavi/nio/file/acd/AcdFileAttributesFactory.java index 8eb3b44a..184298be 100644 --- a/vavi-nio-file-amazondrive/src/main/java/vavi/nio/file/acd/AcdFileAttributesFactory.java +++ b/vavi-nio-file-amazondrive/src/main/java/vavi/nio/file/acd/AcdFileAttributesFactory.java @@ -8,7 +8,7 @@ import org.yetiz.lib.acd.Entity.NodeInfo; -import com.github.fge.filesystem.attributes.FileAttributesFactory; +import com.github.fge.filesystem.driver.ExtendedFileSystemDriverBase.ExtendsdFileAttributesFactory; /** @@ -17,7 +17,7 @@ * @author Naohide Sano (umjammer) * @version 0.00 2016/03/30 umjammer initial version
*/ -public final class AcdFileAttributesFactory extends FileAttributesFactory { +public final class AcdFileAttributesFactory extends ExtendsdFileAttributesFactory { public AcdFileAttributesFactory() { setMetadataClass(NodeInfo.class); diff --git a/vavi-nio-file-amazondrive/src/main/java/vavi/nio/file/acd/AcdFileSystemDriver.java b/vavi-nio-file-amazondrive/src/main/java/vavi/nio/file/acd/AcdFileSystemDriver.java index e80f92ca..7bc23168 100644 --- a/vavi-nio-file-amazondrive/src/main/java/vavi/nio/file/acd/AcdFileSystemDriver.java +++ b/vavi-nio-file-amazondrive/src/main/java/vavi/nio/file/acd/AcdFileSystemDriver.java @@ -6,29 +6,18 @@ package vavi.nio.file.acd; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.nio.file.AccessDeniedException; -import java.nio.file.AccessMode; import java.nio.file.CopyOption; -import java.nio.file.DirectoryNotEmptyException; -import java.nio.file.DirectoryStream; -import java.nio.file.FileAlreadyExistsException; import java.nio.file.FileStore; -import java.nio.file.NoSuchFileException; -import java.nio.file.NotDirectoryException; import java.nio.file.OpenOption; import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.nio.file.attribute.FileAttribute; -import java.nio.file.spi.FileSystemProvider; -import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; -import javax.annotation.Nonnull; import javax.annotation.ParametersAreNonnullByDefault; import org.yetiz.lib.acd.ACD; @@ -37,14 +26,9 @@ import org.yetiz.lib.acd.Entity.NodeInfo; import org.yetiz.lib.acd.api.v1.Nodes; -import com.github.fge.filesystem.driver.ExtendedFileSystemDriverBase; -import com.github.fge.filesystem.exceptions.IsDirectoryException; +import com.github.fge.filesystem.driver.CachedFileSystemDriver; import com.github.fge.filesystem.provider.FileSystemFactoryProvider; -import vavi.nio.file.Cache; -import vavi.nio.file.Util; -import vavi.util.Debug; - import static vavi.nio.file.Util.toFilenameString; import static vavi.nio.file.Util.toPathString; @@ -56,14 +40,11 @@ * @version 0.00 2016/03/30 umjammer initial version
*/ @ParametersAreNonnullByDefault -public final class AcdFileSystemDriver extends ExtendedFileSystemDriverBase { +public final class AcdFileSystemDriver extends CachedFileSystemDriver { private final ACD drive; private ACDSession session; - private boolean ignoreAppleDouble = false; - - @SuppressWarnings("unchecked") public AcdFileSystemDriver(final FileStore fileStore, final FileSystemFactoryProvider provider, final ACD drive, @@ -72,280 +53,91 @@ public AcdFileSystemDriver(final FileStore fileStore, super(fileStore, provider); this.drive = drive; this.session = session; - ignoreAppleDouble = (Boolean) ((Map) env).getOrDefault("ignoreAppleDouble", Boolean.FALSE); -//System.err.println("ignoreAppleDouble: " + ignoreAppleDouble); + setEnv(env); } - /** */ - private Cache cache = new Cache() { - /** - * TODO when the parent is not cached - * @see #ignoreAppleDouble - * @throws NoSuchFileException must be thrown when the path is not found in this cache - */ - public NodeInfo getEntry(Path path) throws IOException { - if (cache.containsFile(path)) { - return cache.getFile(path); - } else { - if (ignoreAppleDouble && path.getFileName() != null && Util.isAppleDouble(path)) { - throw new NoSuchFileException("ignore apple double file: " + path); - } - - if (path.getNameCount() == 0) { - return Nodes.getRootFolder(session); - } else { - NodeInfo entry = Nodes.getFileMetadata(session, toPathString(path)); // TODO -//System.err.println("GOT: path: " + path + ", id: " + entry.getId()); - if (entry == null) { - // cache - if (cache.containsFile(path)) { - cache.removeEntry(path); - } - throw new NoSuchFileException(path.toString()); - } - cache.putFile(path, entry); - return entry; - } - } - } - }; - - @Nonnull @Override - public InputStream newInputStream(final Path path, final Set options) throws IOException { - final NodeInfo entry = cache.getEntry(path); - - if (entry.isFolder()) { - throw new IsDirectoryException(path.toString()); - } - - return drive.getFile(entry.getId()); + protected String getFilenameString(NodeInfo entry) { + return entry.getName(); } - @Nonnull @Override - public OutputStream newOutputStream(final Path path, final Set options) throws IOException { - final NodeInfo entry; - try { - entry = cache.getEntry(path); - - if (entry.isFolder()) { - throw new IsDirectoryException(path.toString()); - } else { - throw new FileAlreadyExistsException(path.toString()); - } - } catch (IOException e) { -Debug.println("newOutputStream: " + e.getMessage()); - } - - java.io.File temp = java.io.File.createTempFile("vavi-apps-fuse-", ".upload"); - - return new AcdOutputStream(drive, temp, toFilenameString(path), FolderInfo.class.cast(cache.getEntry(path.getParent())), file -> { -System.out.println("file: " + file.getName() + ", " + file.getCreationDate() + ", " + file.getContentProperties().getSize()); - cache.addEntry(path, file); - }); + protected boolean isFolder(NodeInfo entry) { + return entry.isFolder(); } - @Nonnull @Override - public DirectoryStream newDirectoryStream(final Path dir, - final DirectoryStream.Filter filter) throws IOException { - return Util.newDirectoryStream(getDirectoryEntries(dir), filter); + protected NodeInfo getRootEntry(Path root) throws IOException { + return Nodes.getRootFolder(session); } @Override - public void createDirectory(final Path dir, final FileAttribute... attrs) throws IOException { - NodeInfo parentEntry = cache.getEntry(dir.getParent()); - - // TODO: how to diagnose? - NodeInfo newEntry = drive.createFolder(parentEntry.getId(), toFilenameString(dir)); - - cache.addEntry(dir, newEntry); + protected NodeInfo getEntry(NodeInfo parentEntry, Path path)throws IOException { + return Nodes.getFileMetadata(session, toPathString(path)); // TODO } @Override - public void delete(final Path path) throws IOException { - removeEntry(path); + protected InputStream downloadEntry(NodeInfo entry, Path path, Set options) throws IOException { + return drive.getFile(entry.getId()); } - // TODO there is no api??? @Override - public void copy(final Path source, final Path target, final Set options) throws IOException { - if (cache.existsEntry(target)) { - if (options != null && options.stream().anyMatch(o -> o.equals(StandardCopyOption.REPLACE_EXISTING))) { - removeEntry(target); - } else { - throw new FileAlreadyExistsException(target.toString()); - } - } - copyEntry(source, target); - } + protected OutputStream uploadEntry(NodeInfo parentEntry, Path path, Set options) throws IOException { + File temp = File.createTempFile("vavi-apps-fuse-", ".upload"); - @Override - public void move(final Path source, final Path target, final Set options) throws IOException { - if (cache.existsEntry(target)) { - if (cache.getEntry(target).isFolder()) { - if (options != null && options.stream().anyMatch(o -> o.equals(StandardCopyOption.REPLACE_EXISTING))) { - // replace the target - if (cache.getChildCount(target) > 0) { - throw new DirectoryNotEmptyException(target.toString()); - } else { - removeEntry(target); - moveEntry(source, target, false); - } - } else { - // move into the target - // TODO SPEC is FileAlreadyExistsException ? - moveEntry(source, target, true); - } - } else { - if (options != null && options.stream().anyMatch(o -> o.equals(StandardCopyOption.REPLACE_EXISTING))) { - removeEntry(target); - moveEntry(source, target, false); - } else { - throw new FileAlreadyExistsException(target.toString()); - } - } - } else { - if (source.getParent().equals(target.getParent())) { - // rename - renameEntry(source, target); - } else { - moveEntry(source, target, false); - } - } + return new AcdOutputStream(drive, temp, toFilenameString(path), FolderInfo.class.cast(parentEntry), file -> { +System.out.println("file: " + file.getName() + ", " + file.getCreationDate() + ", " + file.getContentProperties().getSize()); + updateEntry(path, file); + }); } - /** - * Check access modes for a path on this filesystem - *

- * If no modes are provided to check for, this simply checks for the - * existence of the path. - *

- * - * @param path the path to check - * @param modes the modes to check for, if any - * @throws IOException filesystem level error, or a plain I/O error - * if you use this with javafs (jnr-fuse), you should throw {@link NoSuchFileException} when the file not found. - * @see FileSystemProvider#checkAccess(Path, AccessMode...) - */ @Override - protected void checkAccessImpl(final Path path, final AccessMode... modes) throws IOException { - final NodeInfo entry = cache.getEntry(path); - - if (!entry.isFile()) { - return; - } - - // TODO: assumed; not a file == directory - for (final AccessMode mode : modes) { - if (mode == AccessMode.EXECUTE) { - throw new AccessDeniedException(path.toString()); - } - } + protected List getDirectoryEntries(NodeInfo dirEntry, Path dir) throws IOException { + return drive.getList(dirEntry.getId()); } @Override - public void close() throws IOException { - drive.destroy(); + protected NodeInfo createDirectoryEntry(NodeInfo parentEntry, Path dir) throws IOException { + // TODO: how to diagnose? + return drive.createFolder(parentEntry.getId(), toFilenameString(dir)); } - /** - * @throws IOException if you use this with javafs (jnr-fuse), you should throw {@link NoSuchFileException} when the file not found. - */ - @Nonnull @Override - protected Object getPathMetadataImpl(final Path path) throws IOException { - return cache.getEntry(path); - } - - /** */ - private List getDirectoryEntries(final Path dir) throws IOException { - final NodeInfo entry = cache.getEntry(dir); - - if (!entry.isFolder()) { - throw new NotDirectoryException("dir: " + dir); - } - - List list = null; - if (cache.containsFolder(dir)) { - list = cache.getFolder(dir); - } else { - final List children = drive.getList(entry.getId()); - list = new ArrayList<>(children.size()); - - // TODO nextPageToken - for (final NodeInfo child : children) { - Path childPath = dir.resolve(child.getName()); - list.add(childPath); - cache.putFile(childPath, child); - } - - cache.putFolder(dir, list); - } - - return list; + protected boolean hasChildren(NodeInfo dirEntry, Path dir) throws IOException { + return drive.getList(dirEntry.getId()).size() > 0; } - /** */ - private void removeEntry(Path path) throws IOException { - NodeInfo entry = cache.getEntry(path); + @Override + protected void removeEntry(NodeInfo entry, Path path) throws IOException { if (entry.isFolder()) { - if (drive.getList(entry.getId()).size() > 0) { - throw new DirectoryNotEmptyException(path.toString()); - } - drive.removeFolder(entry.getId()); } else { drive.removeFile(entry.getId()); } - - cache.removeEntry(path); } - /** */ - private void copyEntry(final Path source, final Path target) throws IOException { - NodeInfo sourceEntry = cache.getEntry(source); - NodeInfo targetParentEntry = cache.getEntry(target.getParent()); - if (sourceEntry.isFile()) { - NodeInfo newEntry = null; // TODO - cache.addEntry(target, newEntry); - } else if (sourceEntry.isFolder()) { - // TODO java spec. allows empty folder - throw new IsDirectoryException("source can not be a folder: " + source); - } + @Override + protected NodeInfo copyEntry(NodeInfo sourceEntry, NodeInfo targetParentEntry, Path source, Path target, Set options) throws IOException { + return null; // TODO } - /** - * @param targetIsParent if the target is folder - */ - private void moveEntry(final Path source, final Path target, boolean targetIsParent) throws IOException { - NodeInfo sourceEntry = cache.getEntry(source); - if (sourceEntry.isFile()) { - Path actualPath = targetIsParent ? target.resolve(toFilenameString(source)) : target; - NodeInfo patchedEntry = drive.renameFile(sourceEntry.getId(), toPathString(actualPath)); // TODO - cache.removeEntry(source); - if (targetIsParent) { - cache.addEntry(target.resolve(source.getFileName()), patchedEntry); - } else { - cache.addEntry(target, patchedEntry); - } - } else if (sourceEntry.isFolder()) { - Path actualPath = targetIsParent ? target.resolve(toFilenameString(source)) : target; - NodeInfo patchedEntry = drive.renameFolder(sourceEntry.getId(), toPathString(actualPath)); // TODO - cache.removeEntry(source); - cache.addEntry(target, patchedEntry); - } + @Override + protected NodeInfo moveEntry(NodeInfo sourceEntry, NodeInfo targetParentEntry, Path source, Path target, boolean targetIsParent) throws IOException { + return drive.renameFile(sourceEntry.getId(), toPathString(target)); // TODO } - /** */ - private void renameEntry(final Path source, final Path target) throws IOException { - NodeInfo sourceEntry = cache.getEntry(source); -//Debug.println(sourceEntry.id + ", " + sourceEntry.name); + @Override + protected NodeInfo moveFolderEntry(NodeInfo sourceEntry, NodeInfo targetParentEntry, Path source, Path target, boolean targetIsParent) throws IOException { + return drive.renameFolder(sourceEntry.getId(), toPathString(target)); // TODO + } - NodeInfo patchedEntry = drive.renameFile(sourceEntry.getId(), toFilenameString(target)); // TODO + @Override + protected NodeInfo renameEntry(NodeInfo sourceEntry, NodeInfo targetParentEntry, Path source, Path target) throws IOException { + return drive.renameFile(sourceEntry.getId(), toFilenameString(target)); // TODO + } - cache.removeEntry(source); - cache.addEntry(target, patchedEntry); + @Override + public void close() throws IOException { + drive.destroy(); } } diff --git a/vavi-nio-file-amazondrive/src/main/java/vavi/nio/file/acd/AcdOutputStream.java b/vavi-nio-file-amazondrive/src/main/java/vavi/nio/file/acd/AcdOutputStream.java index 7724d53f..b59f7e07 100644 --- a/vavi-nio-file-amazondrive/src/main/java/vavi/nio/file/acd/AcdOutputStream.java +++ b/vavi-nio-file-amazondrive/src/main/java/vavi/nio/file/acd/AcdOutputStream.java @@ -42,6 +42,7 @@ *

*/ // TODO: more complex than the input stuff; check again (.abort(), etc) +// TODO to be eliminated public final class AcdOutputStream extends OutputStream { private final AtomicBoolean closeCalled = new AtomicBoolean(false); diff --git a/vavi-nio-file-archive/pom.xml b/vavi-nio-file-archive/pom.xml index c6130e74..3422d1de 100644 --- a/vavi-nio-file-archive/pom.xml +++ b/vavi-nio-file-archive/pom.xml @@ -5,7 +5,7 @@ vavi vavi-apps-fuse - 0.1.6 + 0.1.7 vavi-nio-file-archive @@ -19,6 +19,13 @@ https://github.com/umjammer/vavi-nio-file-archive/issues + + + jitpack.io + https://jitpack.io + + + @@ -35,12 +42,12 @@ com.github.umjammer vavi-util-archive - 1.0.8 + 1.1.0 com.github.umjammer vavi-util-archive-sandbox - 1.0.2 + 1.0.4 @@ -63,5 +70,22 @@ junit-platform-commons test + + org.junit.jupiter + junit-jupiter-params + test + + + + vavi + vavi-net-fuse + test + + + vavi + vavi-net-fuse + test-jar + test +
diff --git a/vavi-nio-file-archive/src/main/java/vavi/nio/file/archive/ArchiveBasicFileAttributesProvider.java b/vavi-nio-file-archive/src/main/java/vavi/nio/file/archive/ArchiveBasicFileAttributesProvider.java index 002be096..6cde844d 100644 --- a/vavi-nio-file-archive/src/main/java/vavi/nio/file/archive/ArchiveBasicFileAttributesProvider.java +++ b/vavi-nio-file-archive/src/main/java/vavi/nio/file/archive/ArchiveBasicFileAttributesProvider.java @@ -16,8 +16,6 @@ import java.nio.file.attribute.UserPrincipal; import java.util.Set; -import javax.annotation.Nonnull; - import com.github.fge.filesystem.attributes.provider.BasicFileAttributesProvider; import vavi.util.archive.Entry; @@ -35,9 +33,10 @@ */ public final class ArchiveBasicFileAttributesProvider extends BasicFileAttributesProvider implements PosixFileAttributes { - private Entry entry; + /** null means root */ + private Entry entry; - public ArchiveBasicFileAttributesProvider(@Nonnull final Entry entry) throws IOException { + public ArchiveBasicFileAttributesProvider(final Entry entry) throws IOException { this.entry = entry; } @@ -54,7 +53,7 @@ public ArchiveBasicFileAttributesProvider(@Nonnull final Entry entry) throws */ @Override public FileTime lastModifiedTime() { - return FileTime.fromMillis(entry.getTime()); + return FileTime.fromMillis(entry == null ? 0 : entry.getTime()); } /** @@ -62,7 +61,7 @@ public FileTime lastModifiedTime() { */ @Override public boolean isRegularFile() { - return !entry.isDirectory(); + return entry != null && !entry.isDirectory(); } /** @@ -70,7 +69,8 @@ public boolean isRegularFile() { */ @Override public boolean isDirectory() { - return entry.isDirectory(); +//Debug.println("entry: " + entry + ", " + (entry != null ? entry.isDirectory() : "means root")); + return entry == null || entry.isDirectory(); } /** @@ -84,7 +84,7 @@ public boolean isDirectory() { */ @Override public long size() { - return entry.getSize(); + return entry == null ? 0 : entry.getSize(); } @Override diff --git a/vavi-nio-file-archive/src/main/java/vavi/nio/file/archive/ArchiveFileAttributesFactory.java b/vavi-nio-file-archive/src/main/java/vavi/nio/file/archive/ArchiveFileAttributesFactory.java index 51c89f3f..7530dc89 100644 --- a/vavi-nio-file-archive/src/main/java/vavi/nio/file/archive/ArchiveFileAttributesFactory.java +++ b/vavi-nio-file-archive/src/main/java/vavi/nio/file/archive/ArchiveFileAttributesFactory.java @@ -6,7 +6,7 @@ package vavi.nio.file.archive; -import com.github.fge.filesystem.attributes.FileAttributesFactory; +import com.github.fge.filesystem.driver.ExtendedFileSystemDriverBase.ExtendsdFileAttributesFactory; import vavi.util.archive.Entry; @@ -17,7 +17,7 @@ * @author Naohide Sano (umjammer) * @version 0.00 2016/04/06 umjammer initial version
*/ -public final class ArchiveFileAttributesFactory extends FileAttributesFactory { +public final class ArchiveFileAttributesFactory extends ExtendsdFileAttributesFactory { public ArchiveFileAttributesFactory() { setMetadataClass(Entry.class); diff --git a/vavi-nio-file-archive/src/main/java/vavi/nio/file/archive/ArchiveFileSystemDriver.java b/vavi-nio-file-archive/src/main/java/vavi/nio/file/archive/ArchiveFileSystemDriver.java index 8fb8c357..fc4821c6 100644 --- a/vavi-nio-file-archive/src/main/java/vavi/nio/file/archive/ArchiveFileSystemDriver.java +++ b/vavi-nio-file-archive/src/main/java/vavi/nio/file/archive/ArchiveFileSystemDriver.java @@ -9,7 +9,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.nio.channels.SeekableByteChannel; import java.nio.file.AccessMode; import java.nio.file.CopyOption; import java.nio.file.DirectoryStream; @@ -17,21 +16,20 @@ import java.nio.file.NoSuchFileException; import java.nio.file.OpenOption; import java.nio.file.Path; -import java.nio.file.StandardOpenOption; import java.nio.file.attribute.FileAttribute; -import java.nio.file.spi.FileSystemProvider; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.logging.Level; import javax.annotation.Nonnull; -import javax.annotation.ParametersAreNonnullByDefault; import com.github.fge.filesystem.driver.ExtendedFileSystemDriverBase; import com.github.fge.filesystem.provider.FileSystemFactoryProvider; import vavi.nio.file.Util; +import vavi.util.Debug; import vavi.util.archive.Archive; import vavi.util.archive.Entry; @@ -42,7 +40,6 @@ * @author Naohide Sano (umjammer) * @version 0.00 2016/03/30 umjammer initial version
*/ -@ParametersAreNonnullByDefault public final class ArchiveFileSystemDriver extends ExtendedFileSystemDriverBase { private final Archive archive; @@ -50,119 +47,81 @@ public final class ArchiveFileSystemDriver extends ExtendedFileSystemDriverBase /** * @param env */ - public ArchiveFileSystemDriver(final FileStore fileStore, - final FileSystemFactoryProvider provider, - final Archive archive, - final Map env) throws IOException { + public ArchiveFileSystemDriver(FileStore fileStore, + FileSystemFactoryProvider provider, + Archive archive, + Map env) throws IOException { super(fileStore, provider); + setEnv(env); this.archive = archive; } - @Nonnull + /** */ + private Entry getEntry(Path path) { +Debug.println(Level.FINE, "entry: \"" + path.toAbsolutePath().toString().substring(1) + "\""); + return archive.getEntry(path.toAbsolutePath().toString().substring(1)); + } + @Override - public InputStream newInputStream(final Path path, final Set options) throws IOException { - return archive.getInputStream(archive.getEntry(path.toAbsolutePath().toString())); + public InputStream newInputStream(Path path, Set options) throws IOException { + return archive.getInputStream(getEntry(path)); } - @Nonnull @Override - public OutputStream newOutputStream(final Path path, final Set options) throws IOException { + public OutputStream newOutputStream(Path path, Set options) throws IOException { throw new UnsupportedOperationException("newOutputStream is not supported by the file system"); } @Nonnull @Override - public DirectoryStream newDirectoryStream(final Path dir, - final DirectoryStream.Filter filter) throws IOException { + public DirectoryStream newDirectoryStream(Path dir, + DirectoryStream.Filter filter) throws IOException { return Util.newDirectoryStream(getDirectoryEntries(dir), filter); } @Override - public SeekableByteChannel newByteChannel(Path path, - Set options, - FileAttribute... attrs) throws IOException { - if (options != null && Util.isWriting(options)) { - return new Util.SeekableByteChannelForWriting(newOutputStream(path, options)) { - @Override - protected long getLeftOver() throws IOException { - long leftover = 0; - if (options.contains(StandardOpenOption.APPEND)) { - Entry entry = archive.getEntry(path.toAbsolutePath().toString()); - if (entry != null && entry.getSize() >= 0) { - leftover = entry.getSize(); - } - } - return leftover; - } - }; - } else { - Entry entry = archive.getEntry(path.toAbsolutePath().toString()); - if (entry.isDirectory()) { - throw new NoSuchFileException(path.toString()); - } - return new Util.SeekableByteChannelForReading(newInputStream(path, null)) { - @Override - protected long getSize() throws IOException { - return entry.getSize(); - } - }; - } - } - - @Override - public void createDirectory(final Path dir, final FileAttribute... attrs) throws IOException { + public void createDirectory(Path dir, FileAttribute... attrs) throws IOException { throw new UnsupportedOperationException("createDirectory is not supported by the file system"); } @Override - public void delete(final Path path) throws IOException { + public void delete(Path path) throws IOException { throw new UnsupportedOperationException("delete is not supported by the file system"); } @Override - public void copy(final Path source, final Path target, final Set options) throws IOException { + public void copy(Path source, Path target, Set options) throws IOException { throw new UnsupportedOperationException("copy is not supported by the file system"); } @Override - public void move(final Path source, final Path target, final Set options) throws IOException { + public void move(Path source, Path target, Set options) throws IOException { throw new UnsupportedOperationException("move is not supported by the file system"); } - /** - * Check access modes for a path on this filesystem - *

- * If no modes are provided to check for, this simply checks for the - * existence of the path. - *

- * - * @param path the path to check - * @param modes the modes to check for, if any - * @see FileSystemProvider#checkAccess(Path, AccessMode...) - */ - @Override - protected void checkAccessImpl(final Path path, final AccessMode... modes) throws IOException { - } - @Override - public void close() throws IOException { - archive.close(); + protected void checkAccessImpl(Path path, AccessMode... modes) throws IOException { } /** + * @return null when path is root * @throws IOException if you use this with javafs (jnr-fuse), you should throw {@link NoSuchFileException} when the file not found. */ - @Nonnull @Override protected Object getPathMetadataImpl(final Path path) throws IOException { - return archive.getEntry(path.getFileName().toString()); + return getEntry(path); + } + + @Override + public void close() throws IOException { + archive.close(); } /** */ private List getDirectoryEntries(final Path dir) throws IOException { List list = new ArrayList<>(archive.size()); - for (Entry entry : archive.entries()) { + for (Entry entry : archive.entries()) { Path childPath = dir.resolve(entry.getName()); list.add(childPath); } diff --git a/vavi-nio-file-archive/src/main/java/vavi/nio/file/archive/ArchiveFileSystemRepository.java b/vavi-nio-file-archive/src/main/java/vavi/nio/file/archive/ArchiveFileSystemRepository.java index e6a5dadf..c5431cd0 100644 --- a/vavi-nio-file-archive/src/main/java/vavi/nio/file/archive/ArchiveFileSystemRepository.java +++ b/vavi-nio-file-archive/src/main/java/vavi/nio/file/archive/ArchiveFileSystemRepository.java @@ -37,7 +37,7 @@ public ArchiveFileSystemRepository() { } /** - * @param uri "archive:file:/tmp/jar/exam.jar!/img/sample.png" + * @param uri "archive:file:/tmp/jar/exam.jar!/img/sample.png", space: use %20 */ @Nonnull @Override @@ -48,8 +48,11 @@ public FileSystemDriver createDriver(final URI uri, final Map env) th // currently only support "file" throw new IllegalArgumentException(file.toString()); } - // TODO virtual relative directory from rawSchemeSpecificParts[1] + if (!file.getRawSchemeSpecificPart().startsWith("/")) { + file = URI.create(file.getScheme() + ":" + System.getProperty("user.dir") + "/" + file.getRawSchemeSpecificPart()); + } + // TODO how about after '!' ? Archive archive = Archives.getArchive(Paths.get(file).toFile()); ArchiveFileStore fileStore = new ArchiveFileStore(factoryProvider.getAttributesFactory()); diff --git a/vavi-nio-file-archive/src/test/java/vavi/nio/file/archive/ArchiveFileSystemProviderTest.java b/vavi-nio-file-archive/src/test/java/vavi/nio/file/archive/ArchiveFileSystemProviderTest.java index b8af8f6e..5d3dedee 100644 --- a/vavi-nio-file-archive/src/test/java/vavi/nio/file/archive/ArchiveFileSystemProviderTest.java +++ b/vavi-nio-file-archive/src/test/java/vavi/nio/file/archive/ArchiveFileSystemProviderTest.java @@ -7,13 +7,18 @@ package vavi.nio.file.archive; import java.net.URI; +import java.net.URL; import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.Files; +import java.nio.file.Paths; import java.util.Collections; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable; +import org.junit.jupiter.api.condition.EnabledIf; + +import vavi.util.properties.annotation.Property; +import vavi.util.properties.annotation.PropsEntity; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -24,8 +29,17 @@ * @author Naohide Sano (umjammer) * @version 0.00 2019/06/23 umjammer initial version
*/ +@EnabledIf("localPropertiesExists") +@PropsEntity(url = "file:local.properties") class ArchiveFileSystemProviderTest { + static boolean localPropertiesExists() { + return Files.exists(Paths.get("local.properties")); + } + + @Property(name = "test.archive") + String file; + @Test void test() throws Exception { URI uri = URI.create("archive:file:/tmp/jar/exam.jar!/img/sample.png"); @@ -58,10 +72,10 @@ void test2() throws Exception { } @Test - @DisabledIfEnvironmentVariable(named = "GITHUB_WORKFLOW", matches = ".*") void test3() throws Exception { - URI uri = URI.create("archive:file:/Users/nsano/src/vavi/vavi-util-archive/src/test/resources/test.lzh"); - FileSystem fs = FileSystems.newFileSystem(uri, Collections.EMPTY_MAP); + URL url = ArchiveFileSystemProviderTest.class.getResource("/test.lzh"); + URI uri = URI.create("archive:file:" + url.getPath()); + FileSystem fs = FileSystems.newFileSystem(uri, Collections.emptyMap()); Files.list(fs.getRootDirectories().iterator().next()).forEach(System.err::println); } @@ -69,9 +83,10 @@ void test3() throws Exception { * @param args */ public static void main(String[] args) throws Exception { - String file = "/Users/nsano/Documents/Games/PC98/bacumed/88/ALPHO.D88"; - URI uri = URI.create("archive:file:" + file); - FileSystem fs = FileSystems.newFileSystem(uri, Collections.EMPTY_MAP); + ArchiveFileSystemProviderTest app = new ArchiveFileSystemProviderTest(); + PropsEntity.Util.bind(app); + URI uri = URI.create("archive:file:" + app.file); + FileSystem fs = FileSystems.newFileSystem(uri, Collections.emptyMap()); Files.list(fs.getRootDirectories().iterator().next()).forEach(System.err::println); } } diff --git a/vavi-nio-file-archive/src/test/java/vavi/nio/file/archive/Main4.java b/vavi-nio-file-archive/src/test/java/vavi/nio/file/archive/Main4.java new file mode 100644 index 00000000..22b7628c --- /dev/null +++ b/vavi-nio-file-archive/src/test/java/vavi/nio/file/archive/Main4.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2021 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package vavi.nio.file.archive; + +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.condition.EnabledIf; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import com.github.fge.filesystem.driver.CachedFileSystemDriver; + +import vavi.net.fuse.Base; +import vavi.net.fuse.Fuse; +import vavi.util.properties.annotation.Property; +import vavi.util.properties.annotation.PropsEntity; + + +/** + * Main4. (fuse) + * + * @author Naohide Sano (umjammer) + * @version 0.00 2021/11/24 umjammer initial version
+ */ +@EnabledIf("localPropertiesExists") +@PropsEntity(url = "file://${user.dir}/local.properties") +public class Main4 { + + static { + System.setProperty("vavi.util.logging.VaviFormatter.extraClassMethod", "co\\.paralleluniverse\\.fuse\\.LoggedFuseFilesystem#log"); + } + + static boolean localPropertiesExists() { + return Files.exists(Paths.get("local.properties")); + } + + @Property + String diskImage; + @Property + String mountPoint; + + FileSystem fs; + Map options; + + @BeforeEach + public void before() throws Exception { + PropsEntity.Util.bind(this); + + URI uri = URI.create("archive:file:" + diskImage); + + Map env = new HashMap<>(); + env.put(CachedFileSystemDriver.ENV_IGNORE_APPLE_DOUBLE, true); // mandatory + + fs = FileSystems.newFileSystem(uri, env); +//Files.list(fs.getRootDirectories().iterator().next()).forEach(System.err::println); + + options = new HashMap<>(); + options.put("fsname", "archive_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 + @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); + + Base.testFuse(fs, mountPoint, options); + + fs.close(); + } + + // + + public static void main(String[] args) throws Exception { +// System.setProperty("vavi.net.fuse.FuseProvider.class", "vavi.net.fuse.javafs.JavaFSFuseProvider"); +// System.setProperty("vavi.net.fuse.FuseProvider.class", "vavi.net.fuse.jnrfuse.JnrFuseFuseProvider"); + System.setProperty("vavi.net.fuse.FuseProvider.class", "vavi.net.fuse.fusejna.FuseJnaFuseProvider"); + + Main4 app = new Main4(); + app.before(); + + try (Fuse fuse = Fuse.getFuse()) { + fuse.mount(app.fs, app.mountPoint, app.options); +while (true) { // for jnrfuse + Thread.yield(); +} + } + } +} + +/* */ diff --git a/vavi-nio-file-archive/src/test/resources/test.lzh b/vavi-nio-file-archive/src/test/resources/test.lzh new file mode 100644 index 00000000..5e636913 Binary files /dev/null and b/vavi-nio-file-archive/src/test/resources/test.lzh differ diff --git a/vavi-nio-file-commons/pom.xml b/vavi-nio-file-commons/pom.xml new file mode 100644 index 00000000..c3f6e658 --- /dev/null +++ b/vavi-nio-file-commons/pom.xml @@ -0,0 +1,91 @@ + + + 4.0.0 + + + vavi-apps-fuse + vavi + 0.1.7 + + + vavi-nio-file-commons + + vavi-nio-file-commons + https://github.com/umjammer/vavi-nio-file-commons + + https://github.com/umjammer/vavi-nio-file-commons + + + https://github.com/umjammer/vavi-nio-file-commons/issues + + + + + + maven-compiler-plugin + + 1.8 + 1.8 + utf-8 + + + + org.apache.maven.plugins + maven-surefire-plugin + + -Djava.util.logging.config.file=${project.build.testOutputDirectory}/logging.properties + false + + + + + + + + jitpack.io + https://jitpack.io + + + + + + com.github.umjammer + vavi-commons + 1.1.6 + + + + + ${java7-fs-base.groupId} + java7-fs-base + + + + org.eclipse.jetty.websocket + javax-websocket-client-impl + 9.4.30.v20200611 + + + + com.google.code.gson + gson + 2.8.6 + + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.junit.platform + junit-platform-commons + test + + + diff --git a/vavi-nio-file-commons/src/main/java/vavi/nio/file/watch/webhook/Notification.java b/vavi-nio-file-commons/src/main/java/vavi/nio/file/watch/webhook/Notification.java new file mode 100644 index 00000000..de6e1d83 --- /dev/null +++ b/vavi-nio-file-commons/src/main/java/vavi/nio/file/watch/webhook/Notification.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2020 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package vavi.nio.file.watch.webhook; + +import java.io.Closeable; +import java.io.IOException; +import java.util.NoSuchElementException; +import java.util.ServiceLoader; +import java.util.function.Consumer; + + +/** + * Notification. + * + * @author Naohide Sano (umjammer) + * @version 0.00 2020/07/06 umjammer initial version
+ */ +public interface Notification extends Closeable { + + static ServiceLoader providers = ServiceLoader.load(NotificationProvider.class); + + /** + * @param matcher will be compared by {@link String#contains(CharSequence)} + */ + static Notification getNotification(String matcher, Consumer callback, Object... args) throws IOException { + for (NotificationProvider provider : providers) { + if (provider.getClass().getName().contains(matcher)) { + return provider.getNotification(callback, args); + } + } + throw new NoSuchElementException(matcher); + } +} + +/* */ diff --git a/vavi-nio-file-commons/src/main/java/vavi/nio/file/watch/webhook/NotificationProvider.java b/vavi-nio-file-commons/src/main/java/vavi/nio/file/watch/webhook/NotificationProvider.java new file mode 100644 index 00000000..e4a9aabb --- /dev/null +++ b/vavi-nio-file-commons/src/main/java/vavi/nio/file/watch/webhook/NotificationProvider.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2020 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package vavi.nio.file.watch.webhook; + +import java.io.IOException; +import java.util.function.Consumer; + + +/** + * NotificationProvider. + *

+ * for example, ngrok provider. + * + * @author Naohide Sano (umjammer) + * @version 0.00 2020/07/07 umjammer initial version
+ */ +public interface NotificationProvider { + + Notification getNotification(Consumer callback, Object... args) throws IOException; +} + +/* */ diff --git a/vavi-nio-file-commons/src/main/java/vavi/nio/file/watch/webhook/WebHookBaseWatchService.java b/vavi-nio-file-commons/src/main/java/vavi/nio/file/watch/webhook/WebHookBaseWatchService.java new file mode 100644 index 00000000..905a682a --- /dev/null +++ b/vavi-nio-file-commons/src/main/java/vavi/nio/file/watch/webhook/WebHookBaseWatchService.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2020 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package vavi.nio.file.watch.webhook; + +import java.io.IOException; +import java.nio.file.WatchEvent; +import java.nio.file.WatchEvent.Kind; +import java.nio.file.Watchable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.BiConsumer; + +import com.github.fge.filesystem.watch.AbstractWatchService; + +import vavi.util.Debug; + + +/** + * WebHookBaseWatchService. + * + * @author Naohide Sano (umjammer) + * @version 0.00 2020/07/06 umjammer initial version
+ */ +public abstract class WebHookBaseWatchService extends AbstractWatchService { + + /** */ + private static Map> notifications = new HashMap<>(); + + static { + Runtime.getRuntime().addShutdownHook(new Thread(WebHookBaseWatchService::dispose)); + } + + /** */ + protected static void setupNotification(WebHookBaseWatchService o, String matcher, Object... args) throws IOException { + if (!notifications.containsKey(matcher)) { + Notification notification = Notification.getNotification(matcher, n -> { + try { + o.onNotifyMessage(n); + } catch (IOException e) { + throw new IllegalStateException(e); + } + }, args); + notifications.put(matcher, notification); + } + } + + /** call {@link #listener}.accept */ + protected abstract void onNotifyMessage(T notification) throws IOException; + + /** for the user watch service */ + protected BiConsumer> listener = this::processNotification; + + /** for the system watch service */ + public void setNotificationListener(BiConsumer> listener) { + this.listener = listener; + } + + /** for watchkey */ + private void processNotification(String id, Kind kind) { + for (BasicWatchKey watchKey : watchKeys) { + if (watchKey.subscribesTo(kind)) { + watchKey.signal(); + } + } + } + + /** TODO */ + public static void dispose() { + notifications.values().forEach(notification -> { + try { + notification.close(); +Debug.println("NOIFICATION: notification closed: " + notification); + } catch (IOException e) { + e.printStackTrace(); + } + }); + notifications.clear(); + } + + /** */ + private List watchKeys = new ArrayList<>(); + + // design error? + @Override + public BasicWatchKey register(Watchable watchable, + Iterable> eventTypes, + WatchEvent.Modifier... modifiers) throws IOException { +// if (eventTypes == null) { +// throw new IllegalArgumentException("no eventTypes"); +// } + BasicWatchKey watchKey = super.register(watchable, eventTypes); + watchKeys.add(watchKey); + return watchKey; + } + + @Override + public void cancelled(BasicWatchKey watchKey) { + watchKeys.remove(watchKey); // TODO ??? + } +} diff --git a/vavi-nio-file-commons/src/main/java/vavi/nio/file/watch/webhook/websocket/BaseWebSocketNotification.java b/vavi-nio-file-commons/src/main/java/vavi/nio/file/watch/webhook/websocket/BaseWebSocketNotification.java new file mode 100644 index 00000000..4113c268 --- /dev/null +++ b/vavi-nio-file-commons/src/main/java/vavi/nio/file/watch/webhook/websocket/BaseWebSocketNotification.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2020 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package vavi.nio.file.watch.webhook.websocket; + +import java.io.IOException; +import java.net.URI; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.websocket.ContainerProvider; +import javax.websocket.DeploymentException; +import javax.websocket.OnError; +import javax.websocket.Session; +import javax.websocket.WebSocketContainer; + +import vavi.nio.file.watch.webhook.Notification; +import vavi.util.Debug; + + +/** + * BaseWebSocketNotification. + * + * @author Naohide Sano (umjammer) + * @version 0.00 2020/07/26 umjammer initial version
+ */ +public abstract class BaseWebSocketNotification implements Notification { + + /** */ + private WebSocketContainer container = ContainerProvider.getWebSocketContainer(); + + /** websocket uri */ + private URI uri; + /** */ + protected Session session; + /** */ + private AtomicBoolean reconnect = new AtomicBoolean(true); + /** */ + private Throwable throwable; + /** */ + protected Object[] args; + + /** */ + protected BaseWebSocketNotification(URI uri, Object... args) throws IOException { + this.uri = uri; + this.args = args; + try { + session = container.connectToServer(this, uri); + } catch (DeploymentException e) { + throw new IOException(e); + } + } + + /** */ + protected abstract void onOpenImpl(Session session) throws IOException; + + /** */ + protected abstract void onNotifyMessageImpl(T notification) throws IOException; + + @OnError + public final void onError(Throwable t) { +Debug.println("WEBSOCKET: onError"); +t.printStackTrace(); + throwable = t; + } + + /** */ + protected abstract void onCloseImpl(Session session) throws IOException; + + /** */ + protected final void onCloseInternal(Session session) throws IOException { +Debug.println("WEBSOCKET: onClose: " + session.getId()); + onCloseImpl(session); + + if (reconnect.get()) { + if (throwable == null) { + try { +Debug.println("WEBSOCKET: reconnect"); + this.session = container.connectToServer(this, uri); + } catch (DeploymentException e) { + throw new IOException(e); + } + } else { +Debug.println("WEBSOCKET: has error, exit"); + } + } + } + + @Override + public final void close() throws IOException { + if (reconnect.getAndSet(false)) { + session.close(); +Debug.println("WEBSOCKET: close: " + session.getId()); + + // TODO encapsulate into jsr356 + // https://stackoverflow.com/a/46472909/6102938 + if (container != null && container instanceof org.eclipse.jetty.util.component.LifeCycle) { + try { +Debug.println("WEBSOCKET: stopping jetty's websocket client"); + ((org.eclipse.jetty.util.component.LifeCycle) container).stop(); +Debug.println("WEBSOCKET: jetty's websocket stopped: " + ((org.eclipse.jetty.util.component.LifeCycle) container).isStopped()); + } catch (Exception e) { + throw new IOException(e); + } + } + } +//Debug.println("close: exit: " + reconnect.get() + ", " + this.hashCode()); + } +} + +/* */ diff --git a/vavi-nio-file-commons/src/main/java/vavi/nio/file/watch/webhook/websocket/BasicAuthorizationConfigurator.java b/vavi-nio-file-commons/src/main/java/vavi/nio/file/watch/webhook/websocket/BasicAuthorizationConfigurator.java new file mode 100644 index 00000000..9576017d --- /dev/null +++ b/vavi-nio-file-commons/src/main/java/vavi/nio/file/watch/webhook/websocket/BasicAuthorizationConfigurator.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2020 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package vavi.nio.file.watch.webhook.websocket; + +import java.util.Arrays; +import java.util.Base64; +import java.util.List; +import java.util.Map; + +import javax.websocket.ClientEndpointConfig; + + +/** + * BasicAuthorizationConfigurator. + *

+ * environment variables + *

    + *
  • VAVI_APPS_WEBHOOK_USERNAME + *
  • VAVI_APPS_WEBHOOK_PASSWORD + *
+ * + * @author Naohide Sano (umjammer) + * @version 0.00 2020/07/07 umjammer initial version
+ */ +public class BasicAuthorizationConfigurator extends ClientEndpointConfig.Configurator { + + private static final String username = System.getenv("VAVI_APPS_WEBHOOK_USERNAME"); + private static final String password = System.getenv("VAVI_APPS_WEBHOOK_PASSWORD"); + + @Override + public void beforeRequest(Map> headers) { + headers.put("Authorization", Arrays.asList("Basic " + Base64.getEncoder().encodeToString((username + ":" + password).getBytes()))); + } +} + +/* */ diff --git a/vavi-nio-file-commons/src/main/java/vavi/nio/file/watch/webhook/websocket/JsonCodec.java b/vavi-nio-file-commons/src/main/java/vavi/nio/file/watch/webhook/websocket/JsonCodec.java new file mode 100644 index 00000000..9cf89ab1 --- /dev/null +++ b/vavi-nio-file-commons/src/main/java/vavi/nio/file/watch/webhook/websocket/JsonCodec.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2020 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package vavi.nio.file.watch.webhook.websocket; + +import java.io.InputStream; + +import javax.websocket.DecodeException; +import javax.websocket.Decoder; +import javax.websocket.EncodeException; +import javax.websocket.Encoder; +import javax.websocket.EndpointConfig; + +import com.google.gson.ExclusionStrategy; +import com.google.gson.FieldAttributes; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonSyntaxException; + + +/** + * JsonCodec. + * + * @author Naohide Sano (umjammer) + * @version 0.00 2020/07/02 umjammer initial version
+ */ +public abstract class JsonCodec { + + protected static Gson gson = new GsonBuilder().addSerializationExclusionStrategy(new ExclusionStrategy() { + @Override + public boolean shouldSkipField(FieldAttributes f) { + return false; + } + @Override + public boolean shouldSkipClass(Class clazz) { + return InputStream.class.equals(clazz); + } + }).create(); + + protected static abstract class JsonEncoder implements Encoder.Text { + @Override + public void init(EndpointConfig config) { + } + + @Override + public String encode(T notification) throws EncodeException { + return gson.toJson(notification); + } + + @Override + public void destroy() { + } + } + + protected static abstract class JsonDecoder implements Decoder.Text { + + protected abstract Class getType(); + + @Override + public void init(EndpointConfig config) { + } + + @Override + public boolean willDecode(String s) { + try { + gson.fromJson(s, getType()); + return true; + } catch (JsonSyntaxException e) { + return false; + } + } + + @Override + public T decode(String s) throws DecodeException { + try { + return gson.fromJson(s, getType()); + } catch (JsonSyntaxException e) { + throw new DecodeException(s, e.getMessage(), e); + } + } + + @Override + public void destroy() { + } + } +} + +/* */ diff --git a/vavi-nio-file-commons/src/main/java/vavi/nio/file/watch/webhook/websocket/StringWebSocketNotification.java b/vavi-nio-file-commons/src/main/java/vavi/nio/file/watch/webhook/websocket/StringWebSocketNotification.java new file mode 100644 index 00000000..c9c3273a --- /dev/null +++ b/vavi-nio-file-commons/src/main/java/vavi/nio/file/watch/webhook/websocket/StringWebSocketNotification.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2020 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package vavi.nio.file.watch.webhook.websocket; + +import java.io.IOException; +import java.net.URI; + +import javax.websocket.OnMessage; +import javax.websocket.OnOpen; +import javax.websocket.Session; + +import vavi.util.Debug; + + +/** + * StringWebSocketNotification. + * + * https://stackoverflow.com/a/22090459/6102938 + * + * @author Naohide Sano (umjammer) + * @version 0.00 2020/07/26 umjammer initial version
+ */ +public abstract class StringWebSocketNotification extends BaseWebSocketNotification { + + /** */ + protected StringWebSocketNotification(URI uri, Object... args) throws IOException { + super(uri, args); + } + + @OnOpen + public final void onOpen(Session session) throws IOException { +Debug.println("WEBSOCKET: onOpen: " + session.getId()); + onOpenImpl(session); + } + + @OnMessage + public final void onNotifyMessage(String notification, Session session) throws IOException { + onNotifyMessageImpl(notification); + } +} + +/* */ diff --git a/vavi-nio-file-commons/src/main/java/vavi/nio/file/watch/webhook/websocket/WebSocketNotification.java b/vavi-nio-file-commons/src/main/java/vavi/nio/file/watch/webhook/websocket/WebSocketNotification.java new file mode 100644 index 00000000..56cc755c --- /dev/null +++ b/vavi-nio-file-commons/src/main/java/vavi/nio/file/watch/webhook/websocket/WebSocketNotification.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2020 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package vavi.nio.file.watch.webhook.websocket; + +import java.io.IOException; +import java.net.URI; + +import javax.websocket.OnClose; +import javax.websocket.OnMessage; +import javax.websocket.OnOpen; +import javax.websocket.Session; + +import vavi.util.Debug; + + +/** + * WebSocketNotification. + * + * @author Naohide Sano (umjammer) + * @version 0.00 2020/07/06 umjammer initial version
+ */ +public abstract class WebSocketNotification extends BaseWebSocketNotification { + + /** */ + protected WebSocketNotification(URI uri, Object... args) throws IOException { + super(uri, args); + } + + @OnOpen + public final void onOpen(Session session) throws IOException { +Debug.println("WEBSOCKET: onOpen: " + session.getId()); + onOpenImpl(session); + } + + @OnMessage + public final void onNotifyMessage(T notification, Session session) throws IOException { + onNotifyMessageImpl(notification); + } + + @OnClose + public final void onClose(Session session) throws IOException { + onCloseInternal(session); + } +} + +/* */ diff --git a/vavi-nio-file-commons/src/test/resources/logging.properties b/vavi-nio-file-commons/src/test/resources/logging.properties new file mode 100644 index 00000000..7d550e01 --- /dev/null +++ b/vavi-nio-file-commons/src/test/resources/logging.properties @@ -0,0 +1,14 @@ +handlers=java.util.logging.ConsoleHandler +.level=INFO +# Limit the message that are printed on the console to INFO and above. +java.util.logging.ConsoleHandler.level=INFO +#java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter +# %1:date/time %2:className and methodName %3logName %4:level %5:message %6:errormessage +#java.util.logging.SimpleFormatter.format=%1$tY/%1$tm/%1$td %1$tH:%1$tM:%1$tS.%1$tL [%4$s] %5$s %6$s%n + +# HttpUrlConnection +#java.util.logging.ConsoleHandler.level=FINEST +#sun.net.www.protocol.http.HttpURLConnection.level=ALL + +java.util.logging.ConsoleHandler.formatter=vavi.util.logging.VaviFormatter +#java.util.logging.ConsoleHandler.formatter=vavi.util.logging.BetterFormatter diff --git a/vavi-nio-file-flickr/pom.xml b/vavi-nio-file-flickr/pom.xml index ac8daae2..e1a48b79 100644 --- a/vavi-nio-file-flickr/pom.xml +++ b/vavi-nio-file-flickr/pom.xml @@ -5,7 +5,7 @@ vavi vavi-apps-fuse - 0.1.6 + 0.1.7 vavi-nio-file-flickr @@ -32,7 +32,7 @@ - com.github.umjammer.vavi-net-auth + ${vavi-net-auth.groupId} vavi-net-auth-flickr ${vavi-net-auth.version} diff --git a/vavi-nio-file-flickr/src/main/java/vavi/nio/file/flickr/FlickrBasicFileAttributesProvider.java b/vavi-nio-file-flickr/src/main/java/vavi/nio/file/flickr/FlickrBasicFileAttributesProvider.java index d4bef1cc..e0ab9977 100644 --- a/vavi-nio-file-flickr/src/main/java/vavi/nio/file/flickr/FlickrBasicFileAttributesProvider.java +++ b/vavi-nio-file-flickr/src/main/java/vavi/nio/file/flickr/FlickrBasicFileAttributesProvider.java @@ -86,7 +86,7 @@ public boolean isDirectory() { public long size() { // TODO image size try { - return entry.getOriginalSize().getWidth() * entry.getOriginalSize().getHeight() * 4; + return entry.getOriginalSize().getWidth() * entry.getOriginalSize().getHeight() * 4L; } catch (Exception e) { e.printStackTrace(); return 0; diff --git a/vavi-nio-file-flickr/src/main/java/vavi/nio/file/flickr/FlickrFileAttributesFactory.java b/vavi-nio-file-flickr/src/main/java/vavi/nio/file/flickr/FlickrFileAttributesFactory.java index 960fabe2..4983c7e4 100644 --- a/vavi-nio-file-flickr/src/main/java/vavi/nio/file/flickr/FlickrFileAttributesFactory.java +++ b/vavi-nio-file-flickr/src/main/java/vavi/nio/file/flickr/FlickrFileAttributesFactory.java @@ -7,7 +7,7 @@ package vavi.nio.file.flickr; import com.flickr4java.flickr.photos.Photo; -import com.github.fge.filesystem.attributes.FileAttributesFactory; +import com.github.fge.filesystem.driver.ExtendedFileSystemDriverBase.ExtendsdFileAttributesFactory; /** @@ -16,7 +16,7 @@ * @author Naohide Sano (umjammer) * @version 0.00 2016/03/30 umjammer initial version
*/ -public final class FlickrFileAttributesFactory extends FileAttributesFactory { +public final class FlickrFileAttributesFactory extends ExtendsdFileAttributesFactory { public FlickrFileAttributesFactory() { setMetadataClass(Photo.class); diff --git a/vavi-nio-file-flickr/src/main/java/vavi/nio/file/flickr/FlickrFileSystemDriver.java b/vavi-nio-file-flickr/src/main/java/vavi/nio/file/flickr/FlickrFileSystemDriver.java index 43074ee6..792acd67 100644 --- a/vavi-nio-file-flickr/src/main/java/vavi/nio/file/flickr/FlickrFileSystemDriver.java +++ b/vavi-nio-file-flickr/src/main/java/vavi/nio/file/flickr/FlickrFileSystemDriver.java @@ -9,36 +9,24 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.nio.file.AccessDeniedException; -import java.nio.file.AccessMode; import java.nio.file.CopyOption; -import java.nio.file.DirectoryStream; -import java.nio.file.FileAlreadyExistsException; import java.nio.file.FileStore; -import java.nio.file.NoSuchFileException; import java.nio.file.OpenOption; import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.attribute.FileAttribute; -import java.nio.file.spi.FileSystemProvider; -import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Map; import java.util.Set; -import javax.annotation.Nonnull; import javax.annotation.ParametersAreNonnullByDefault; import com.flickr4java.flickr.Flickr; import com.flickr4java.flickr.FlickrException; import com.flickr4java.flickr.photos.Photo; -import com.flickr4java.flickr.photos.PhotoList; import com.flickr4java.flickr.photos.SearchParameters; -import com.github.fge.filesystem.driver.ExtendedFileSystemDriverBase; +import com.github.fge.filesystem.driver.CachedFileSystemDriver; import com.github.fge.filesystem.provider.FileSystemFactoryProvider; -import vavi.nio.file.Cache; import vavi.nio.file.Util; @@ -49,233 +37,137 @@ * @version 0.00 2016/03/30 umjammer initial version
*/ @ParametersAreNonnullByDefault -public final class FlickrFileSystemDriver extends ExtendedFileSystemDriverBase { +public final class FlickrFileSystemDriver extends CachedFileSystemDriver { private final Flickr flickr; - private boolean ignoreAppleDouble = false; - - @SuppressWarnings("unchecked") public FlickrFileSystemDriver(final FileStore fileStore, final FileSystemFactoryProvider provider, final Flickr drive, final Map env) throws IOException { super(fileStore, provider); this.flickr = drive; - ignoreAppleDouble = (Boolean) ((Map) env).getOrDefault("ignoreAppleDouble", Boolean.FALSE); + setEnv(env); //System.err.println("ignoreAppleDouble: " + ignoreAppleDouble); } - /** */ - private Cache cache = new Cache() { - { - Photo photo = new Photo(); - photo.setTitle("/"); - photo.setId("root"); - photo.setLastUpdate(new Date(0)); // TODO - entryCache.put(Paths.get("/"), photo); // TODO filesystem - } + @Override + protected String getFilenameString(Photo entry) { + return entry.getTitle(); + } - /** - * TODO when the parent is not cached - * @see #ignoreAppleDouble - */ - public Photo getEntry(Path path) throws IOException { - String pathString = Util.toPathString(path); - if (cache.containsFile(path)) { -//System.err.println("CACHE: path: " + path + ", id: " + cache.get(pathString).getId()); - return cache.getFile(path); - } else { - if (ignoreAppleDouble && path.getFileName() != null && Util.isAppleDouble(path)) { - throw new NoSuchFileException("ignore apple double file: " + path); - } + @Override + protected boolean isFolder(Photo entry) { + // flickr doesn't have folder capability + return false; + } - try { - Photo entry = flickr.getPhotosInterface().getInfo(pathString, null); // TODO + @Override + protected Photo getRootEntry(Path root) throws IOException { + Photo photo = new Photo(); + photo.setTitle("/"); + photo.setId("root"); + photo.setLastUpdate(new Date(0)); // TODO + return photo; + } + + @Override + protected Photo getEntry(Photo parentEntry, Path path)throws IOException { + try { + Photo entry = flickr.getPhotosInterface().getInfo(Util.toPathString(path), null); // TODO //System.err.println("GOT: path: " + path + ", id: " + entry.getId()); - cache.putFile(path, entry); - return entry; - } catch (FlickrException e) { - if (e.getMessage().startsWith("404")) { // TODO - throw new NoSuchFileException(path.toString()); - } else { - throw new IOException(e); - } - } + return entry; + } catch (FlickrException e) { + if (e.getMessage().startsWith("404")) { // TODO + return null; + } else { + throw new IOException(e); } } - }; + } - @Nonnull @Override - public InputStream newInputStream(final Path path, final Set options) throws IOException { - final Photo entry = cache.getEntry(path); - + protected InputStream downloadEntry(Photo entry, Path path, Set options) throws IOException { final java.io.File downloadFile = java.io.File.createTempFile("vavi-apps-fuse-", ".download"); - return new FlickrInputStream(flickr, entry, downloadFile); } - @Nonnull @Override - public OutputStream newOutputStream(final Path path, final Set options) throws IOException { - try { - cache.getEntry(path); - - throw new FileAlreadyExistsException("path: " + path); - } catch (IOException e) { - System.err.println("newOutputStream: " + e.getMessage()); - } - + protected OutputStream uploadEntry(Photo parentEntry, Path path, Set options) throws IOException { java.io.File temp = java.io.File.createTempFile("vavi-apps-fuse-", ".upload"); - return new FlickrOutputStream(flickr, temp, Util.toFilenameString(path), newEntry -> { try { System.out.println("file: " + newEntry.getTitle() + ", " + newEntry.getDateAdded()); - cache.addEntry(path, newEntry); + updateEntry(path, newEntry); } catch (Exception e) { throw new IllegalStateException(e); } }); } - @Nonnull @Override - public DirectoryStream newDirectoryStream(final Path dir, - final DirectoryStream.Filter filter) throws IOException { + protected List getDirectoryEntries(Photo dirEntry, Path dir) throws IOException { try { - return Util.newDirectoryStream(getDirectoryEntries(dir), filter); + SearchParameters params = new SearchParameters(); + Date now = new Date(); + params.setMaxTakenDate(now); + params.setMinTakenDate(now); + return flickr.getPhotosInterface().search(params, 10, 0); } catch (FlickrException e) { throw new IOException(e); } } @Override - public void createDirectory(final Path dir, final FileAttribute... attrs) throws IOException { - throw new UnsupportedOperationException("createDirectory"); + protected Photo createDirectoryEntry(Photo parentEntry, Path dir) throws IOException { + throw new UnsupportedOperationException("flickr doesn't have folder capability"); } @Override - public void delete(final Path path) throws IOException { - final Photo entry = cache.getEntry(path); - - try { - flickr.getPhotosInterface().delete(entry.getId()); - - cache.removeEntry(path); - } catch (FlickrException e) { - throw new IOException(e); - } + protected boolean hasChildren(Photo dirEntry, Path dir) throws IOException { + // flickr doesn't have folder capability + return false; } @Override - public void copy(final Path source, final Path target, final Set options) throws IOException { - Photo targetEntry; - String targetFilename; + protected void removeEntry(Photo entry, Path path) throws IOException { try { - targetEntry = cache.getEntry(target); - flickr.getPhotosInterface().delete(targetEntry.getId()); - - cache.removeEntry(target); - - targetEntry = cache.getEntry(target.getParent()); - targetFilename = Util.toFilenameString(target); + flickr.getPhotosInterface().delete(entry.getId()); } catch (FlickrException e) { -System.err.println(e); throw new IOException(e); } - - final Photo sourceEntry = cache.getEntry(source); - Photo entry = new Photo(); - entry.setTitle(targetFilename); - // TODO - Photo newEntry = null; //flickr.getPhotosInterface().copy(sourceEntry.getId(), entry); - - cache.addEntry(target, newEntry); } @Override - public void move(final Path source, final Path target, final Set options) throws IOException { - Photo targetEntry; - String targetFilename; - try { - targetEntry = cache.getEntry(target); - flickr.getPhotosInterface().delete(targetEntry.getId()); - - cache.removeEntry(target); - - targetEntry = cache.getEntry(target.getParent()); - targetFilename = Util.toFilenameString(target); - } catch (FlickrException e) { -System.err.println(e); - throw new IOException(e); - } - - Photo sourceEntry = cache.getEntry(source); - sourceEntry.setTitle(targetFilename); - // TODO - Photo newEntry = null;//flickr.getPeopleInterface().update(sourceEntry.getId(), sourceEntry); - - cache.removeEntry(source); - cache.addEntry(target, newEntry); + protected Photo copyEntry(Photo sourceEntry, Photo targetParentEntry, Path source, Path target, Set options) throws IOException { +// try { + Photo entry = new Photo(); + entry.setTitle(Util.toFilenameString(target)); + // TODO + return null; //flickr.getPhotosInterface().copy(sourceEntry.getId(), entry); +// } catch (FlickrException e) { +// throw new IOException(e); +// } } - /** - * Check access modes for a path on this filesystem - *

- * If no modes are provided to check for, this simply checks for the - * existence of the path. - *

- * - * @param path the path to check - * @param modes the modes to check for, if any - * @throws IOException filesystem level error, or a plain I/O error - * if you use this with javafs (jnr-fuse), you should throw {@link NoSuchFileException} when the file not found. - * @see FileSystemProvider#checkAccess(Path, AccessMode...) - */ @Override - protected void checkAccessImpl(final Path path, final AccessMode... modes) throws IOException { - cache.getEntry(path); - - // TODO: assumed; not a file == directory - for (final AccessMode mode : modes) { - if (mode == AccessMode.EXECUTE) { - throw new AccessDeniedException(path.toString()); - } - } + protected Photo moveEntry(Photo sourceEntry, Photo targetParentEntry, Path source, Path target, boolean targetIsParent) throws IOException { +// try { + sourceEntry.setTitle(Util.toFilenameString(target)); + // TODO + return null; //flickr.getPeopleInterface().update(sourceEntry.getId(), sourceEntry); +// } catch (FlickrException e) { +// throw new IOException(e); +// } } @Override - public void close() throws IOException { - // TODO: what to do here? Flickr does not implement Closeable :( + protected Photo moveFolderEntry(Photo sourceEntry, Photo targetParentEntry, Path source, Path target, boolean targetIsParent) throws IOException { + throw new UnsupportedOperationException("flickr doesn't have folder capability"); } - /** - * @throws IOException if you use this with javafs (jnr-fuse), you should throw {@link NoSuchFileException} when the file not found. - */ - @Nonnull @Override - protected Object getPathMetadataImpl(final Path path) throws IOException { - return cache.getEntry(path); - } - - /** */ - private List getDirectoryEntries(final Path dir) throws IOException, FlickrException { - List list = null; - SearchParameters params = new SearchParameters(); - params.setMaxTakenDate(new Date()); - params.setMinTakenDate(new Date()); - final PhotoList children = flickr.getPhotosInterface().search(params, 10, 0); - list = new ArrayList<>(children.size()); - - // TODO nextPageToken - for (final Photo child : children) { - Path childPath = dir.resolve(child.getTitle()); - list.add(childPath); -//System.err.println("child: " + childPath.toRealPath().toString()); - cache.addEntry(childPath, child); - } - - return list; + protected Photo renameEntry(Photo sourceEntry, Photo targetParentEntry, Path source, Path target) throws IOException { + throw new UnsupportedOperationException("flickr doesn't have folder capability"); } } diff --git a/vavi-nio-file-gathered/pom.xml b/vavi-nio-file-gathered/pom.xml index 42de1a6d..592ba963 100644 --- a/vavi-nio-file-gathered/pom.xml +++ b/vavi-nio-file-gathered/pom.xml @@ -5,7 +5,7 @@ vavi vavi-apps-fuse - 0.1.6 + 0.1.7 vavi-nio-file-gathered @@ -19,6 +19,13 @@ https://github.com/umjammer/vavi-nio-file-gathered/issues + + + jitpack.io + https://jitpack.io + + + @@ -38,7 +45,7 @@ com.google.guava guava - 29.0-jre + 31.0.1-jre @@ -59,7 +66,6 @@ org.junit.jupiter junit-jupiter-params - 5.3.0 test @@ -89,13 +95,13 @@ vavi vavi-nio-file-googledrive - 0.1.6 + 0.1.7 test vavi vavi-nio-file-onedrive4 - 0.1.6 + 0.1.7 test diff --git a/vavi-nio-file-gathered/src/main/java/vavi/nio/file/gathered/GatheredFileAttributesFactory.java b/vavi-nio-file-gathered/src/main/java/vavi/nio/file/gathered/GatheredFileAttributesFactory.java index b2ff0b71..dde9857e 100644 --- a/vavi-nio-file-gathered/src/main/java/vavi/nio/file/gathered/GatheredFileAttributesFactory.java +++ b/vavi-nio-file-gathered/src/main/java/vavi/nio/file/gathered/GatheredFileAttributesFactory.java @@ -6,7 +6,7 @@ package vavi.nio.file.gathered; -import com.github.fge.filesystem.attributes.FileAttributesFactory; +import com.github.fge.filesystem.driver.ExtendedFileSystemDriverBase.ExtendsdFileAttributesFactory; /** @@ -15,7 +15,7 @@ * @author Naohide Sano (umjammer) * @version 0.00 2016/04/06 umjammer initial version
*/ -public final class GatheredFileAttributesFactory extends FileAttributesFactory { +public final class GatheredFileAttributesFactory extends ExtendsdFileAttributesFactory { public GatheredFileAttributesFactory() { setMetadataClass(Object.class); diff --git a/vavi-nio-file-gathered/src/main/java/vavi/nio/file/gathered/GatheredFileSystemDriver.java b/vavi-nio-file-gathered/src/main/java/vavi/nio/file/gathered/GatheredFileSystemDriver.java index 8501ced9..3b037abe 100644 --- a/vavi-nio-file-gathered/src/main/java/vavi/nio/file/gathered/GatheredFileSystemDriver.java +++ b/vavi-nio-file-gathered/src/main/java/vavi/nio/file/gathered/GatheredFileSystemDriver.java @@ -19,16 +19,12 @@ import java.nio.file.OpenOption; import java.nio.file.Path; import java.nio.file.attribute.FileAttribute; -import java.nio.file.spi.FileSystemProvider; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; -import javax.annotation.Nonnull; -import javax.annotation.ParametersAreNonnullByDefault; - import com.github.fge.filesystem.driver.ExtendedFileSystemDriverBase; import com.github.fge.filesystem.exceptions.IsDirectoryException; import com.github.fge.filesystem.provider.FileSystemFactoryProvider; @@ -44,7 +40,6 @@ * @author Naohide Sano (umjammer) * @version 0.00 2019/03/30 umjammer initial version
*/ -@ParametersAreNonnullByDefault public final class GatheredFileSystemDriver extends ExtendedFileSystemDriverBase { /** should be unaccessible from outer */ @@ -66,7 +61,6 @@ public GatheredFileSystemDriver(final FileStore fileStore, } } - @Nonnull @Override public InputStream newInputStream(final Path path, final Set options) throws IOException { final Object entry = getPathMetadata(path); @@ -79,14 +73,12 @@ public InputStream newInputStream(final Path path, final Set options) throws IOException { // TODO we can implement using Files throw new UnsupportedOperationException("newOutputStream is not supported by the file system"); } - @Nonnull @Override public DirectoryStream newDirectoryStream(final Path dir, final DirectoryStream.Filter filter) throws IOException { @@ -117,31 +109,14 @@ public void move(final Path source, final Path target, final Set opt throw new UnsupportedOperationException("move is not supported by the file system"); } - /** - * Check access modes for a path on this filesystem - *

- * If no modes are provided to check for, this simply checks for the - * existence of the path. - *

- * - * @param path the path to check - * @param modes the modes to check for, if any - * @see FileSystemProvider#checkAccess(Path, AccessMode...) - */ @Override protected void checkAccessImpl(final Path path, final AccessMode... modes) throws IOException { // TODO currently check read only? } - @Override - public void close() throws IOException { - // TODO: what to do here? gathered fs does not implement Closeable :( - } - /** * @throws IOException you should throw {@link NoSuchFileException} when the file not found. */ - @Nonnull @Override protected Object getPathMetadataImpl(final Path path) throws IOException { //Debug.println("path: " + path); @@ -152,6 +127,11 @@ protected Object getPathMetadataImpl(final Path path) throws IOException { } } + @Override + public void close() throws IOException { + // TODO: what to do here? gathered fs does not implement Closeable :( + } + /** */ private List getDirectoryEntries(final Path dir) throws IOException { if (dir.getNameCount() == 0) { diff --git a/vavi-nio-file-gathered/src/test/java/vavi/nio/file/gathered/GatheredFileSystemProviderTest.java b/vavi-nio-file-gathered/src/test/java/vavi/nio/file/gathered/GatheredFileSystemProviderTest.java index 1ffcf2c7..23b39652 100644 --- a/vavi-nio-file-gathered/src/test/java/vavi/nio/file/gathered/GatheredFileSystemProviderTest.java +++ b/vavi-nio-file-gathered/src/test/java/vavi/nio/file/gathered/GatheredFileSystemProviderTest.java @@ -26,7 +26,7 @@ import vavi.net.auth.oauth2.OAuth2AppCredential; import vavi.net.auth.oauth2.box.BoxLocalAppCredential; import vavi.net.auth.oauth2.dropbox.DropBoxLocalAppCredential; -import vavi.net.auth.oauth2.google.GoogleLocalAppCredential; +import vavi.net.auth.oauth2.google.GoogleLocalOAuth2AppCredential; import vavi.net.auth.oauth2.microsoft.MicrosoftGraphLocalAppCredential; import vavi.net.fuse.Fuse; import vavi.nio.file.googledrive.GoogleDriveFileSystemProvider; @@ -86,7 +86,7 @@ public static void main(String[] args) throws Exception { /** */ private OAuth2AppCredential microsoftAppCredential = new MicrosoftGraphLocalAppCredential(); /** */ - private OAuth2AppCredential googleAppCredential = new GoogleLocalAppCredential(); + private OAuth2AppCredential googleAppCredential = new GoogleLocalOAuth2AppCredential("googledrive"); /** */ private OAuth2AppCredential boxAppCredential = new BoxLocalAppCredential(); /** */ diff --git a/vavi-nio-file-gathered/src/test/resources/onedrive.properties b/vavi-nio-file-gathered/src/test/resources/onedrive.properties new file mode 100644 index 00000000..3f300320 --- /dev/null +++ b/vavi-nio-file-gathered/src/test/resources/onedrive.properties @@ -0,0 +1 @@ +authenticatorClassName=vavi.net.auth.oauth2.microsoft.MicrosoftBasicAuthenticator diff --git a/vavi-nio-file-googledrive/pom.xml b/vavi-nio-file-googledrive/pom.xml index 2dd2a16d..cdaf00e5 100644 --- a/vavi-nio-file-googledrive/pom.xml +++ b/vavi-nio-file-googledrive/pom.xml @@ -5,7 +5,7 @@ vavi vavi-apps-fuse - 0.1.6 + 0.1.7 vavi-nio-file-googledrive @@ -54,7 +54,6 @@ org.apache.maven.plugins maven-surefire-plugin - 2.22.2 ${test4.account} @@ -69,6 +68,27 @@ + + + + maven-compiler-plugin + + 1.8 + 1.8 + utf-8 + eclipse + + + + org.codehaus.plexus + plexus-compiler-eclipse + 2.7 + + + + + + @@ -82,11 +102,22 @@ - com.github.umjammer.vavi-net-auth + vavi + vavi-nio-file-commons + + + + ${vavi-net-auth.groupId} vavi-net-auth-google ${vavi-net-auth.version} + + com.google.apis + google-api-services-drive + v3-rev20211017-1.32.1 + + org.junit.jupiter junit-jupiter-api @@ -105,7 +136,6 @@ org.junit.jupiter junit-jupiter-params - 5.3.0 test diff --git a/vavi-nio-file-googledrive/src/main/java/vavi/nio/file/googledrive/GoogleDriveBasicFileAttributesProvider.java b/vavi-nio-file-googledrive/src/main/java/vavi/nio/file/googledrive/GoogleDriveBasicFileAttributesProvider.java index 42ef2ee9..cb593c3e 100644 --- a/vavi-nio-file-googledrive/src/main/java/vavi/nio/file/googledrive/GoogleDriveBasicFileAttributesProvider.java +++ b/vavi-nio-file-googledrive/src/main/java/vavi/nio/file/googledrive/GoogleDriveBasicFileAttributesProvider.java @@ -22,6 +22,8 @@ import com.github.fge.filesystem.attributes.provider.BasicFileAttributesProvider; import com.google.api.services.drive.model.File; +import vavi.nio.file.googledrive.GoogleDriveFileAttributesFactory.Metadata; + /** * {@link BasicFileAttributes} implementation for GoogleDrive @@ -35,10 +37,12 @@ */ public final class GoogleDriveBasicFileAttributesProvider extends BasicFileAttributesProvider implements PosixFileAttributes { + private final GoogleDriveFileSystemDriver driver; private final File entry; - public GoogleDriveBasicFileAttributesProvider(@Nonnull final File entry) throws IOException { - this.entry = Objects.requireNonNull(entry); + public GoogleDriveBasicFileAttributesProvider(@Nonnull final Metadata entry) throws IOException { + this.driver = Objects.requireNonNull(entry).driver; + this.entry = Objects.requireNonNull(entry).file; } /** @@ -62,7 +66,7 @@ public FileTime lastModifiedTime() { */ @Override public boolean isRegularFile() { - return !GoogleDriveFileSystemDriver.isFolder(entry); + return !driver.isFolder(entry); } /** @@ -70,7 +74,7 @@ public boolean isRegularFile() { */ @Override public boolean isDirectory() { - return GoogleDriveFileSystemDriver.isFolder(entry); + return driver.isFolder(entry); } /** diff --git a/vavi-nio-file-googledrive/src/main/java/vavi/nio/file/googledrive/GoogleDriveCopyOption.java b/vavi-nio-file-googledrive/src/main/java/vavi/nio/file/googledrive/GoogleDriveCopyOption.java index cd796a5d..ea41bbe2 100644 --- a/vavi-nio-file-googledrive/src/main/java/vavi/nio/file/googledrive/GoogleDriveCopyOption.java +++ b/vavi-nio-file-googledrive/src/main/java/vavi/nio/file/googledrive/GoogleDriveCopyOption.java @@ -17,6 +17,9 @@ */ public enum GoogleDriveCopyOption implements CopyOption { + /** + * you can use for OCR image files. + */ EXPORT_AS_GDOCS("application/vnd.google-apps.document"); /** */ diff --git a/vavi-nio-file-googledrive/src/main/java/vavi/nio/file/googledrive/GoogleDriveFileAttributesFactory.java b/vavi-nio-file-googledrive/src/main/java/vavi/nio/file/googledrive/GoogleDriveFileAttributesFactory.java index e7ad527b..77447353 100644 --- a/vavi-nio-file-googledrive/src/main/java/vavi/nio/file/googledrive/GoogleDriveFileAttributesFactory.java +++ b/vavi-nio-file-googledrive/src/main/java/vavi/nio/file/googledrive/GoogleDriveFileAttributesFactory.java @@ -6,7 +6,7 @@ package vavi.nio.file.googledrive; -import com.github.fge.filesystem.attributes.FileAttributesFactory; +import com.github.fge.filesystem.driver.ExtendedFileSystemDriverBase.ExtendsdFileAttributesFactory; import com.google.api.services.drive.model.File; @@ -16,10 +16,20 @@ * @author Naohide Sano (umjammer) * @version 0.00 2016/03/30 umjammer initial version
*/ -public final class GoogleDriveFileAttributesFactory extends FileAttributesFactory { +public final class GoogleDriveFileAttributesFactory extends ExtendsdFileAttributesFactory { + + static class Metadata { + GoogleDriveFileSystemDriver driver; + File file; + Metadata(GoogleDriveFileSystemDriver driver, File file) { + this.driver = driver; + this.file = file; + } + } public GoogleDriveFileAttributesFactory() { - setMetadataClass(File.class); + setMetadataClass(Metadata.class); addImplementation("basic", GoogleDriveBasicFileAttributesProvider.class); + addImplementation("user", GoogleDriveUserDefinedFileAttributesProvider.class); } } diff --git a/vavi-nio-file-googledrive/src/main/java/vavi/nio/file/googledrive/GoogleDriveFileSystemDriver.java b/vavi-nio-file-googledrive/src/main/java/vavi/nio/file/googledrive/GoogleDriveFileSystemDriver.java index 1f99dd93..c501b293 100644 --- a/vavi-nio-file-googledrive/src/main/java/vavi/nio/file/googledrive/GoogleDriveFileSystemDriver.java +++ b/vavi-nio-file-googledrive/src/main/java/vavi/nio/file/googledrive/GoogleDriveFileSystemDriver.java @@ -10,45 +10,42 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.nio.file.AccessDeniedException; -import java.nio.file.AccessMode; import java.nio.file.CopyOption; -import java.nio.file.DirectoryNotEmptyException; -import java.nio.file.DirectoryStream; -import java.nio.file.FileAlreadyExistsException; import java.nio.file.FileStore; -import java.nio.file.NoSuchFileException; -import java.nio.file.NotDirectoryException; +import java.nio.file.Files; import java.nio.file.OpenOption; import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.nio.file.attribute.FileAttribute; -import java.nio.file.spi.FileSystemProvider; +import java.nio.file.WatchEvent.Kind; +import java.nio.file.WatchService; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.NoSuchElementException; import java.util.Set; +import java.util.logging.Level; -import javax.annotation.Nonnull; -import javax.annotation.ParametersAreNonnullByDefault; - -import com.github.fge.filesystem.driver.ExtendedFileSystemDriverBase; +import com.github.fge.filesystem.driver.CachedFileSystemDriver; import com.github.fge.filesystem.exceptions.IsDirectoryException; import com.github.fge.filesystem.provider.FileSystemFactoryProvider; import com.google.api.client.googleapis.json.GoogleJsonResponseException; import com.google.api.client.googleapis.media.MediaHttpUploader; import com.google.api.client.http.AbstractInputStreamContent; +import com.google.api.client.http.InputStreamContent; import com.google.api.services.drive.Drive; -import com.google.api.services.drive.Drive.Files; import com.google.api.services.drive.model.File; import com.google.api.services.drive.model.FileList; +import com.google.api.services.drive.model.Revision; +import com.google.api.services.drive.model.RevisionList; -import vavi.nio.file.Cache; import vavi.nio.file.Util; +import vavi.nio.file.googledrive.GoogleDriveFileAttributesFactory.Metadata; import vavi.util.Debug; +import vavi.util.StringUtil; +import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE; import static vavi.nio.file.Util.toFilenameString; +import static vavi.nio.file.googledrive.GoogleDriveFileSystemProvider.ENV_USE_SYSTEM_WATCHER; /** @@ -57,123 +54,144 @@ * @author Naohide Sano (umjammer) * @version 0.00 2016/03/30 umjammer initial version
*/ -@ParametersAreNonnullByDefault -public final class GoogleDriveFileSystemDriver extends ExtendedFileSystemDriverBase { +public final class GoogleDriveFileSystemDriver extends CachedFileSystemDriver { - private final Drive drive; + private Drive drive; - private boolean ignoreAppleDouble = false; + private GoogleDriveWatchService systemWatcher; @SuppressWarnings("unchecked") - public GoogleDriveFileSystemDriver(final FileStore fileStore, - final FileSystemFactoryProvider provider, - final Drive drive, - final Map env) throws IOException { + public GoogleDriveFileSystemDriver(FileStore fileStore, + FileSystemFactoryProvider provider, + Drive drive, + Map env) throws IOException { super(fileStore, provider); this.drive = drive; - ignoreAppleDouble = (Boolean) ((Map) env).getOrDefault("ignoreAppleDouble", Boolean.FALSE); + setEnv(env); + boolean useSystemWatcher = (Boolean) ((Map) env).getOrDefault(ENV_USE_SYSTEM_WATCHER, false); + + if (useSystemWatcher) { + systemWatcher = new GoogleDriveWatchService(drive); + systemWatcher.setNotificationListener(this::processNotification); + } + } + + /** for system watcher */ + private void processNotification(String id, Kind kind) { + if (ENTRY_DELETE == kind) { + try { + Path path = cache.getEntry(e -> id.equals(e.getId())); + cache.removeEntry(path); + } catch (NoSuchElementException e) { +Debug.println("NOTIFICATION: already deleted: " + id); + } + } else { + try { + try { + Path path = cache.getEntry(e -> id.equals(e.getId())); +Debug.println("NOTIFICATION: maybe updated: " + path); + cache.removeEntry(path); + cache.getEntry(path); + } catch (NoSuchElementException e) { + File entry = drive.files().get(id).execute(); + Path parent = cache.getEntry(f -> entry.getParents().get(0).equals(f.getId())); + Path path = parent.resolve(entry.getName()); +Debug.println("NOTIFICATION: maybe created: " + path); + cache.addEntry(path, entry); + } + } catch (NoSuchElementException e) { +Debug.println("NOTIFICATION: parent not found: " + e); + } catch (IOException e) { + e.printStackTrace(); + } + } } /** */ - private static final String ENTRY_FIELDS = "id, parents, name, size, mimeType, createdTime, modifiedTime"; + private static final String ENTRY_FIELDS = "id, parents, name, size, mimeType, createdTime, modifiedTime, description"; /** */ - private static final String MIME_TYPE_DIR = "application/vnd.google-apps.folder"; + public static final String MIME_TYPE_DIR = "application/vnd.google-apps.folder"; - /** ugly */ - static boolean isFolder(File file) { - return file.getMimeType().equals(MIME_TYPE_DIR); + @Override + protected String getFilenameString(File entry) { + try { + return Util.toNormalizedString(entry.getName()); + } catch (IOException e) { + throw new IllegalStateException(e); + } } - /** */ - private Cache cache = new Cache() { - /** - * @see #ignoreAppleDouble - */ - public File getEntry(Path path) throws IOException { - try { - if (cache.containsFile(path)) { - return cache.getFile(path); - } else { - if (ignoreAppleDouble && path.getFileName() != null && Util.isAppleDouble(path)) { - throw new NoSuchFileException("ignore apple double file: " + path); - } + @Override + protected boolean isFolder(File entry) { + return MIME_TYPE_DIR.equals(entry.getMimeType()); + } - File entry; - if (path.getNameCount() == 0) { - entry = drive.files().get("root").setFields(ENTRY_FIELDS).execute().set("name", "/"); - cache.putFile(path, entry); - return entry; - } else { - List siblings = getDirectoryEntries(path.getParent(), false); - for (int i = 0; i < siblings.size(); i++) { // avoid ConcurrentModificationException - Path p = siblings.get(i); - if (p.getFileName().equals(path.getFileName())) { - return cache.getEntry(p); - } - } - throw new NoSuchFileException(path.toString()); - } - } - } catch (GoogleJsonResponseException e) { - if (e.getMessage().startsWith("404")) { - // cache - if (cache.containsFile(path)) { - cache.removeEntry(path); - } + @Override + protected File getRootEntry(Path root) throws IOException { + return drive.files().get("root").setFields(ENTRY_FIELDS).execute().set("name", "/"); + } - throw (IOException) new NoSuchFileException("path: " + path).initCause(e); - } else { - throw e; - } + @Override + protected File getEntry(File parentEntry, Path path) throws IOException { + try { + String q = "'" + parentEntry.getId() + "' in parents and name = '" + path.getFileName() + "' and trashed=false"; +//System.out.println("q: " + q); + FileList files = drive.files().list() + .setQ(q) + .setSpaces("drive") + .setFields("nextPageToken, files(" + ENTRY_FIELDS + ")") + .execute(); + if (files.getFiles().size() > 0) { + return files.getFiles().get(0); + } else { + return null; + } + } catch (GoogleJsonResponseException e) { + if (e.getMessage().startsWith("404")) { + return null; + } else { + throw e; } } - }; + } - @Nonnull @Override - public InputStream newInputStream(final Path path, final Set options) throws IOException { - final File entry = cache.getEntry(path); - - if (isFolder(entry)) { - throw new IsDirectoryException("path: " + path); - } - - // TODO detect automatically? - GoogleDriveOpenOption option = Util.getOneOfOptions(GoogleDriveOpenOption.class, options); - if (option != null) { + protected InputStream downloadEntry(File entry, Path path, Set options) throws IOException { + // TODO detect automatically? (w/o options) + if (options != null && options.stream().anyMatch(o -> o.equals(GoogleDriveOpenOption.EXPORT_WITH_GDOCS_DOCX) || + o.equals(GoogleDriveOpenOption.EXPORT_WITH_GDOCS_XLSX))) { + GoogleDriveOpenOption option = Util.getOneOfOptions(GoogleDriveOpenOption.class, options); return drive.files().export(entry.getId(), option.getValue()).executeMediaAsInputStream(); } else { return drive.files().get(entry.getId()).executeMediaAsInputStream(); } } - @Nonnull @Override - public OutputStream newOutputStream(final Path path, final Set options) throws IOException { - final File entry; - try { - entry = cache.getEntry(path); - - if (isFolder(entry)) { - throw new IsDirectoryException("path: " + path); - } else { - throw new FileAlreadyExistsException("path: " + path); - } - } catch (NoSuchFileException e) { -Debug.println("newOutputStream: " + e.getMessage()); + protected void whenUploadEntryExists(File sourceEntry, Path path, Set options) throws IOException { + if (options == null || !options.stream().anyMatch(o -> o.equals(GoogleDriveOpenOption.INPORT_AS_NEW_REVISION))) { + super.whenUploadEntryExists(sourceEntry, path, options); } - // TODO detect automatically? - @SuppressWarnings("unused") - GoogleDriveOpenOption option = Util.getOneOfOptions(GoogleDriveOpenOption.class, options); + File entry = new File(); - return uploadEntry(path); + AbstractInputStreamContent mediaContent = new InputStreamContent(null, Files.newInputStream(path)); + Drive.Files.Update updator = drive.files().update(sourceEntry.getId(), entry, mediaContent); + MediaHttpUploader uploader = updator.getMediaHttpUploader(); + uploader.setDirectUploadEnabled(true); + // MediaHttpUploader#getProgress() cannot use because w/o content length, using #getNumBytesUploaded() instead + uploader.setProgressListener(u -> { Debug.println("new revision progress: " + u.getNumBytesUploaded() + ", " + u.getUploadState()); }); + updator.getMediaHttpUploader(); + File newEntry = updator.setFields(ENTRY_FIELDS).execute(); +Debug.printf("file: %1$s, %2$tF %2$tT.%2$tL, %3$d\n", newEntry.getName(), newEntry.getCreatedTime().getValue(), newEntry.getSize()); + updateEntry(path, newEntry); } - /** */ - private OutputStream uploadEntry(Path path) throws IOException { - return new BufferedOutputStream(new Util.StrealingOutputStreamForUploading() { + @Override + protected OutputStream uploadEntry(File parentEntry, Path path, Set options) throws IOException { + + return new BufferedOutputStream(new Util.StealingOutputStreamForUploading() { @Override protected File upload() throws IOException { AbstractInputStreamContent mediaContent = new AbstractInputStreamContent(null) { // implements HttpContent @@ -195,11 +213,11 @@ public void writeTo(OutputStream os) throws IOException { } }; - File fileMetadata = new File(); - fileMetadata.setName(toFilenameString(path)); - fileMetadata.setParents(Arrays.asList(cache.getEntry(path.getParent()).getId())); + File entry = new File(); + entry.setName(toFilenameString(path)); + entry.setParents(Arrays.asList(parentEntry.getId())); - Drive.Files.Create creator = drive.files().create(fileMetadata, mediaContent); // why not HttpContent ??? + Drive.Files.Create creator = drive.files().create(entry, mediaContent); // why not HttpContent ??? MediaHttpUploader uploader = creator.getMediaHttpUploader(); uploader.setDirectUploadEnabled(true); // MediaHttpUploader#getProgress() cannot use because w/o content length, using #getNumBytesUploaded() instead @@ -210,246 +228,198 @@ public void writeTo(OutputStream os) throws IOException { @Override protected void onClosed(File newEntry) { Debug.printf("file: %1$s, %2$tF %2$tT.%2$tL, %3$d\n", newEntry.getName(), newEntry.getCreatedTime().getValue(), newEntry.getSize()); - cache.addEntry(path, newEntry); + updateEntry(path, newEntry); } }, Util.BUFFER_SIZE); } - @Nonnull @Override - public DirectoryStream newDirectoryStream(final Path dir, - final DirectoryStream.Filter filter) throws IOException { - return Util.newDirectoryStream(getDirectoryEntries(dir, true), filter); + protected List getDirectoryEntries(File dirEntry, Path dir) throws IOException { + List list = new ArrayList<>(); + String pageToken = null; + do { + FileList files = drive.files().list() + .setQ("'" + dirEntry.getId() + "' in parents and trashed=false") + .setSpaces("drive") + .setPageSize(1000) + .setFields("nextPageToken, files(" + ENTRY_FIELDS + ")") + .setPageToken(pageToken) + .setOrderBy("name_natural") + .execute(); + + for (File child : files.getFiles()) { + list.add(child); + } + + pageToken = files.getNextPageToken(); +//System.out.println("t: " + (System.currentTimeMillis() - t) + ", " + children.size() + ", " + (pageToken != null)); + } while (pageToken != null); + + return list; } @Override - public void createDirectory(final Path dir, final FileAttribute... attrs) throws IOException { + protected File createDirectoryEntry(File parentEntry, Path dir) throws IOException { File dirEntry = new File(); dirEntry.setName(toFilenameString(dir)); dirEntry.setMimeType(MIME_TYPE_DIR); - if (dir.getParent().getFileName() != null) { - dirEntry.setParents(Arrays.asList(cache.getEntry(dir.getParent()).getId())); + if (dir.toAbsolutePath().getParent().getNameCount() != 0) { + dirEntry.setParents(Arrays.asList(parentEntry.getId())); } - File newEntry = drive.files().create(dirEntry).setFields(ENTRY_FIELDS).execute(); - cache.addEntry(dir, newEntry); + return drive.files().create(dirEntry).setFields(ENTRY_FIELDS).execute(); } @Override - public void delete(final Path path) throws IOException { - removeEntry(path); + protected boolean hasChildren(File dirEntry, Path dir) throws IOException { + // TODO use cache ??? + List files = drive.files().list() + .setQ("'" + dirEntry.getId() + "' in parents and trashed=false") + .execute().getFiles(); + return files != null && files.size() > 0; } @Override - public void copy(final Path source, final Path target, final Set options) throws IOException { - if (cache.existsEntry(target)) { - if (options != null && options.stream().anyMatch(o -> o.equals(StandardCopyOption.REPLACE_EXISTING))) { - removeEntry(target); - } else { - throw new FileAlreadyExistsException(target.toString()); - } - } - - copyEntry(source, target, options); + protected void removeEntry(File entry, Path path) throws IOException { + drive.files().delete(entry.getId()).execute(); } @Override - public void move(final Path source, final Path target, final Set options) throws IOException { - if (cache.existsEntry(target)) { - if (isFolder(cache.getEntry(target))) { - if (options != null && options.stream().anyMatch(o -> o.equals(StandardCopyOption.REPLACE_EXISTING))) { - // replace the target - if (cache.getChildCount(target) > 0) { - throw new DirectoryNotEmptyException(target.toString()); - } else { - removeEntry(target); - moveEntry(source, target, false); - } - } else { - // move into the target - // TODO SPEC is FileAlreadyExistsException ? - moveEntry(source, target, true); - } - } else { - if (options != null && options.stream().anyMatch(o -> o.equals(StandardCopyOption.REPLACE_EXISTING))) { - removeEntry(target); - moveEntry(source, target, false); - } else { - throw new FileAlreadyExistsException(target.toString()); - } - } - } else { - if (source.getParent().equals(target.getParent())) { - // rename - renameEntry(source, target); - } else { - moveEntry(source, target, false); - } + protected File copyEntry(File sourceEntry, File targetParentEntry, Path source, Path target, Set options) throws IOException { + File entry = new File(); + entry.setName(toFilenameString(target)); + entry.setParents(Arrays.asList(targetParentEntry.getId())); + if (options != null && options.stream().anyMatch(o -> o.equals(GoogleDriveCopyOption.EXPORT_AS_GDOCS))) { + entry.setMimeType(GoogleDriveCopyOption.EXPORT_AS_GDOCS.getValue()); } + return drive.files().copy(sourceEntry.getId(), entry).setFields(ENTRY_FIELDS).execute(); } - /** - * Check access modes for a path on this filesystem - *

- * If no modes are provided to check for, this simply checks for the - * existence of the path. - *

- * - * @param path the path to check - * @param modes the modes to check for, if any - * @throws IOException filesystem level error, or a plain I/O error - * if you use this with fuse, you should throw {@link NoSuchFileException} when the file not found. - * @see FileSystemProvider#checkAccess(Path, AccessMode...) - */ @Override - protected void checkAccessImpl(final Path path, final AccessMode... modes) throws IOException { - final File entry = cache.getEntry(path); - - if (isFolder(entry)) { - return; + protected File moveEntry(File sourceEntry, File targetParentEntry, Path source, Path target, boolean targetIsParent) throws IOException { + File entry = new File(); + entry.setName(toFilenameString(target)); + String previousParents = null; + if (sourceEntry.getParents() != null) { + previousParents = String.join(",", sourceEntry.getParents()); } + return drive.files().update(sourceEntry.getId(), entry) + .setAddParents(targetParentEntry.getId()) + .setRemoveParents(previousParents) + .setFields(ENTRY_FIELDS).execute(); + } - // TODO: assumed; not a file == directory - for (final AccessMode mode : modes) { - if (mode == AccessMode.EXECUTE) { - throw new AccessDeniedException(path.toString()); - } + @Override + protected File moveFolderEntry(File sourceEntry, File targetParentEntry, Path source, Path target, boolean targetIsParent) throws IOException { + File dirEntry = new File(); + dirEntry.setName(toFilenameString(target)); + dirEntry.setMimeType(MIME_TYPE_DIR); + String previousParents = null; + if (sourceEntry.getParents() != null) { + previousParents = String.join(",", sourceEntry.getParents()); } + return drive.files().update(sourceEntry.getId(), dirEntry) + .setAddParents(targetParentEntry.getId()) + .setRemoveParents(previousParents) + .setFields(ENTRY_FIELDS).execute(); } @Override - public void close() throws IOException { - // GoogleDrive does not implement Closeable :( + protected File renameEntry(File sourceEntry, File targetParentEntry, Path source, Path target) throws IOException { + File entry = new File(); + entry.setName(toFilenameString(target)); + return drive.files().update(sourceEntry.getId(), entry).setFields(ENTRY_FIELDS).execute(); } - /** - * @throws IOException if you use this with fuse, you should throw {@link NoSuchFileException} when the file not found. - */ - @Nonnull @Override - protected Object getPathMetadataImpl(final Path path) throws IOException { - return cache.getEntry(path); + protected Object getPathMetadata(File entry) throws IOException { + return new Metadata(this, entry); } - /** */ - private List getDirectoryEntries(Path dir, boolean useCache) throws IOException { - final File entry = cache.getEntry(dir); - - if (!isFolder(entry)) { - throw new NotDirectoryException("dir: " + dir); + @Override + public WatchService newWatchService() { + try { + return new GoogleDriveWatchService(drive); + } catch (IOException e) { + throw new IllegalStateException(e); } + } - List list = new ArrayList<>(); - if (useCache && cache.containsFolder(dir)) { - list = cache.getFolder(dir); - } else { - Files.List request = drive.files().list(); - do { - FileList files = request - .setQ("'" + entry.getId() + "' in parents and trashed=false") - .setFields("nextPageToken, files(" + ENTRY_FIELDS + ")").execute(); - final List children = files.getFiles(); - request.setPageToken(files.getNextPageToken()); - - for (final File child : children) { - Path childPath = dir.resolve(child.getName()); - list.add(childPath); - - cache.putFile(childPath, child); - } - } while (request.getPageToken() != null && request.getPageToken().length() > 0); - - cache.putFolder(dir, list); - } + // + // user:attributes + // - return list; + /** attributes user:description */ + void patchEntryDescription(File sourceEntry, String description) throws IOException { + File entry = new File(); + entry.setDescription(description); + File newEntry = drive.files().update(sourceEntry.getId(), entry).setFields(ENTRY_FIELDS).execute(); + Path path = cache.getEntry(sourceEntry); + cache.removeEntry(path); + cache.addEntry(path, newEntry); } - /** */ - private void removeEntry(Path path) throws IOException { - final File entry = cache.getEntry(path); + /** attributes user:revisions */ + List getRevisions(File entry) throws IOException { if (isFolder(entry)) { - // TODO use cache ??? - List list = drive.files().list() - .setQ("'" + entry.getId() + "' in parents and trashed=false") - .execute().getFiles(); + throw new IsDirectoryException("dir: " + entry.getName()); + } - if (list != null && list.size() > 0) { - throw new DirectoryNotEmptyException(path.toString()); + List list = new ArrayList<>(); + String pageToken = null; + do { + RevisionList revisions = drive.revisions().list(entry.getId()) + .setPageSize(1000) + .setFields("nextPageToken, revisions(id, mimeType, modifiedTime, size)") + .setPageToken(pageToken) + .execute(); + + if (revisions.getRevisions() != null) { +Debug.println(Level.FINE, "revisions: " + revisions.getRevisions().size() + ", " + revisions.getNextPageToken()); + revisions.getRevisions().forEach(r -> list.add(r)); } - } - drive.files().delete(entry.getId()).execute(); + pageToken = revisions.getNextPageToken(); + } while (pageToken != null); - cache.removeEntry(path); + return list; } - /** */ - private void copyEntry(final Path source, final Path target, Set options) throws IOException { - final File sourceEntry = cache.getEntry(source); - File targetParentEntry = cache.getEntry(target.getParent()); - if (!isFolder(sourceEntry)) { - File entry = new File(); - entry.setName(toFilenameString(target)); - entry.setParents(Arrays.asList(targetParentEntry.getId())); - if (options != null && options.stream().anyMatch(o -> o.equals(GoogleDriveCopyOption.EXPORT_AS_GDOCS))) { - entry.setMimeType(GoogleDriveCopyOption.EXPORT_AS_GDOCS.getValue()); - } - File newEntry = drive.files().copy(sourceEntry.getId(), entry).setFields(ENTRY_FIELDS).execute(); - - cache.addEntry(target, newEntry); - } else { - // TODO java spec. allows empty folder - throw new UnsupportedOperationException("source can not be a folder"); - } + /** attributes user:revisions */ + void removeRevision(File entry, String revisionId) throws IOException { +Debug.println(Level.INFO, "delete revision: " + entry.getName() + ", revision: " + revisionId); + drive.revisions().delete(entry.getId(), revisionId).execute(); } /** - * @param targetIsParent if the target is folder + * attributes user:thumbnail + * @param image currently only jpeg is available. */ - private void moveEntry(final Path source, final Path target, boolean targetIsParent) throws IOException { - File sourceEntry = cache.getEntry(source); - File targetParentEntry = cache.getEntry(targetIsParent ? target : target.getParent()); - if (!isFolder(sourceEntry)) { - File entry = new File(); - entry.setName(targetIsParent ? toFilenameString(source) : toFilenameString(target)); - String previousParents = null; - if (sourceEntry.getParents() != null) { - previousParents = String.join(",", sourceEntry.getParents()); - } - File newEntry = drive.files().update(sourceEntry.getId(), entry) - .setAddParents(targetParentEntry.getId()) - .setRemoveParents(previousParents) - .setFields(ENTRY_FIELDS).execute(); - cache.removeEntry(source); - if (targetIsParent) { - cache.addEntry(target.resolve(source.getFileName()), newEntry); - } else { - cache.addEntry(target, newEntry); - } - } else if (isFolder(sourceEntry)) { - File dirEntry = new File(); - dirEntry.setName(toFilenameString(target)); - dirEntry.setMimeType(MIME_TYPE_DIR); - String previousParents = null; - if (sourceEntry.getParents() != null) { - previousParents = String.join(",", sourceEntry.getParents()); - } - File newEntry = drive.files().update(sourceEntry.getId(), dirEntry) - .setAddParents(targetParentEntry.getId()) - .setRemoveParents(previousParents) - .setFields(ENTRY_FIELDS).execute(); - cache.moveEntry(source, target, newEntry); - } + void setThumbnail(File sourceEntry, byte[] image) throws IOException { + File.ContentHints.Thumbnail thumbnail = new File.ContentHints.Thumbnail(); + thumbnail.setMimeType("image/jpg"); + thumbnail.encodeImage(image); + + File.ContentHints contentHints = new File.ContentHints(); + contentHints.setThumbnail(thumbnail); + + File entry = new File(); + entry.setContentHints(contentHints); + + File newEntry = drive.files().update(sourceEntry.getId(), entry).setFields("thumbnailLink").execute(); +Debug.println(Level.INFO, "thumbnail updated: " + sourceEntry.getName() + ", size: " + image.length + ", " + StringUtil.paramString(newEntry)); } - /** */ - private void renameEntry(final Path source, final Path target) throws IOException { - File sourceEntry = cache.getEntry(source); + /** + * attributes user:thumbnail + * @return url + * @see "https://stackoverflow.com/a/45027853" + */ + String getThumbnail(File sourceEntry) throws IOException { File entry = new File(); - entry.setName(toFilenameString(target)); - File newEntry = drive.files().update(sourceEntry.getId(), entry).setFields(ENTRY_FIELDS).execute(); - cache.removeEntry(source); - cache.addEntry(target, newEntry); + + File newEntry = drive.files().update(sourceEntry.getId(), entry).setFields("thumbnailLink").execute(); +Debug.println(Level.INFO, "thumbnail url: " + sourceEntry.getName() + ", url: " + newEntry.getThumbnailLink()); + return newEntry.getThumbnailLink(); } } diff --git a/vavi-nio-file-googledrive/src/main/java/vavi/nio/file/googledrive/GoogleDriveFileSystemOptionsFactory.java b/vavi-nio-file-googledrive/src/main/java/vavi/nio/file/googledrive/GoogleDriveFileSystemOptionsFactory.java index 9b429cfc..4d96f21d 100644 --- a/vavi-nio-file-googledrive/src/main/java/vavi/nio/file/googledrive/GoogleDriveFileSystemOptionsFactory.java +++ b/vavi-nio-file-googledrive/src/main/java/vavi/nio/file/googledrive/GoogleDriveFileSystemOptionsFactory.java @@ -23,6 +23,8 @@ public GoogleDriveFileSystemOptionsFactory() { addLinkOption(LinkOption.NOFOLLOW_LINKS); addCopyOption(GoogleDriveCopyOption.EXPORT_AS_GDOCS); addReadOpenOption(GoogleDriveOpenOption.EXPORT_WITH_GDOCS_DOCX); + addReadOpenOption(GoogleDriveOpenOption.EXPORT_WITH_GDOCS_XLSX); + addWriteOpenOption(GoogleDriveOpenOption.INPORT_AS_NEW_REVISION); } } diff --git a/vavi-nio-file-googledrive/src/main/java/vavi/nio/file/googledrive/GoogleDriveFileSystemProvider.java b/vavi-nio-file-googledrive/src/main/java/vavi/nio/file/googledrive/GoogleDriveFileSystemProvider.java index f40aeb46..fffd1ac2 100644 --- a/vavi-nio-file-googledrive/src/main/java/vavi/nio/file/googledrive/GoogleDriveFileSystemProvider.java +++ b/vavi-nio-file-googledrive/src/main/java/vavi/nio/file/googledrive/GoogleDriveFileSystemProvider.java @@ -23,6 +23,8 @@ public final class GoogleDriveFileSystemProvider extends FileSystemProviderBase public static final String ENV_APP_CREDENTIAL = "app_credential"; + public static final String ENV_USE_SYSTEM_WATCHER = "use_system_watcher"; + public GoogleDriveFileSystemProvider() { super(new GoogleDriveFileSystemRepository()); } diff --git a/vavi-nio-file-googledrive/src/main/java/vavi/nio/file/googledrive/GoogleDriveFileSystemRepository.java b/vavi-nio-file-googledrive/src/main/java/vavi/nio/file/googledrive/GoogleDriveFileSystemRepository.java index 7b652c13..fc7be447 100644 --- a/vavi-nio-file-googledrive/src/main/java/vavi/nio/file/googledrive/GoogleDriveFileSystemRepository.java +++ b/vavi-nio-file-googledrive/src/main/java/vavi/nio/file/googledrive/GoogleDriveFileSystemRepository.java @@ -16,11 +16,12 @@ import com.github.fge.filesystem.driver.FileSystemDriver; import com.github.fge.filesystem.provider.FileSystemRepositoryBase; +import com.google.api.client.auth.oauth2.Credential; import com.google.api.services.drive.Drive; import vavi.net.auth.WithTotpUserCredential; -import vavi.net.auth.oauth2.google.GoogleAppCredential; -import vavi.net.auth.oauth2.google.GoogleLocalAppCredential; +import vavi.net.auth.oauth2.google.GoogleOAuth2AppCredential; +import vavi.net.auth.oauth2.google.GoogleLocalOAuth2AppCredential; import vavi.net.auth.oauth2.google.GoogleOAuth2; import vavi.net.auth.web.google.GoogleLocalUserCredential; @@ -64,18 +65,26 @@ public FileSystemDriver createDriver(final URI uri, final Map env) th } // 2. app credential - GoogleAppCredential appCredential = null; + GoogleOAuth2AppCredential appCredential = null; if (env.containsKey(GoogleDriveFileSystemProvider.ENV_APP_CREDENTIAL)) { - appCredential = GoogleAppCredential.class.cast(env.get(GoogleDriveFileSystemProvider.ENV_APP_CREDENTIAL)); + appCredential = GoogleOAuth2AppCredential.class.cast(env.get(GoogleDriveFileSystemProvider.ENV_APP_CREDENTIAL)); } if (appCredential == null) { - appCredential = new GoogleLocalAppCredential(); // TODO use props + appCredential = new GoogleLocalOAuth2AppCredential("googledrive"); // TODO use props } // 3. process - Drive drive = new GoogleOAuth2(appCredential).authorize(userCredential); + Credential credential = new GoogleOAuth2(appCredential).authorize(userCredential); + Drive drive = new Drive.Builder(GoogleOAuth2.getHttpTransport(), GoogleOAuth2.getJsonFactory(), credential) + .setHttpRequestInitializer(httpRequest -> { + credential.initialize(httpRequest); + httpRequest.setConnectTimeout(30 * 1000); + httpRequest.setReadTimeout(30 * 1000); + }) + .setApplicationName(appCredential.getClientId()) + .build(); GoogleDriveFileStore fileStore = new GoogleDriveFileStore(drive, factoryProvider.getAttributesFactory()); return new GoogleDriveFileSystemDriver(fileStore, factoryProvider, drive, env); } diff --git a/vavi-nio-file-googledrive/src/main/java/vavi/nio/file/googledrive/GoogleDriveOpenOption.java b/vavi-nio-file-googledrive/src/main/java/vavi/nio/file/googledrive/GoogleDriveOpenOption.java index 9c58c25d..4021c9f4 100644 --- a/vavi-nio-file-googledrive/src/main/java/vavi/nio/file/googledrive/GoogleDriveOpenOption.java +++ b/vavi-nio-file-googledrive/src/main/java/vavi/nio/file/googledrive/GoogleDriveOpenOption.java @@ -12,15 +12,27 @@ /** * GoogleDriveOpenOption. * + * TODO direct ocr + * * @author Naohide Sano (umjammer) * @version 0.00 2017/03/01 umjammer initial version
*/ public enum GoogleDriveOpenOption implements OpenOption { + /** + * download a file as docx. + */ EXPORT_WITH_GDOCS_DOCX("application/vnd.openxmlformats-officedocument.wordprocessingml.document"), - EXPORT_WITH_GDOCS_XLSX("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); - - /** */ + /** + * download a file as xlsx. + */ + EXPORT_WITH_GDOCS_XLSX("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"), + /** + * upload a file as new revision. + */ + INPORT_AS_NEW_REVISION(null); + + /** depends on enum stuff */ private String value; /** */ diff --git a/vavi-nio-file-googledrive/src/main/java/vavi/nio/file/googledrive/GoogleDriveUserDefinedFileAttributesProvider.java b/vavi-nio-file-googledrive/src/main/java/vavi/nio/file/googledrive/GoogleDriveUserDefinedFileAttributesProvider.java new file mode 100644 index 00000000..cb731e28 --- /dev/null +++ b/vavi-nio-file-googledrive/src/main/java/vavi/nio/file/googledrive/GoogleDriveUserDefinedFileAttributesProvider.java @@ -0,0 +1,295 @@ +/* + * Copyright (c) 2020 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package vavi.nio.file.googledrive; + +import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.ByteBuffer; +import java.time.OffsetDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.stream.Collectors; + +import com.github.fge.filesystem.attributes.provider.UserDefinedFileAttributesProvider; +import com.google.api.services.drive.model.File; +import com.google.api.services.drive.model.Revision; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import vavi.nio.file.googledrive.GoogleDriveFileAttributesFactory.Metadata; +import vavi.util.Debug; + + +/** + * GoogleDriveUserDefinedFileAttributesProvider. + * + * @author Naohide Sano (umjammer) + * @version 0.00 2020/09/08 umjammer initial version
+ */ +public class GoogleDriveUserDefinedFileAttributesProvider extends UserDefinedFileAttributesProvider { + + /** driver & file entry */ + private final Metadata entry; + + /** */ + public GoogleDriveUserDefinedFileAttributesProvider(Metadata entry) throws IOException { + this.entry = entry; + } + + /** pre-listed */ + private static final List list = Arrays.stream(UserAttributes.values()).map(e -> e.name()).collect(Collectors.toList()); + + @Override + public List list() throws IOException { + return list; + } + + @Override + public int size(String name) throws IOException { + return UserAttributes.valueOf(name).size(entry); + } + + @Override + public int read(String name, ByteBuffer dst) throws IOException { + return UserAttributes.valueOf(name).read(entry, dst); + } + + @Override + public int write(final String name, final ByteBuffer src) throws IOException { + return UserAttributes.valueOf(name).write(entry, src); + } + + /** */ + private interface UserAttribute { + int size(T entry) throws IOException; + int read(T entry, ByteBuffer dst) throws IOException; + int write(T entry, ByteBuffer src) throws IOException; + } + + /** user:foo */ + private enum UserAttributes implements UserAttribute { + /** {@link String} */ + description { + /** */ + Map descriptionCache = new ConcurrentHashMap<>(); // TODO LRU + + /** */ + private String getDescription(File file) { + if (descriptionCache.containsKey(file)) { + return descriptionCache.get(file); + } else { + String description = file.getDescription(); + descriptionCache.put(file, description); + return description; + } + } + + @Override + public int size(Metadata entry) throws IOException { + String description = getDescription(entry.file); +if (description != null) { + Debug.println(Level.FINE, "size " + name() + ": " + description); +} + return description == null ? 0 : description.getBytes().length; + } + + @Override + public int read(Metadata entry, ByteBuffer dst) throws IOException { + String description = getDescription(entry.file); +if (description != null) { + Debug.println(Level.FINE, "read " + name() + ": " + description); +} + if (description != null) { + dst.put(description.getBytes()); + } + return dst.array().length; + } + + @Override + public int write(Metadata entry, ByteBuffer src) throws IOException { + String description = new String(src.array()); +Debug.println(Level.FINE, "write " + name() + ": " + description); + entry.driver.patchEntryDescription(entry.file, description); + return description.getBytes().length; + } + }, + /** {@link String}, "\n" separated */ + revisions { + /** */ + Map> revisionsCache = new ConcurrentHashMap<>(); // TODO LRU + + /** */ + private List getRevisions(Metadata entry) throws IOException { + if (revisionsCache.containsKey(entry.file)) { + return revisionsCache.get(entry.file); + } else { + List revisions = entry.driver.getRevisions(entry.file); + List results = revisions.stream().map(r -> r.toString()).collect(Collectors.toList()); + revisionsCache.put(entry.file, results); + return results; + } + } + + @Override + public int size(Metadata entry) throws IOException { + // joined by '\n' + int len = getRevisions(entry).stream().mapToInt(r -> r.toString().getBytes().length + 1).sum() - 1; +if (len > 0) { + Debug.println(Level.FINE, "size " + name() + ": " + len); +} + return len; + } + + @Override + public int read(Metadata entry, ByteBuffer dst) throws IOException { +Debug.println(Level.FINE, "read " + name() + ":\n" + String.join("\n", getRevisions(entry))); + dst.put(String.join("\n", getRevisions(entry)).getBytes()); + return dst.array().length; + } + + @Override + public int write(Metadata entry, ByteBuffer src) throws IOException { + String[] revisions = RevisionsUtil.split(src.array()); +Arrays.stream(revisions).forEach(r -> { + Debug.println(Level.INFO, "write " + name() + ": " + r); +}); + // to be deleted + List toDeleted = new ArrayList<>(); + Arrays.stream(revisions) + .map(y -> (String) gson.fromJson(y, Map.class).get("id")) + .forEach(b -> { + try { + toDeleted.addAll(getRevisions(entry).stream() + .map(x -> (String) gson.fromJson(x, Map.class).get("id")) + .filter(a -> !a.equals(b)) + .collect(Collectors.toList())); + } catch (IOException e) { +Debug.printStackTrace(Level.WARNING, e); + } + }); + + toDeleted.forEach(id -> { + try { + entry.driver.removeRevision(entry.file, id); + } catch (IOException e) { +Debug.printStackTrace(Level.WARNING, e); + } + }); + + revisionsCache.remove(entry.file); + + // TODO to be added? + + return src.array().length; + } + }, + /** whole image file */ + thumbnail { + /** */ + Map thumbnailCache = new ConcurrentHashMap<>(); // TODO LRU + + /** */ + private String getUrl(Metadata entry) throws IOException { + String url = thumbnailCache.get(entry.file); + if (url != null) { + return url; + } else { + url = entry.driver.getThumbnail(entry.file); + if (url != null) { + thumbnailCache.put(entry.file, url); + } + return url; + } + } + + /** */ + private byte[] getThumbnail(Metadata entry) throws IOException { +try { + String url = getUrl(entry); + InputStream is = new BufferedInputStream(new URL(url).openStream()); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + byte[] buffer = new byte[8024]; + int l = 0; + while ((l = is.read(buffer)) != -1) { + baos.write(buffer, 0, l); + } + return baos.toByteArray(); +} catch (java.io.FileNotFoundException e) { + Debug.println(Level.WARNING, e.toString()); + return null; +} + } + + @Override + public int size(Metadata entry) throws IOException { + String url = getUrl(entry); + if (url != null) { + byte[] thumbnail = getThumbnail(entry); + if (thumbnail != null) { + return thumbnail.length; + } + } + return 0; + } + + @Override + public int read(Metadata entry, ByteBuffer dst) throws IOException { + String url = getUrl(entry); + if (url != null) { + byte[] thumbnail = getThumbnail(entry); + if (thumbnail != null) { + dst.put(thumbnail); + return thumbnail.length; + } + } + return 0; + } + + @Override + public int write(Metadata entry, ByteBuffer src) throws IOException { + byte[] thumbnail = src.array(); + entry.driver.setThumbnail(entry.file, thumbnail); + return thumbnail.length; + } + }; + } + + /** */ + private static Gson gson = new GsonBuilder().create(); + + /** utility */ + public static class RevisionsUtil { + /** sort by "modifiedTime" desc */ + public static byte[] getLatestOnly(byte[] revisions) { + List> revisionList = Arrays.stream(split(revisions)) + .map(r -> gson.fromJson(r, Map.class)) + .sorted((o1, o2) -> { + OffsetDateTime odt1 = OffsetDateTime.parse((String) o1.get("modifiedTime")); + OffsetDateTime odt2 = OffsetDateTime.parse((String) o2.get("modifiedTime")); + return odt2.compareTo(odt1); + }) + .collect(Collectors.toList()); +//revisionList.forEach(System.err::println); + return gson.toJson(revisionList.get(0)).getBytes(); + } + public static String[] split(byte[] revisions) { + return new String((byte[]) revisions).split("\n"); + } + public static int size(Object revisions) { + return split((byte[]) revisions).length; + } + } +} + +/* */ diff --git a/vavi-nio-file-googledrive/src/main/java/vavi/nio/file/googledrive/GoogleDriveWatchService.java b/vavi-nio-file-googledrive/src/main/java/vavi/nio/file/googledrive/GoogleDriveWatchService.java new file mode 100644 index 00000000..3e7a8c5d --- /dev/null +++ b/vavi-nio-file-googledrive/src/main/java/vavi/nio/file/googledrive/GoogleDriveWatchService.java @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2020 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package vavi.nio.file.googledrive; + +import java.io.IOException; +import java.util.UUID; + +import com.google.api.client.googleapis.notifications.UnparsedNotification; +import com.google.api.services.drive.Drive; +import com.google.api.services.drive.model.Change; +import com.google.api.services.drive.model.ChangeList; +import com.google.api.services.drive.model.Channel; +import com.google.api.services.drive.model.File; +import com.google.api.services.drive.model.StartPageToken; + +import vavi.nio.file.watch.webhook.WebHookBaseWatchService; +import vavi.util.Debug; + +import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE; +import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY; + + +/** + * GoogleDriveWatchService. + *

+ * notification source is {@link #channel}. + *

+ * system properties + *

    + *
  • vavi.nio.file.watch.webhook.NotificationProvider.googledrive + *
+ *

+ *

+ * environment variables + *

    + *
  • VAVI_APPS_WEBHOOK_SECRET + *
  • VAVI_APPS_WEBHOOK_WEBHOOK_GOOGLE_URL + *
+ * + * @author Naohide Sano (umjammer) + * @version 0.00 2020/07/06 umjammer initial version
+ */ +public class GoogleDriveWatchService extends WebHookBaseWatchService { + + private static final String WEBHOOK_NOTIFICATION_PROVIDER = + System.getProperty("vavi.nio.file.watch.webhook.NotificationProvider.googledrive", ".googledrive.webhook.websocket"); + + private static final String VAVI_APPS_WEBHOOK_SECRET = System.getenv("VAVI_APPS_WEBHOOK_SECRET"); + private static final String webhooktUrl = System.getenv("VAVI_APPS_WEBHOOK_WEBHOOK_GOOGLE_URL"); + + private Drive drive; + private Channel channel = null; + + private String savedStartPageToken; + + /** */ + public GoogleDriveWatchService(Drive drive) throws IOException { + this.drive = drive; + + UUID uuid = UUID.randomUUID(); + + setupNotification(this, WEBHOOK_NOTIFICATION_PROVIDER, uuid); + + StartPageToken response = drive.changes().getStartPageToken().execute(); + savedStartPageToken = response.getStartPageToken(); +Debug.println("GOOGLE: start token: " + savedStartPageToken); + + Channel content = new Channel() + .setId(uuid.toString()) + .setType("web_hook") + .setToken(VAVI_APPS_WEBHOOK_SECRET) + .setAddress(webhooktUrl); + + channel = drive.changes().watch(savedStartPageToken, content).execute(); +Debug.println("GOOGLE: channel: " + channel); + } + + /** + UnparsedNotification{ + messageNumber=9727, + resourceState=change, + resourceId=r7_j-XM_WRzwHe8Lns_ECZzzXS8, + resourceUri=https://www.googleapis.com/drive/v3/changes?includeCorpusRemovals=false&includeItemsFromAllDrives=false&includeRemoved=true&includeTeamDriveItems=false&pageSize=100&pageToken=905064&restrictToMyDrive=false&spaces=drive&supportsAllDrives=false&supportsTeamDrives=false&alt=json, + channelId=$uuid, + channelExpiration=Mon, 06 Jul 2020 16:44:43 GMT, + channelToken=$VAVI_APPS_WEBHOOK_SECRET, + changed=null, + contentType=null + } + */ + protected void onNotifyMessage(UnparsedNotification notification) throws IOException { +Debug.println(">> notification: " + notification.getResourceState()); + + if (!channel.getId().equals(notification.getChannelId())) { +Debug.println(">> notification is not for this channel: " + notification.getChannelId()); + +//try { // *** STOP ANOTHER CHANNEL *** +// Channel c = new Channel(); +// c.setId(notification.getChannelId()); +// c.setResourceId(notification.getResourceId()); +// drive.channels().stop(c).execute(); +// Debug.println(">> notification: stop another channel: " + c.getId()); +//} catch (IOException e) { +// e.printStackTrace(); +//} + + return; + } + + switch (notification.getResourceState()) { + case "sync": +Debug.println(">> synched"); + break; + case "change": + // Begin with our last saved start token for this user or the + // current token from getStartPageToken() + String pageToken = savedStartPageToken; + while (pageToken != null) { + ChangeList changes = drive.changes().list(pageToken).execute(); + for (Change change : changes.getChanges()) { + // Process change +Debug.println(">> " + (change.getFile() == null ? "id" : isFolder(change.getFile()) ? "folder" : "file") + + "[" + (change.getFile() != null ? change.getFile().getName() : change.getFileId()) + "] " + (change.getRemoved() ? "deleted" : "updated?")); + + listener.accept(change.getFileId(), change.getRemoved() ? ENTRY_DELETE : ENTRY_MODIFY); + } + if (changes.getNewStartPageToken() != null) { + // Last page, save this token for the next polling interval + savedStartPageToken = changes.getNewStartPageToken(); + } + pageToken = changes.getNextPageToken(); + } + break; + default: +Debug.println(">> unhandled state: " + notification.getResourceState()); + break; + } + +Debug.println(">> notification: done"); + } + + @Override + public void close() throws IOException { + if (isOpen()) { + super.close(); + + drive.channels().stop(channel).execute(); +Debug.println("GOOGLE: channel deleted: " + channel); + } + } + + /** TODO duplicated */ + private static boolean isFolder(File file) { + return GoogleDriveFileSystemDriver.MIME_TYPE_DIR.equals(file.getMimeType()); + } +} diff --git a/vavi-nio-file-googledrive/src/main/java/vavi/nio/file/googledrive/webhook/websocket/GoogleJsonCodec.java b/vavi-nio-file-googledrive/src/main/java/vavi/nio/file/googledrive/webhook/websocket/GoogleJsonCodec.java new file mode 100644 index 00000000..a453536d --- /dev/null +++ b/vavi-nio-file-googledrive/src/main/java/vavi/nio/file/googledrive/webhook/websocket/GoogleJsonCodec.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2020 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package vavi.nio.file.googledrive.webhook.websocket; + +import com.google.api.client.googleapis.notifications.UnparsedNotification; +import com.google.gson.Gson; + +import vavi.nio.file.watch.webhook.websocket.JsonCodec; + + +/** + * GoogleJsonCodec. + * + * @author Naohide Sano (umjammer) + * @version 0.00 2020/07/02 umjammer initial version
+ */ +public class GoogleJsonCodec extends JsonCodec { + + public static Gson gsonForGoogleJsonCodec = gson; + + public static class GoogleJsonEncoder extends JsonEncoder { + } + + public static class GoogleJsonDecoder extends JsonDecoder { + @Override + protected Class getType() { + return UnparsedNotification.class; + } + } +} + +/* */ diff --git a/vavi-nio-file-googledrive/src/main/java/vavi/nio/file/googledrive/webhook/websocket/GoogleWebSocketNotification.java b/vavi-nio-file-googledrive/src/main/java/vavi/nio/file/googledrive/webhook/websocket/GoogleWebSocketNotification.java new file mode 100644 index 00000000..3bd523f5 --- /dev/null +++ b/vavi-nio-file-googledrive/src/main/java/vavi/nio/file/googledrive/webhook/websocket/GoogleWebSocketNotification.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2020 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package vavi.nio.file.googledrive.webhook.websocket; + +import java.io.IOException; +import java.net.URI; +import java.util.function.Consumer; + +import javax.websocket.ClientEndpoint; +import javax.websocket.Session; + +import com.google.api.client.googleapis.notifications.UnparsedNotification; + +import vavi.nio.file.googledrive.webhook.websocket.GoogleJsonCodec.GoogleJsonDecoder; +import vavi.nio.file.googledrive.webhook.websocket.GoogleJsonCodec.GoogleJsonEncoder; +import vavi.nio.file.watch.webhook.websocket.BasicAuthorizationConfigurator; +import vavi.nio.file.watch.webhook.websocket.WebSocketNotification; + + +/** + * GoogleWebSocketNotification. + *

+ * environment variables + *

    + *
  • VAVI_APPS_WEBHOOK_WEBSOCKET_BASE_URL + *
  • VAVI_APPS_WEBHOOK_WEBSOCKET_GOOGLE_PATH + *
+ * + * @author Naohide Sano (umjammer) + * @version 0.00 2020/07/06 umjammer initial version
+ */ +@ClientEndpoint(decoders = GoogleJsonDecoder.class, + encoders = GoogleJsonEncoder.class, + configurator = BasicAuthorizationConfigurator.class) +public class GoogleWebSocketNotification extends WebSocketNotification { + + private static final String websocketBaseUrl = System.getenv("VAVI_APPS_WEBHOOK_WEBSOCKET_BASE_URL"); + private static final String websocketPath = System.getenv("VAVI_APPS_WEBHOOK_WEBSOCKET_GOOGLE_PATH"); + + private static final URI uri = URI.create(websocketBaseUrl + websocketPath); + + private Consumer callback; + + /** + * @param args 0: uuid for channel id + */ + public GoogleWebSocketNotification(Consumer callback, Object... args) throws IOException { + super(uri, args); + this.callback = callback; + } + + @Override + public void onOpenImpl(Session session) throws IOException { + session.getBasicRemote().sendText(String.join(" ", "GOOGLE_DRIVE_CHANGE", args[0].toString())); + } + + @Override + protected void onNotifyMessageImpl(UnparsedNotification notification) throws IOException { + callback.accept(notification); + } + + @Override + protected void onCloseImpl(Session session) throws IOException { + } +} + +/* */ diff --git a/vavi-nio-file-googledrive/src/main/java/vavi/nio/file/googledrive/webhook/websocket/GoogleWebSocketNotificationProvider.java b/vavi-nio-file-googledrive/src/main/java/vavi/nio/file/googledrive/webhook/websocket/GoogleWebSocketNotificationProvider.java new file mode 100644 index 00000000..efaf4e6c --- /dev/null +++ b/vavi-nio-file-googledrive/src/main/java/vavi/nio/file/googledrive/webhook/websocket/GoogleWebSocketNotificationProvider.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2020 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package vavi.nio.file.googledrive.webhook.websocket; + +import java.io.IOException; +import java.util.function.Consumer; + +import vavi.nio.file.watch.webhook.Notification; +import vavi.nio.file.watch.webhook.NotificationProvider; + + +/** + * GoogleWebSocketNotificationProvider. + * + * @author Naohide Sano (umjammer) + * @version 0.00 2020/07/07 umjammer initial version
+ */ +public class GoogleWebSocketNotificationProvider implements NotificationProvider { + + @Override + public Notification getNotification(Consumer callback, Object... args) throws IOException { + return Notification.class.cast(new GoogleWebSocketNotification(Consumer.class.cast(callback), args)); + } +} + +/* */ diff --git a/vavi-nio-file-googledrive/src/main/resources/META-INF/services/vavi.nio.file.watch.webhook.NotificationProvider b/vavi-nio-file-googledrive/src/main/resources/META-INF/services/vavi.nio.file.watch.webhook.NotificationProvider new file mode 100644 index 00000000..451d5380 --- /dev/null +++ b/vavi-nio-file-googledrive/src/main/resources/META-INF/services/vavi.nio.file.watch.webhook.NotificationProvider @@ -0,0 +1 @@ +vavi.nio.file.googledrive.webhook.websocket.GoogleWebSocketNotificationProvider diff --git a/vavi-nio-file-googledrive/src/test/java/vavi/nio/file/googledrive/GoogleDriveFS.java b/vavi-nio-file-googledrive/src/test/java/vavi/nio/file/googledrive/GoogleDriveFS.java deleted file mode 100644 index 6dcb5c56..00000000 --- a/vavi-nio-file-googledrive/src/test/java/vavi/nio/file/googledrive/GoogleDriveFS.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) 2016 by Naohide Sano, All rights reserved. - * - * Programmed by Naohide Sano - */ - -package vavi.nio.file.googledrive; - -import java.net.URI; -import java.nio.file.FileSystem; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import vavi.net.fuse.Fuse; - - -/** - * GoogleDriveFS. - * - * @depends "file://${HOME}.vavifuse/googledrive/?" - * - * @author Naohide Sano (umjammer) - * @version 0.00 2016/02/29 umjammer initial version
- */ -public class GoogleDriveFS { - /** - * @param args 0: mount point, 1: email - */ - public static void main(String[] args) throws Exception { - if (args.length != 2) { - System.err.println("Usage: GoogleDriveFS "); - System.exit(1); - } - - String email = args[1]; - - URI uri = URI.create("googledrive:///?id=" + email); - - Map env = new HashMap<>(); - env.put("ignoreAppleDouble", true); - - FileSystem fs = new GoogleDriveFileSystemProvider().newFileSystem(uri, env); - -// System.setProperty("vavi.net.fuse.FuseProvider.class", "vavi.net.fuse.javafs.JavaFSFuseProvider"); -// System.setProperty("vavi.net.fuse.FuseProvider.class", "vavi.net.fuse.jnrfuse.JnrFuseFuseProvider"); - - Fuse.getFuse().mount(fs, args[0], Collections.EMPTY_MAP); - } -} diff --git a/vavi-nio-file-googledrive/src/test/java/vavi/nio/file/googledrive/Main.java b/vavi-nio-file-googledrive/src/test/java/vavi/nio/file/googledrive/Main.java index a1059f16..7cc3308b 100644 --- a/vavi-nio-file-googledrive/src/test/java/vavi/nio/file/googledrive/Main.java +++ b/vavi-nio-file-googledrive/src/test/java/vavi/nio/file/googledrive/Main.java @@ -11,7 +11,6 @@ import java.nio.file.Files; import java.util.Collections; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import static vavi.nio.file.Base.testAll; @@ -35,17 +34,16 @@ void test01() throws Exception { URI uri = URI.create("googledrive:///?id=" + email); - testAll(new GoogleDriveFileSystemProvider().newFileSystem(uri, Collections.EMPTY_MAP)); + testAll(new GoogleDriveFileSystemProvider().newFileSystem(uri, Collections.emptyMap())); } - @Test - @Disabled - void test02() throws Exception { - String email = System.getenv("GOOGLE_TEST_ACCOUNT2"); + /** */ + public static void main(String[] args) throws Exception { + String email = System.getenv("GOOGLE_TEST_ACCOUNT"); URI uri = URI.create("googledrive:///?id=" + email); - FileSystem fs = new GoogleDriveFileSystemProvider().newFileSystem(uri, Collections.EMPTY_MAP); + FileSystem fs = new GoogleDriveFileSystemProvider().newFileSystem(uri, Collections.emptyMap()); Files.list(fs.getPath("/")).forEach(System.out::println); } } \ No newline at end of file diff --git a/vavi-nio-file-googledrive/src/test/java/vavi/nio/file/googledrive/Main2.java b/vavi-nio-file-googledrive/src/test/java/vavi/nio/file/googledrive/Main2.java index b39ad608..fa80ee48 100644 --- a/vavi-nio-file-googledrive/src/test/java/vavi/nio/file/googledrive/Main2.java +++ b/vavi-nio-file-googledrive/src/test/java/vavi/nio/file/googledrive/Main2.java @@ -11,6 +11,7 @@ import java.util.Collections; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable; import static vavi.nio.file.Base.testLargeFile; @@ -27,11 +28,12 @@ public class Main2 { * caution! google drive api quota is small for repeating tests. */ @Test + @DisabledIfEnvironmentVariable(named = "GITHUB_WORKFLOW", matches = ".*") void test01() throws Exception { String email = System.getenv("GOOGLE_TEST_ACCOUNT"); URI uri = URI.create("googledrive:///?id=" + email); - FileSystem fs = new GoogleDriveFileSystemProvider().newFileSystem(uri, Collections.EMPTY_MAP); + FileSystem fs = new GoogleDriveFileSystemProvider().newFileSystem(uri, Collections.emptyMap()); testLargeFile(fs, null); } diff --git a/vavi-nio-file-googledrive/src/test/java/vavi/nio/file/googledrive/Main3.java b/vavi-nio-file-googledrive/src/test/java/vavi/nio/file/googledrive/Main3.java index ea43f868..36552127 100644 --- a/vavi-nio-file-googledrive/src/test/java/vavi/nio/file/googledrive/Main3.java +++ b/vavi-nio-file-googledrive/src/test/java/vavi/nio/file/googledrive/Main3.java @@ -31,7 +31,7 @@ void test01() throws Exception { String email = System.getenv("GOOGLE_TEST_ACCOUNT"); URI uri = URI.create("googledrive:///?id=" + email); - FileSystem fs = new GoogleDriveFileSystemProvider().newFileSystem(uri, Collections.EMPTY_MAP); + FileSystem fs = new GoogleDriveFileSystemProvider().newFileSystem(uri, Collections.emptyMap()); testMoveFolder(fs); } diff --git a/vavi-nio-file-googledrive/src/test/java/vavi/nio/file/googledrive/Main4.java b/vavi-nio-file-googledrive/src/test/java/vavi/nio/file/googledrive/Main4.java index 7bb6ca19..6c0425c7 100644 --- a/vavi-nio-file-googledrive/src/test/java/vavi/nio/file/googledrive/Main4.java +++ b/vavi-nio-file-googledrive/src/test/java/vavi/nio/file/googledrive/Main4.java @@ -10,7 +10,6 @@ import java.net.URI; import java.nio.file.FileSystem; import java.nio.file.FileSystems; -import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -74,7 +73,7 @@ public void test01(String providerClassName) throws Exception { // - public static void main(final String... args) throws IOException { + public static void main(String[] args) throws IOException { String email = args[1]; Map env = new HashMap<>(); @@ -84,17 +83,17 @@ public static void main(final String... args) throws IOException { FileSystem fs = new GoogleDriveFileSystemProvider().newFileSystem(uri, env); -// System.setProperty("vavi.net.fuse.FuseProvider.class", "vavi.net.fuse.javafs.JavaFSFuseProvider"); + System.setProperty("vavi.net.fuse.FuseProvider.class", "vavi.net.fuse.javafs.JavaFSFuseProvider"); // System.setProperty("vavi.net.fuse.FuseProvider.class", "vavi.net.fuse.jnrfuse.JnrFuseFuseProvider"); Map options = new HashMap<>(); options.put("fsname", "googledrive_fs" + "@" + System.currentTimeMillis()); - options.put(vavi.net.fuse.javafs.JavaFSFuse.ENV_DEBUG, true); + options.put(vavi.net.fuse.javafs.JavaFSFuse.ENV_DEBUG, false); options.put(vavi.net.fuse.javafs.JavaFSFuse.ENV_READ_ONLY, false); options.put("noappledouble", null); // options.put("noapplexattr", null); - Fuse.getFuse().mount(fs, args[0], Collections.EMPTY_MAP); + Fuse.getFuse().mount(fs, args[0], options); } } diff --git a/vavi-nio-file-googledrive/src/test/java/vavi/nio/file/googledrive/Main6.java b/vavi-nio-file-googledrive/src/test/java/vavi/nio/file/googledrive/Main6.java new file mode 100644 index 00000000..b6a41740 --- /dev/null +++ b/vavi-nio-file-googledrive/src/test/java/vavi/nio/file/googledrive/Main6.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2020 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package vavi.nio.file.googledrive; + +import java.io.IOException; +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.util.Collections; + +import org.junit.jupiter.api.Test; + +import static vavi.nio.file.Base.testDescription; + + +/** + * GoogleDrive user:description. + * + * @author Naohide Sano (umjammer) + * @version 0.00 2020/05/31 umjammer initial version
+ */ +public class Main6 { + + @Test + void test01() throws Exception { + String email = System.getenv("GOOGLE_TEST_ACCOUNT"); + + URI uri = URI.create("googledrive:///?id=" + email); + FileSystem fs = new GoogleDriveFileSystemProvider().newFileSystem(uri, Collections.emptyMap()); + + testDescription(fs); + } + + // + + public static void main(String[] args) throws IOException { + String email = System.getenv("GOOGLE_TEST_ACCOUNT"); + + URI uri = URI.create("googledrive:///?id=" + email); + FileSystem fs = new GoogleDriveFileSystemProvider().newFileSystem(uri, Collections.emptyMap()); + + String description = "čĒŦ明テ゚ト " + System.currentTimeMillis() + "\n\n" + "あãƒŧだこãƒŧだ"; + Files.setAttribute(fs.getPath("tmp/amazon_parchase_history.txt"), "user:description", description.getBytes()); + + fs.close(); + } +} \ No newline at end of file diff --git a/vavi-nio-file-googledrive/src/test/java/vavi/nio/file/googledrive/Main7.java b/vavi-nio-file-googledrive/src/test/java/vavi/nio/file/googledrive/Main7.java new file mode 100644 index 00000000..4f894652 --- /dev/null +++ b/vavi-nio-file-googledrive/src/test/java/vavi/nio/file/googledrive/Main7.java @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2021 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package vavi.nio.file.googledrive; + +import java.io.IOException; +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.Arrays; +import java.util.Collections; + +import org.junit.jupiter.api.Test; + +import vavi.nio.file.googledrive.GoogleDriveUserDefinedFileAttributesProvider.RevisionsUtil; +import vavi.util.Debug; + +import static java.nio.file.FileVisitResult.CONTINUE; +import static org.junit.jupiter.api.Assertions.assertEquals; + + +/** + * GoogleDrive attribute user:revision. + * + * TODO upload a file as new revision + * + * @author Naohide Sano (umjammer) + * @version 0.00 2021/10/30 umjammer initial version
+ */ +public class Main7 { + + @Test + void test01() throws Exception { + String email = System.getenv("GOOGLE_TEST_ACCOUNT"); + + URI uri = URI.create("googledrive:///?id=" + email); + FileSystem fs = new GoogleDriveFileSystemProvider().newFileSystem(uri, Collections.emptyMap()); + + Path src = Paths.get(Main7.class.getResource("/Hello.java").toURI()); + Path dst = fs.getPath("/tmp/vavi.nio.file.googledrive.Main7-" + System.currentTimeMillis()); + + // revision 1 + Files.copy(src, dst); + + Thread.sleep(100); + + // revision 2 + // copy options don't be passed to the upload method of file system driver... + Files.copy(src, Files.newOutputStream(dst, GoogleDriveOpenOption.INPORT_AS_NEW_REVISION)); + + byte[] in = (byte[]) Files.getAttribute(dst, "user:revisions"); + String[] revisions = RevisionsUtil.split(in); + assertEquals(2, revisions.length); +System.err.println(dst); +Arrays.stream(revisions).forEach(System.err::println); + + // revision latest + byte[] out = RevisionsUtil.getLatestOnly(in); + Files.setAttribute(dst, "user:revisions", out); + + in = (byte[]) Files.getAttribute(dst, "user:revisions"); + revisions = RevisionsUtil.split(in); + assertEquals(1, revisions.length); +System.err.println("---- latest ---"); +Arrays.stream(revisions).forEach(System.err::println); + + // clean up + Files.delete(dst); + + fs.close(); + } + + // + + public static void main(String[] args) throws IOException { + t1(args); + } + + /** list */ + static void t2(String[] args) throws IOException { + String email = System.getenv("GOOGLE_TEST_ACCOUNT"); + + URI uri = URI.create("googledrive:///?id=" + email); + FileSystem fs = new GoogleDriveFileSystemProvider().newFileSystem(uri, Collections.emptyMap()); + + Files.list(fs.getPath("/tmp")).forEach(p -> { + try { + if (!Files.isDirectory(p)) { + System.err.println(p + "\n" + new String((byte[]) Files.getAttribute(p, "user:revisions"))); + } + } catch (IOException e) { + System.err.println(e); + } + }); + + fs.close(); + } + + /** remove all remaining latest */ + static void t3(String[] args) throws IOException { + String email = System.getenv("GOOGLE_TEST_ACCOUNT"); + + URI uri = URI.create("googledrive:///?id=" + email); + FileSystem fs = new GoogleDriveFileSystemProvider().newFileSystem(uri, Collections.emptyMap()); + Path path = fs.getPath("/tmp/heroku-blog-duke.png"); + +System.err.println("--- before ---"); + byte[] in = (byte[]) Files.getAttribute(path, "user:revisions"); + byte[] out = RevisionsUtil.getLatestOnly(in); +System.err.println("--- write ---"); +System.err.println(new String(out)); + Files.setAttribute(path, "user:revisions", out); + +System.err.println("--- after ---"); + in = (byte[]) Files.getAttribute(path, "user:revisions"); + String[] as = RevisionsUtil.split(in); +Arrays.stream(as).forEach(System.err::println); + + fs.close(); + } + + static class MyFileVisitor extends SimpleFileVisitor { + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attr) { + if (attr.isRegularFile()) { + try { + int size = RevisionsUtil.size(Files.getAttribute(file, "user:revisions")); + if (size > 1) { + System.err.println(file + ": " + size); + +System.err.println("--- before ---"); + byte[] in = (byte[]) Files.getAttribute(file, "user:revisions"); + byte[] out = RevisionsUtil.getLatestOnly(in); +System.err.println("--- write ---"); +System.err.println(new String(out)); + Files.setAttribute(file, "user:revisions", out); + +System.err.println("--- after ---"); + in = (byte[]) Files.getAttribute(file, "user:revisions"); + String[] as = RevisionsUtil.split(in); +Arrays.stream(as).forEach(System.err::println); +System.err.println("\n"); + } + } catch (IOException e) { + System.err.println(e); + } + } + return CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) { + return CONTINUE; + } + + @Override + public FileVisitResult visitFileFailed(Path file, IOException exc) { + System.err.println(exc); + return CONTINUE; + } + } + + /** remove all remaining latest, tree */ + static void t1(String[] args) throws IOException { + String email = System.getenv("GOOGLE_TEST_ACCOUNT"); + + URI uri = URI.create("googledrive:///?id=" + email); + FileSystem fs = new GoogleDriveFileSystemProvider().newFileSystem(uri, Collections.emptyMap()); + + Files.walkFileTree(fs.getPath("/Books/Comics/"), new MyFileVisitor()); + + fs.close(); +Debug.println("Done"); + } +} \ No newline at end of file diff --git a/vavi-nio-file-googledrive/src/test/java/vavi/nio/file/googledrive/Main9.java b/vavi-nio-file-googledrive/src/test/java/vavi/nio/file/googledrive/Main9.java new file mode 100644 index 00000000..446bba06 --- /dev/null +++ b/vavi-nio-file-googledrive/src/test/java/vavi/nio/file/googledrive/Main9.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2022 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package vavi.nio.file.googledrive; + +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collections; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertNotEquals; + + +/** + * GoogleDrive attribute user:thumbnail + * + * @author Naohide Sano (umjammer) + * @version 0.00 2022/01/26 umjammer initial version
+ */ +class Main9 { + + @Test + void test01() throws Exception { + String email = System.getenv("GOOGLE_TEST_ACCOUNT"); + + URI uri = URI.create("googledrive:///?id=" + email); + FileSystem fs = new GoogleDriveFileSystemProvider().newFileSystem(uri, Collections.emptyMap()); + + // write + byte[] bytes = Files.readAllBytes(Paths.get(Main9.class.getResource("/duke.jpg").toURI())); + + Path sourcePath = Paths.get(Main9.class.getResource("/test.zip").toURI()); + +// String name = "/tmp/test.zip"; + String name = "/tmp/test" + System.currentTimeMillis() + ".zip"; + Path targetPath = fs.getPath(name); + + Files.copy(sourcePath, targetPath); + + Thread.sleep(10000); + + Files.setAttribute(targetPath, "user:thumbnail", bytes); + + int retry = 3; + while (retry > 0) { + bytes = (byte[]) Files.getAttribute(targetPath, "user:thumbnail"); + if (bytes.length == 0) { + fs.close(); + + fs = new GoogleDriveFileSystemProvider().newFileSystem(uri, Collections.emptyMap()); + + targetPath = fs.getPath(name); + + Thread.sleep(10000); + + Files.setAttribute(targetPath, "user:thumbnail", bytes); + + retry--; + } else { + break; + } + } + + fs.close(); + + Thread.sleep(1000); + + // read + fs = new GoogleDriveFileSystemProvider().newFileSystem(uri, Collections.emptyMap()); + + targetPath = fs.getPath(name); + + bytes = (byte[]) Files.getAttribute(targetPath, "user:thumbnail"); + + Files.delete(targetPath); + fs.close(); + + assertNotEquals(0, bytes.length); + Files.write(Paths.get("tmp/thumbnail.jpg"), bytes); + } +} + +/* */ diff --git a/vavi-nio-file-googledrive/src/test/java/vavi/nio/file/googledrive/WatchDirectoryTest.java b/vavi-nio-file-googledrive/src/test/java/vavi/nio/file/googledrive/WatchDirectoryTest.java new file mode 100644 index 00000000..27c963ee --- /dev/null +++ b/vavi-nio-file-googledrive/src/test/java/vavi/nio/file/googledrive/WatchDirectoryTest.java @@ -0,0 +1,164 @@ +/* + * https://github.com/bbejeck/Java-7/blob/cf0256be2122d9063e21743fb01bd19f07feef69/src/test/java/bbejeck/nio/files/watch/WatchDirectoryTest.java + */ + +package vavi.nio.file.googledrive; + +import java.io.IOException; +import java.io.PrintWriter; +import java.net.URI; +import java.nio.charset.Charset; +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardWatchEventKinds; +import java.nio.file.WatchEvent; +import java.nio.file.WatchKey; +import java.nio.file.WatchService; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static vavi.nio.file.Base.removeTree; + +/** + * Created by IntelliJ IDEA. + * User: bbejeck + * Date: 2/13/12 + * Time: 9:47 PM + */ +@Disabled +public class WatchDirectoryTest { + + static Path dir1Path; + + private WatchService watchService; + private WatchKey basePathWatchKey; + + static FileSystem fs; + + static Path basePath; + + @BeforeAll + static void before() throws IOException { + String email = System.getenv("GOOGLE_TEST_ACCOUNT"); + + URI uri = URI.create("googledrive:///?id=" + email); + fs = new GoogleDriveFileSystemProvider().newFileSystem(uri, Collections.emptyMap()); + + basePath = Files.createTempDirectory(fs.getRootDirectories().iterator().next(), "VAVIFUSE-TEST-WATCHSERVICE-"); + dir1Path = basePath.resolve("dir1"); + } + + @AfterAll + static void afterAll() throws Exception { + Files.delete(basePath); + } + + @BeforeEach + public void setUo() throws Exception { + watchService = fs.newWatchService(); + basePathWatchKey = basePath.register(watchService,ENTRY_CREATE); + } + + @AfterEach + public void after() throws Exception { + watchService.close(); + removeTree(basePath, false); + } + + @Test + public void testEventForDirectory() throws Exception { + generateFile(basePath.resolve("newTextFile.txt"), 10); + generateFile(basePath.resolve("newTextFileII.txt"), 10); + generateFile(basePath.resolve("newTextFileIII.txt"), 10); + WatchKey watchKey = watchService.poll(20, TimeUnit.SECONDS); + assertNotNull(watchKey); + assertEquals(basePathWatchKey, watchKey); + List> eventList = watchKey.pollEvents(); + assertEquals(3, eventList.size()); + for (WatchEvent event : eventList) { + assertTrue(event.kind() == StandardWatchEventKinds.ENTRY_CREATE); + assertEquals(1, event.count()); + } + Path eventPath = (Path) eventList.get(0).context(); + assertTrue(Files.isSameFile(eventPath, Paths.get("newTextFile.txt"))); + Path watchedPath = (Path) watchKey.watchable(); + assertTrue(Files.isSameFile(watchedPath, basePath)); + } + + @Test + public void testEventForDirectoryWatchKey() throws Exception { + generateFile(basePath.resolve("newTextFile.txt"), 10); + List> eventList = basePathWatchKey.pollEvents(); + while (eventList.size() == 0 ){ + eventList = basePathWatchKey.pollEvents(); + Thread.sleep(10000); + } + assertEquals(1, eventList.size()); + for (WatchEvent event : eventList) { + assertTrue(event.kind() == StandardWatchEventKinds.ENTRY_CREATE); + } + basePathWatchKey.reset(); + generateFile(basePath.resolve("newTextFileII.txt"), 10); + generateFile(basePath.resolve("newTextFileIII.txt"), 10); + while (eventList.size() == 0 ){ + eventList = basePathWatchKey.pollEvents(); + Thread.sleep(10000); + } + Path eventPath = (Path) eventList.get(0).context(); + assertTrue(Files.isSameFile(eventPath, Paths.get("newTextFile.txt"))); + Path watchedPath = (Path) basePathWatchKey.watchable(); + assertTrue(Files.isSameFile(watchedPath, basePath)); + } + + + + @Test + public void testEventForSubDirectory() throws Exception { + dir1Path.register(watchService, StandardWatchEventKinds.ENTRY_CREATE); + generateFile(basePath.resolve("newTextFile.txt"), 10); + generateFile(dir1Path.resolve("newTextFile.txt"), 10); + int count = 0; + while (count < 2) { + WatchKey watchKey = watchService.poll(20, TimeUnit.SECONDS); + @SuppressWarnings("unused") + Path watchedPath = (Path) watchKey.watchable(); + assertNotNull(watchKey); + List> eventList = watchKey.pollEvents(); + WatchEvent event = eventList.get(0); + assertEquals(1, event.count()); + assertTrue(event.kind() == StandardWatchEventKinds.ENTRY_CREATE); + assertTrue(Files.isSameFile((Path) event.context(), Paths.get("newTextFile.txt"))); + count++; + } + } + + private static final String LINE_OF_TEXT = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do " + + "eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad " + + "minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea " + + "commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit" + + " esse cillum dolore eu fugiat nulla pariatur. " + + "Excepteur sint occaecat cupidatat non proident, sunt " + + "in culpa qui officia deserunt mollit anim id est laborum."; + + public static void generateFile(Path path, int lines) throws Exception { + try(PrintWriter printWriter = new PrintWriter(Files.newBufferedWriter(path, Charset.defaultCharset()))){ + for(int i = 0; i< lines; i++){ + printWriter.println(LINE_OF_TEXT); + } + } + } +} diff --git a/vavi-nio-file-googledrive/src/test/java/vavi/nio/file/googledrive/WatchServiceTest.java b/vavi-nio-file-googledrive/src/test/java/vavi/nio/file/googledrive/WatchServiceTest.java new file mode 100644 index 00000000..9a18510e --- /dev/null +++ b/vavi-nio-file-googledrive/src/test/java/vavi/nio/file/googledrive/WatchServiceTest.java @@ -0,0 +1,266 @@ +/* + * Copyright 2013 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package vavi.nio.file.googledrive; + +import java.io.IOException; +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.NotDirectoryException; +import java.nio.file.Path; +import java.nio.file.WatchEvent; +import java.nio.file.WatchEvent.Kind; +import java.nio.file.WatchKey; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +import com.github.fge.filesystem.watch.AbstractWatchService; +import com.google.common.collect.ImmutableList; +import com.google.common.util.concurrent.Uninterruptibles; + +import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE; +import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE; +import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertIterableEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + + +/** + * Tests for {@link GoogleDriveWatchService}. + * + * @author Colin Decker + */ +@Disabled +public class WatchServiceTest { + + private FileSystem fs; + + private AbstractWatchService watcher; + + @BeforeEach + public void setUp() throws IOException { + String email = System.getenv("GOOGLE_TEST_ACCOUNT"); + + URI uri = URI.create("googledrive:///?id=" + email); + fs = new GoogleDriveFileSystemProvider().newFileSystem(uri, Collections.emptyMap()); + + watcher = (AbstractWatchService) fs.newWatchService(); + } + + @AfterEach + public void tearDown() throws IOException { + watcher.close(); + fs.close(); + watcher = null; + fs = null; + } + + @Test + public void testNewWatcher() { +// assertTrue(watcher.isOpen()); +// assertFalse(watcher.isPolling()); + } + + @Test + public void testRegister() throws IOException { + WatchKey key = watcher.register(createDirectory(), ImmutableList.of(ENTRY_CREATE)); + assertTrue(key.isValid()); + +// assertTrue(watcher.isPolling()); + } + + @Test + public void testRegister_fileDoesNotExist() throws IOException { + try { + watcher.register(fs.getPath("/a/b/c"), ImmutableList.of(ENTRY_CREATE)); + fail(); + } catch (NoSuchFileException expected) { + } + } + + @Test + public void testRegister_fileIsNotDirectory() throws IOException { + Path path = fs.getPath("/a.txt"); + Files.createFile(path); + try { + watcher.register(path, ImmutableList.of(ENTRY_CREATE)); + fail(); + } catch (NotDirectoryException expected) { + } + } + + @Test + public void testCancellingLastKeyStopsPolling() throws IOException { + WatchKey key = watcher.register(createDirectory(), ImmutableList.of(ENTRY_CREATE)); + key.cancel(); + assertFalse(key.isValid()); + +// assertFalse(watcher.isPolling()); + + WatchKey key2 = watcher.register(createDirectory(), ImmutableList.of(ENTRY_CREATE)); + WatchKey key3 = watcher.register(createDirectory(), ImmutableList.of(ENTRY_DELETE)); + +// assertTrue(watcher.isPolling()); + + key2.cancel(); + +// assertTrue(watcher.isPolling()); + + key3.cancel(); + +// assertFalse(watcher.isPolling()); + } + + @Test + public void testCloseCancelsAllKeysAndStopsPolling() throws IOException { + WatchKey key1 = watcher.register(createDirectory(), ImmutableList.of(ENTRY_CREATE)); + WatchKey key2 = watcher.register(createDirectory(), ImmutableList.of(ENTRY_DELETE)); + + assertTrue(key1.isValid()); + assertTrue(key2.isValid()); +// assertTrue(watcher.isPolling()); + + watcher.close(); + + assertFalse(key1.isValid()); + assertFalse(key2.isValid()); +// assertFalse(watcher.isPolling()); + } + + @Test + @Timeout(30000) + public void testWatchForOneEventType() throws IOException, InterruptedException { + Path path = createDirectory(); + watcher.register(path, ImmutableList.of(ENTRY_CREATE)); + + Files.createFile(path.resolve("foo")); + + assertWatcherHasEvents(HackedAbstractWatchService.createWatchEvent(ENTRY_CREATE, 1, fs.getPath("foo"))); + + Files.createFile(path.resolve("bar")); + Files.createFile(path.resolve("baz")); + + assertWatcherHasEvents(HackedAbstractWatchService.createWatchEvent(ENTRY_CREATE, 1, fs.getPath("bar")), + HackedAbstractWatchService.createWatchEvent(ENTRY_CREATE, 1, fs.getPath("baz"))); + } + + @Test + @Timeout(30000) + public void testWatchForMultipleEventTypes() throws IOException, InterruptedException { + Path path = createDirectory(); + watcher.register(path, ImmutableList.of(ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY)); + + Files.createDirectory(path.resolve("foo")); + Files.createFile(path.resolve("bar")); + + assertWatcherHasEvents(HackedAbstractWatchService.createWatchEvent(ENTRY_CREATE, 1, fs.getPath("bar")), + HackedAbstractWatchService.createWatchEvent(ENTRY_CREATE, 1, fs.getPath("foo"))); + + Files.createFile(path.resolve("baz")); + Files.delete(path.resolve("bar")); + Files.createFile(path.resolve("foo/bar")); + + assertWatcherHasEvents(HackedAbstractWatchService.createWatchEvent(ENTRY_CREATE, 1, fs.getPath("baz")), + HackedAbstractWatchService.createWatchEvent(ENTRY_DELETE, 1, fs.getPath("bar")), + HackedAbstractWatchService.createWatchEvent(ENTRY_MODIFY, 1, fs.getPath("foo"))); + + Files.delete(path.resolve("foo/bar")); + ensureTimeToPoll(); // watcher polls, seeing modification, then polls + // again, seeing delete + Files.delete(path.resolve("foo")); + + assertWatcherHasEvents(HackedAbstractWatchService.createWatchEvent(ENTRY_MODIFY, 1, fs.getPath("foo")), + HackedAbstractWatchService.createWatchEvent(ENTRY_DELETE, 1, fs.getPath("foo"))); + + Files.createDirectories(path.resolve("foo/bar")); + + // polling here may either see just the creation of foo, or may first + // see the creation of foo + // and then the creation of foo/bar (modification of foo) since those + // don't happen atomically + assertWatcherHasEvents(ImmutableList + .> of(HackedAbstractWatchService.createWatchEvent(ENTRY_CREATE, 1, fs.getPath("foo"))), + // or + ImmutableList.> of( + HackedAbstractWatchService + .createWatchEvent(ENTRY_CREATE, 1, fs.getPath("foo")), + HackedAbstractWatchService + .createWatchEvent(ENTRY_MODIFY, 1, fs.getPath("foo")))); + + Files.delete(path.resolve("foo/bar")); + Files.delete(path.resolve("foo")); + + // polling here may either just see the deletion of foo, or may first + // see the deletion of bar + // (modification of foo) and then the deletion of foo + assertWatcherHasEvents(ImmutableList + .> of(HackedAbstractWatchService.createWatchEvent(ENTRY_DELETE, 1, fs.getPath("foo"))), + // or + ImmutableList.> of( + HackedAbstractWatchService + .createWatchEvent(ENTRY_MODIFY, 1, fs.getPath("foo")), + HackedAbstractWatchService + .createWatchEvent(ENTRY_DELETE, 1, fs.getPath("foo")))); + } + + private void assertWatcherHasEvents(WatchEvent... events) throws InterruptedException { + assertWatcherHasEvents(Arrays.asList(events), ImmutableList.> of()); + } + + private void assertWatcherHasEvents(List> expected, + List> alternate) throws InterruptedException { + ensureTimeToPoll(); // otherwise we could read 1 event but not all the + // events we're expecting + WatchKey key = watcher.take(); + List> keyEvents = key.pollEvents(); + + if (keyEvents.size() == expected.size() || alternate.isEmpty()) { + assertIterableEquals(expected, keyEvents); + } else { + assertIterableEquals(alternate, keyEvents); + } + key.reset(); + } + + private static void ensureTimeToPoll() { + Uninterruptibles.sleepUninterruptibly(40, MILLISECONDS); + } + + private Path createDirectory() throws IOException { + Path path = fs.getPath("/" + UUID.randomUUID().toString()); + Files.createDirectory(path); + return path; + } + + static class HackedAbstractWatchService extends AbstractWatchService { + public static WatchEvent createWatchEvent(Kind kind, int count, T context) { + return new BasicWatchEvent<>(kind, count, context); + } + } +} diff --git a/vavi-nio-file-googledrive/src/test/java/vavi/nio/file/googledrive/WebHookTest.java b/vavi-nio-file-googledrive/src/test/java/vavi/nio/file/googledrive/WebHookTest.java new file mode 100644 index 00000000..0a798335 --- /dev/null +++ b/vavi-nio-file-googledrive/src/test/java/vavi/nio/file/googledrive/WebHookTest.java @@ -0,0 +1,244 @@ +/* + * Copyright (c) 2020 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package vavi.nio.file.googledrive; + +import java.io.IOException; +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Base64; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import javax.websocket.ClientEndpoint; +import javax.websocket.ClientEndpointConfig; +import javax.websocket.ContainerProvider; +import javax.websocket.DeploymentException; +import javax.websocket.OnClose; +import javax.websocket.OnError; +import javax.websocket.OnMessage; +import javax.websocket.OnOpen; +import javax.websocket.Session; +import javax.websocket.WebSocketContainer; + +import com.google.api.client.auth.oauth2.Credential; +import com.google.api.client.googleapis.notifications.UnparsedNotification; +import com.google.api.client.http.HttpRequest; +import com.google.api.client.http.HttpRequestInitializer; +import com.google.api.services.drive.Drive; +import com.google.api.services.drive.model.Change; +import com.google.api.services.drive.model.ChangeList; +import com.google.api.services.drive.model.Channel; +import com.google.api.services.drive.model.StartPageToken; + +import vavi.net.auth.WithTotpUserCredential; +import vavi.net.auth.oauth2.google.GoogleOAuth2AppCredential; +import vavi.net.auth.oauth2.google.GoogleLocalOAuth2AppCredential; +import vavi.net.auth.oauth2.google.GoogleOAuth2; +import vavi.net.auth.web.google.GoogleLocalUserCredential; +import vavi.nio.file.googledrive.webhook.websocket.GoogleJsonCodec.GoogleJsonDecoder; +import vavi.nio.file.googledrive.webhook.websocket.GoogleJsonCodec.GoogleJsonEncoder; +import vavi.util.Debug; + + +/** + * WebHookTest. google drive + * + * @depends "file://${HOME}.vavifuse/googledrive/?" + * + * @author Naohide Sano (umjammer) + * @version 0.00 2016/02/29 umjammer initial version
+ */ +public class WebHookTest { + + static final String VAVI_APPS_WEBHOOK_SECRET = System.getenv("VAVI_APPS_WEBHOOK_SECRET"); + + @ClientEndpoint(decoders = GoogleJsonDecoder.class, + encoders = GoogleJsonEncoder.class, + configurator = AuthorizationConfigurator.class) + public static class NotificationClient { + Service service; + NotificationClient(Service service) { + this.service = service; + } + @OnOpen + public void onOpen(Session session) throws IOException { +Debug.println("OPEN: " + session); + session.getBasicRemote().sendText(String.join(" ", "GOOGLE_DRIVE_CHANGE", service.uuid.toString())); + } + + @OnMessage + public void onMessage(UnparsedNotification notification) throws IOException { +Debug.println(notification); + service.process(notification); + } + + @OnError + public void onError(Throwable t) { +t.printStackTrace(); + } + + @OnClose + public void onClose(Session session) throws IOException { +Debug.println("CLOSE"); + service.stop(); + } + } + + static String websocketBaseUrl = System.getenv("VAVI_APPS_WEBHOOK_WEBSOCKET_BASE_URL"); + static String websocketPath = System.getenv("VAVI_APPS_WEBHOOK_WEBSOCKET_GOOGLE_PATH"); + static String webhooktUrl = System.getenv("VAVI_APPS_WEBHOOK_WEBHOOK_GOOGLE_URL"); + static String username = System.getenv("VAVI_APPS_WEBHOOK_USERNAME"); + static String password = System.getenv("VAVI_APPS_WEBHOOK_PASSWORD"); + + public static class AuthorizationConfigurator extends ClientEndpointConfig.Configurator { + @Override + public void beforeRequest(Map> headers) { + headers.put("Authorization", Arrays.asList("Basic " + Base64.getEncoder().encodeToString((username + ":" + password).getBytes()))); + } + }; + + static class Service { + Drive driveService; + String savedStartPageToken; + Channel channel = null; + Session session; + UUID uuid; + + Service() throws IOException { + WithTotpUserCredential userCredential = new GoogleLocalUserCredential(email); + GoogleOAuth2AppCredential appCredential = new GoogleLocalOAuth2AppCredential("googledrive"); + + Credential credential = new GoogleOAuth2(appCredential).authorize(userCredential); + driveService = new Drive.Builder(GoogleOAuth2.getHttpTransport(), GoogleOAuth2.getJsonFactory(), credential) + .setHttpRequestInitializer(new HttpRequestInitializer() { + @Override + public void initialize(HttpRequest httpRequest) throws IOException { + credential.initialize(httpRequest); + httpRequest.setConnectTimeout(30 * 1000); + httpRequest.setReadTimeout(30 * 1000); + } + }) + .setApplicationName(appCredential.getClientId()) + .build(); + } + + void start() throws IOException { + uuid = UUID.randomUUID(); + + try { + WebSocketContainer container = ContainerProvider.getWebSocketContainer(); + URI uri = URI.create(websocketBaseUrl + websocketPath); + // TODO using client ssl is so annoying + // https://github.com/eclipse/jetty.project/issues/155 +// URI uri = URI.create(String.format("ws://localhost:5000" + websocketPath)); + session = container.connectToServer(new NotificationClient(this), uri); +// session.setMaxIdleTimeout(0); + } catch (DeploymentException e) { + throw new IOException(e); + } + + StartPageToken response = driveService.changes().getStartPageToken().execute(); + savedStartPageToken = response.getStartPageToken(); +Debug.println("Start token: " + savedStartPageToken); + + Channel content = new Channel() + .setId(uuid.toString()) + .setType("web_hook") + .setToken(VAVI_APPS_WEBHOOK_SECRET) + .setAddress(webhooktUrl); + + channel = driveService.changes().watch(savedStartPageToken, content).execute(); +Debug.println("channel: " + channel); + } + + void stop() throws IOException { + if (channel != null) { + driveService.channels().stop(channel).execute(); +Debug.println("channel deleted: " + channel); + channel = null; + } + + if (session != null) { + session.close(); + session = null; + } + } + + void process(UnparsedNotification notification) throws IOException { +Debug.println("notification: " + notification.getResourceState()); + // Begin with our last saved start token for this user or the + // current token from getStartPageToken() + String pageToken = savedStartPageToken; + while (pageToken != null) { + ChangeList changes = driveService.changes().list(pageToken).execute(); + for (Change change : changes.getChanges()) { + // Process change +Debug.println("Change found for file: " + change); + } + if (changes.getNewStartPageToken() != null) { + // Last page, save this token for the next polling interval + savedStartPageToken = changes.getNewStartPageToken(); + } + pageToken = changes.getNextPageToken(); + } + } + } + + static String email = System.getenv("GOOGLE_TEST_ACCOUNT"); + + /** + * @param args 0: email + * + * https://developers.google.com/drive/api/v3/reference/changes/watch + * https://stackoverflow.com/a/43793313/6102938 + * TODO needs domain authorize + */ + public static void main(String[] args) throws Exception { + WebHookTest app = new WebHookTest(); + app.test(); + } + + void test() throws Exception { +Debug.println("Start"); + Service service = new Service(); + try { + service.start(); + + URI uri = URI.create("googledrive:///?id=" + email); + FileSystem fs = FileSystems.newFileSystem(uri, Collections.emptyMap()); + + Path tmpDir = fs.getPath("tmp"); + if (!Files.exists(tmpDir)) { +System.out.println("rmdir " + tmpDir); + Files.createDirectory(tmpDir); + } + Path remote = tmpDir.resolve("Test+Watch"); + if (Files.exists(remote)) { +System.out.println("rm " + remote); + Files.delete(remote); + } + Path source = Paths.get("src/test/resources", "Hello.java"); +System.out.println("cp " + source + " " + remote); + Files.copy(source, remote); + +System.out.println("rm " + remote); + Files.delete(remote); + + Thread.sleep(5000); + } finally { + service.stop(); + } +Debug.println("Done"); + } +} diff --git a/vavi-nio-file-googledrive/src/test/java/vavi/nio/file/googledrive/WebHookTest2.java b/vavi-nio-file-googledrive/src/test/java/vavi/nio/file/googledrive/WebHookTest2.java new file mode 100644 index 00000000..0ad8822d --- /dev/null +++ b/vavi-nio-file-googledrive/src/test/java/vavi/nio/file/googledrive/WebHookTest2.java @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2020 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package vavi.nio.file.googledrive; + +import java.io.IOException; +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collections; +import java.util.UUID; +import java.util.concurrent.CountDownLatch; + +import com.google.api.client.auth.oauth2.Credential; +import com.google.api.client.googleapis.notifications.UnparsedNotification; +import com.google.api.client.http.HttpRequest; +import com.google.api.client.http.HttpRequestInitializer; +import com.google.api.services.drive.Drive; +import com.google.api.services.drive.model.Change; +import com.google.api.services.drive.model.ChangeList; +import com.google.api.services.drive.model.Channel; +import com.google.api.services.drive.model.StartPageToken; + +import vavi.net.auth.WithTotpUserCredential; +import vavi.net.auth.oauth2.google.GoogleOAuth2AppCredential; +import vavi.net.auth.oauth2.google.GoogleLocalOAuth2AppCredential; +import vavi.net.auth.oauth2.google.GoogleOAuth2; +import vavi.net.auth.web.google.GoogleLocalUserCredential; +import vavi.nio.file.watch.webhook.Notification; +import vavi.util.Debug; + + +/** + * WebHookTest2. google drive, using now construction libraries. + * + * @depends "file://${HOME}.vavifuse/googledrive/?" + * + * @author Naohide Sano (umjammer) + * @version 0.00 2016/02/29 umjammer initial version
+ */ +public class WebHookTest2 { + + static final String VAVI_APPS_WEBHOOK_SECRET = System.getenv("VAVI_APPS_WEBHOOK_SECRET"); + static String webhooktUrl = System.getenv("VAVI_APPS_WEBHOOK_WEBHOOK_GOOGLE_URL"); + + static CountDownLatch countDownLatch = new CountDownLatch(1); + + static class Service { + Drive driveService; + String savedStartPageToken; + Channel channel = null; + Notification notification; + + Service() throws IOException { + WithTotpUserCredential userCredential = new GoogleLocalUserCredential(email); + GoogleOAuth2AppCredential appCredential = new GoogleLocalOAuth2AppCredential("googledrive"); + + Credential credential = new GoogleOAuth2(appCredential).authorize(userCredential); + driveService = new Drive.Builder(GoogleOAuth2.getHttpTransport(), GoogleOAuth2.getJsonFactory(), credential) + .setHttpRequestInitializer(new HttpRequestInitializer() { + @Override + public void initialize(HttpRequest httpRequest) throws IOException { + credential.initialize(httpRequest); + httpRequest.setConnectTimeout(30 * 1000); + httpRequest.setReadTimeout(30 * 1000); + } + }) + .setApplicationName(appCredential.getClientId()) + .build(); + } + + void start() throws IOException { + UUID uuid = UUID.randomUUID(); + + notification = Notification.getNotification("googledrive.webhook.websocket", notification -> { + try { + process(notification); + } catch (IOException e) { + throw new IllegalStateException(e); + } + }, uuid); +Debug.println("notification: " + notification); + + StartPageToken response = driveService.changes().getStartPageToken().execute(); + savedStartPageToken = response.getStartPageToken(); +Debug.println("Start token: " + savedStartPageToken); + + Channel content = new Channel() + .setId(uuid.toString()) + .setType("web_hook") + .setToken(VAVI_APPS_WEBHOOK_SECRET) + .setAddress(webhooktUrl); + + channel = driveService.changes().watch(savedStartPageToken, content).execute(); +Debug.println("channel: " + channel); + } + + void stop() throws IOException { + if (channel != null) { + driveService.channels().stop(channel).execute(); +Debug.println("channel deleted: " + channel); + channel = null; + } + notification.close(); + } + + private void process(UnparsedNotification notification) throws IOException { +Debug.println(">> notification: " + notification.getResourceState()); + if (notification.getResourceState().equals("sync")) { + countDownLatch.countDown(); +Debug.println(">> synched"); + } else { + // Begin with our last saved start token for this user or the + // current token from getStartPageToken() + String pageToken = savedStartPageToken; + while (pageToken != null) { + ChangeList changes = driveService.changes().list(pageToken).execute(); + for (Change change : changes.getChanges()) { + // Process change +Debug.println(">> change: " + change); + } + if (changes.getNewStartPageToken() != null) { + // Last page, save this token for the next polling interval + savedStartPageToken = changes.getNewStartPageToken(); + } + pageToken = changes.getNextPageToken(); + } + } + } + } + + static String email = System.getenv("GOOGLE_TEST_ACCOUNT"); + + /** + * @param args 0: email + * + * https://developers.google.com/drive/api/v3/reference/changes/watch + * https://stackoverflow.com/a/43793313/6102938 + * TODO needs domain authorize + */ + public static void main(String[] args) throws Exception { + WebHookTest2 app = new WebHookTest2(); + app.test(); + } + + void test() throws Exception { +Debug.println("Start"); + Service service = new Service(); + try { + service.start(); + + URI uri = URI.create("googledrive:///?id=" + email); + FileSystem fs = FileSystems.newFileSystem(uri, Collections.emptyMap()); + + countDownLatch.await(); + + Path tmpDir = fs.getPath("tmp"); + if (!Files.exists(tmpDir)) { +System.out.println("rmdir " + tmpDir); + Files.createDirectory(tmpDir); + } + Path remote = tmpDir.resolve("Test+Watch"); + if (Files.exists(remote)) { +System.out.println("rm " + remote); + Files.delete(remote); + } + Path source = Paths.get("src/test/resources", "Hello.java"); +System.out.println("cp " + source + " " + remote); + Files.copy(source, remote); + +System.out.println("rm " + remote); + Files.delete(remote); + + Thread.sleep(10000); + + } catch (Exception e) { + e.printStackTrace(); + } finally { + service.stop(); + } +Debug.println("Done"); +//Thread.getAllStackTraces().keySet().forEach(System.err::println); + } +} diff --git a/vavi-nio-file-googledrive/src/test/java/vavi/nio/file/googledrive/WebHookTest3.java b/vavi-nio-file-googledrive/src/test/java/vavi/nio/file/googledrive/WebHookTest3.java new file mode 100644 index 00000000..850608cb --- /dev/null +++ b/vavi-nio-file-googledrive/src/test/java/vavi/nio/file/googledrive/WebHookTest3.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2020 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package vavi.nio.file.googledrive; + +import java.io.IOException; +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collections; + +import com.google.api.client.auth.oauth2.Credential; +import com.google.api.client.http.HttpRequest; +import com.google.api.client.http.HttpRequestInitializer; +import com.google.api.services.drive.Drive; + +import vavi.net.auth.WithTotpUserCredential; +import vavi.net.auth.oauth2.google.GoogleOAuth2AppCredential; +import vavi.net.auth.oauth2.google.GoogleLocalOAuth2AppCredential; +import vavi.net.auth.oauth2.google.GoogleOAuth2; +import vavi.net.auth.web.google.GoogleLocalUserCredential; +import vavi.util.Debug; + + +/** + * WebHookTest3. google drive, using libraries now construction. + * + * @depends "file://${HOME}.vavifuse/googledrive/?" + * + * @author Naohide Sano (umjammer) + * @version 0.00 2016/02/29 umjammer initial version
+ */ +public class WebHookTest3 { + + static String email = System.getenv("GOOGLE_TEST_ACCOUNT"); + + /** + * @param args 0: email + * + * https://developers.google.com/drive/api/v3/reference/changes/watch + * https://stackoverflow.com/a/43793313/6102938 + * TODO needs domain authorize + */ + public static void main(String[] args) throws Exception { + WebHookTest3 app = new WebHookTest3(); + app.test(); + } + + void test() throws Exception { + WithTotpUserCredential userCredential = new GoogleLocalUserCredential(email); + GoogleOAuth2AppCredential appCredential = new GoogleLocalOAuth2AppCredential("googledrive"); + + Credential credential = new GoogleOAuth2(appCredential).authorize(userCredential); + Drive driveService = new Drive.Builder(GoogleOAuth2.getHttpTransport(), GoogleOAuth2.getJsonFactory(), credential) + .setHttpRequestInitializer(new HttpRequestInitializer() { + @Override + public void initialize(HttpRequest httpRequest) throws IOException { + credential.initialize(httpRequest); + httpRequest.setConnectTimeout(30 * 1000); + httpRequest.setReadTimeout(30 * 1000); + } + }) + .setApplicationName(appCredential.getClientId()) + .build(); + + GoogleDriveWatchService service = new GoogleDriveWatchService(driveService); +Debug.println("WEBSOCKET: start: " + service); + try { + URI uri = URI.create("googledrive:///?id=" + email); + FileSystem fs = FileSystems.newFileSystem(uri, Collections.emptyMap()); + + Path tmpDir = fs.getPath("tmp"); + if (!Files.exists(tmpDir)) { +System.out.println("rmdir " + tmpDir); + Files.createDirectory(tmpDir); + } + Path remote = tmpDir.resolve("Test+Watch"); + if (Files.exists(remote)) { +System.out.println("rm " + remote); + Files.delete(remote); + } + Path source = Paths.get("src/test/resources", "Hello.java"); +System.out.println("cp " + source + " " + remote); + Files.copy(source, remote); + +System.out.println("rm " + remote); + Files.delete(remote); + + Thread.sleep(10000); + + } catch (Exception e) { + e.printStackTrace(); + } finally { + service.close(); + } +Debug.println("APP: done"); + GoogleDriveWatchService.dispose(); +//Thread.getAllStackTraces().keySet().forEach(System.err::println); + } +} diff --git a/vavi-nio-file-googledrive/src/test/java/vavi/nio/file/googledrive/WebSocketTest.java b/vavi-nio-file-googledrive/src/test/java/vavi/nio/file/googledrive/WebSocketTest.java new file mode 100644 index 00000000..999d800d --- /dev/null +++ b/vavi-nio-file-googledrive/src/test/java/vavi/nio/file/googledrive/WebSocketTest.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2020 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package vavi.nio.file.googledrive; + +import java.net.URI; +import java.util.concurrent.CountDownLatch; + +import javax.websocket.ClientEndpoint; +import javax.websocket.ContainerProvider; +import javax.websocket.OnClose; +import javax.websocket.OnError; +import javax.websocket.OnMessage; +import javax.websocket.OnOpen; +import javax.websocket.Session; +import javax.websocket.WebSocketContainer; + +import com.google.api.client.googleapis.notifications.UnparsedNotification; + +import vavi.nio.file.googledrive.WebHookTest.AuthorizationConfigurator; +import vavi.nio.file.googledrive.webhook.websocket.GoogleJsonCodec.GoogleJsonDecoder; +import vavi.nio.file.googledrive.webhook.websocket.GoogleJsonCodec.GoogleJsonEncoder; +import vavi.util.Debug; + + +/** + * WebSocketTest. + * + * @author Naohide Sano (umjammer) + * @version 0.00 2020/07/01 umjammer initial version
+ */ +public class WebSocketTest { + + static String websocketBaseUrl = System.getenv("VAVI_APPS_WEBHOOK_WEBSOCKET_BASE_URL"); + static String websocketPath = System.getenv("VAVI_APPS_WEBHOOK_WEBSOCKET_GOOGLE_PATH"); + + static CountDownLatch cdl = new CountDownLatch(1); + + @ClientEndpoint(decoders = GoogleJsonDecoder.class, + encoders = GoogleJsonEncoder.class, + configurator = AuthorizationConfigurator.class) + public static class NotificationClient { + + @OnOpen + public void onOpen(Session session) { +Debug.println("OPEN: " + session); + } + + @OnMessage + public void onMessage(UnparsedNotification notification) { +Debug.println(notification); + } + + @OnError + public void onError(Throwable t) { +t.printStackTrace(); + } + + @OnClose + public void onClose(Session session) { +Debug.println("CLOSE"); + cdl.countDown(); + } + } + + /** + * @param args + */ + public static void main(String[] args) throws Exception { + WebSocketContainer container = ContainerProvider.getWebSocketContainer(); +// URI uri = URI.create("ws://localhost:5000" + websocketPath); + URI uri = URI.create(websocketBaseUrl + websocketPath); + Session session = container.connectToServer(new NotificationClient(), uri); + +Debug.println("Stop"); + cdl.await(); + + session.close(); +Debug.println("Done"); + +// TODO https://stackoverflow.com/a/46472909/6102938 +if(container != null && container instanceof org.eclipse.jetty.util.component.LifeCycle) { + try { +Debug.println("Stopping Jetty's WebSocket Client"); + ((org.eclipse.jetty.util.component.LifeCycle) container).stop(); + } catch (Exception e) { + throw new IllegalStateException(e); + } +} + } +} + +/* */ diff --git a/vavi-nio-file-googledrive/src/test/resources/duke.jpg b/vavi-nio-file-googledrive/src/test/resources/duke.jpg new file mode 100644 index 00000000..3402f12e Binary files /dev/null and b/vavi-nio-file-googledrive/src/test/resources/duke.jpg differ diff --git a/vavi-nio-file-googledrive/src/test/resources/googledrive.properties b/vavi-nio-file-googledrive/src/test/resources/googledrive.properties index d8d319de..39b70f03 100644 --- a/vavi-nio-file-googledrive/src/test/resources/googledrive.properties +++ b/vavi-nio-file-googledrive/src/test/resources/googledrive.properties @@ -1 +1 @@ -authenticatorClassName=vavi.net.auth.oauth2.google.GoogleLocalAuthenticator +authenticatorClassName=vavi.net.auth.oauth2.google.GoogleBasicOAuth2Authenticator diff --git a/vavi-nio-file-googledrive/src/test/resources/test.zip b/vavi-nio-file-googledrive/src/test/resources/test.zip new file mode 100644 index 00000000..8fcb7600 Binary files /dev/null and b/vavi-nio-file-googledrive/src/test/resources/test.zip differ diff --git a/vavi-nio-file-hfs/pom.xml b/vavi-nio-file-hfs/pom.xml index b70cfc95..f3228395 100644 --- a/vavi-nio-file-hfs/pom.xml +++ b/vavi-nio-file-hfs/pom.xml @@ -5,7 +5,7 @@ vavi vavi-apps-fuse - 0.1.6 + 0.1.7 vavi-nio-file-hfs @@ -32,16 +32,10 @@
- + com.github.umjammer hfsexplorer - 0.23.0 - - - org.netbeans.external - swing-layout-1.0.4 - - + 0.23.2 @@ -64,5 +58,22 @@ junit-platform-commons test + + org.junit.jupiter + junit-jupiter-params + test + + + + vavi + vavi-net-fuse + test + + + vavi + vavi-net-fuse + test-jar + test +
diff --git a/vavi-nio-file-hfs/src/main/java/vavi/nio/file/hfs/HfsFileAttributesFactory.java b/vavi-nio-file-hfs/src/main/java/vavi/nio/file/hfs/HfsFileAttributesFactory.java index 93b006d3..1bf1b06d 100644 --- a/vavi-nio-file-hfs/src/main/java/vavi/nio/file/hfs/HfsFileAttributesFactory.java +++ b/vavi-nio-file-hfs/src/main/java/vavi/nio/file/hfs/HfsFileAttributesFactory.java @@ -8,7 +8,7 @@ import org.catacombae.storage.fs.FSEntry; -import com.github.fge.filesystem.attributes.FileAttributesFactory; +import com.github.fge.filesystem.driver.ExtendedFileSystemDriverBase.ExtendsdFileAttributesFactory; /** @@ -17,7 +17,7 @@ * @author Naohide Sano (umjammer) * @version 0.00 2016/04/06 umjammer initial version
*/ -public final class HfsFileAttributesFactory extends FileAttributesFactory { +public final class HfsFileAttributesFactory extends ExtendsdFileAttributesFactory { public HfsFileAttributesFactory() { setMetadataClass(FSEntry.class); diff --git a/vavi-nio-file-hfs/src/main/java/vavi/nio/file/hfs/HfsFileStore.java b/vavi-nio-file-hfs/src/main/java/vavi/nio/file/hfs/HfsFileStore.java index e0e48885..3809958b 100644 --- a/vavi-nio-file-hfs/src/main/java/vavi/nio/file/hfs/HfsFileStore.java +++ b/vavi-nio-file-hfs/src/main/java/vavi/nio/file/hfs/HfsFileStore.java @@ -8,6 +8,7 @@ import java.io.IOException; import java.nio.file.FileStore; +import java.util.logging.Level; import org.catacombae.storage.fs.FSFolder; import org.catacombae.storage.fs.FSForkType; @@ -16,6 +17,9 @@ import com.github.fge.filesystem.attributes.FileAttributesFactory; import com.github.fge.filesystem.filestore.FileStoreBase; +import vavi.util.Debug; +import vavi.util.StringUtil; + /** * A simple HFS {@link FileStore} @@ -35,6 +39,9 @@ public final class HfsFileStore extends FileStoreBase { public HfsFileStore(HFSCommonFileSystemHandler handler, final FileAttributesFactory factory) { super("hfs", factory, false); this.root = handler.getRoot(); +Debug.println(Level.FINE, StringUtil.paramString(root)); +Debug.println(Level.FINE, StringUtil.paramString(root.getAllForks())); +Debug.println(Level.FINE, StringUtil.paramString(root.getAttributes())); } /** @@ -46,7 +53,7 @@ public HfsFileStore(HFSCommonFileSystemHandler handler, final FileAttributesFact */ @Override public long getTotalSpace() throws IOException { - return root.getForkByType(FSForkType.DATA).getLength(); + return 100000000; } /** @@ -67,7 +74,7 @@ public long getTotalSpace() throws IOException { */ @Override public long getUsableSpace() throws IOException { - return root.getForkByType(FSForkType.DATA).getLength(); + return 100000000; } /** @@ -86,6 +93,6 @@ public long getUsableSpace() throws IOException { */ @Override public long getUnallocatedSpace() throws IOException { - return root.getForkByType(FSForkType.DATA).getLength(); + return 100000000; } } diff --git a/vavi-nio-file-hfs/src/main/java/vavi/nio/file/hfs/HfsFileSystemDriver.java b/vavi-nio-file-hfs/src/main/java/vavi/nio/file/hfs/HfsFileSystemDriver.java index d6543adc..f6f36288 100644 --- a/vavi-nio-file-hfs/src/main/java/vavi/nio/file/hfs/HfsFileSystemDriver.java +++ b/vavi-nio-file-hfs/src/main/java/vavi/nio/file/hfs/HfsFileSystemDriver.java @@ -19,7 +19,6 @@ import java.nio.file.OpenOption; import java.nio.file.Path; import java.nio.file.attribute.FileAttribute; -import java.nio.file.spi.FileSystemProvider; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -54,19 +53,16 @@ public final class HfsFileSystemDriver extends ExtendedFileSystemDriverBase { private final HFSCommonFileSystemHandler handler; - private boolean ignoreAppleDouble = false; - /** * @param env */ - @SuppressWarnings("unchecked") public HfsFileSystemDriver(final FileStore fileStore, final FileSystemFactoryProvider provider, final HFSCommonFileSystemHandler handler, final Map env) throws IOException { super(fileStore, provider); + setEnv(env); this.handler = handler; - ignoreAppleDouble = (Boolean) ((Map) env).getOrDefault("ignoreAppleDouble", Boolean.FALSE); } /** */ @@ -88,8 +84,10 @@ private FSEntry getEntry(Path path) throws IOException { if (path.getNameCount() == 0) { return handler.getRoot(); } else { - FSEntry entry = handler.getEntry(toPathString(path)); +//Debug.println(Arrays.toString(toPathString(path).replaceFirst("^/", "").split("/", -1))); + FSEntry entry = handler.getEntry(toPathString(path).replaceFirst("^/", "").split("/", -1)); if (entry != null) { +//Debug.println("entry: " + entry.getName() + ", " + isFolder(entry)); return entry; } else { throw new NoSuchFileException(path.toString()); @@ -142,17 +140,6 @@ public void move(final Path source, final Path target, final Set opt throw new UnsupportedOperationException("this file system is read only"); } - /** - * Check access modes for a path on this filesystem - *

- * If no modes are provided to check for, this simply checks for the - * existence of the path. - *

- * - * @param path the path to check - * @param modes the modes to check for, if any - * @see FileSystemProvider#checkAccess(Path, AccessMode...) - */ @Override protected void checkAccessImpl(final Path path, final AccessMode... modes) throws IOException { final FSEntry entry = getEntry(path); @@ -169,20 +156,17 @@ protected void checkAccessImpl(final Path path, final AccessMode... modes) throw } } - @Override - public void close() throws IOException { - handler.close(); - } - - /** - * @throws IOException if you use this with javafs (jnr-fuse), you should throw {@link NoSuchFileException} when the file not found. - */ @Nonnull @Override protected Object getPathMetadataImpl(final Path path) throws IOException { return getEntry(path); } + @Override + public void close() throws IOException { + handler.close(); + } + /** */ private List getDirectoryEntries(final Path dir) throws IOException { final FSEntry dirEntry = getEntry(dir); diff --git a/vavi-nio-file-hfs/src/test/java/vavi/nio/file/hfs/HfsFileSystemProviderTest.java b/vavi-nio-file-hfs/src/test/java/vavi/nio/file/hfs/HfsFileSystemProviderTest.java index 4862fb15..f33e0cd4 100644 --- a/vavi-nio-file-hfs/src/test/java/vavi/nio/file/hfs/HfsFileSystemProviderTest.java +++ b/vavi-nio-file-hfs/src/test/java/vavi/nio/file/hfs/HfsFileSystemProviderTest.java @@ -6,13 +6,23 @@ package vavi.nio.file.hfs; +import java.io.IOException; import java.net.URI; +import java.net.URL; import java.nio.file.FileSystem; import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Collections; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable; +import org.junit.jupiter.api.condition.EnabledIf; + +import vavi.util.Debug; +import vavi.util.properties.annotation.Property; +import vavi.util.properties.annotation.PropsEntity; /** @@ -21,13 +31,52 @@ * @author Naohide Sano (umjammer) * @version 0.00 2019/06/23 umjammer initial version
*/ +@PropsEntity(url = "file://${user.dir}/local.properties") class HfsFileSystemProviderTest { + static boolean localPropertiesExists() { + return Files.exists(Paths.get("local.properties")); + } + @Test - @DisabledIfEnvironmentVariable(named = "GITHUB_WORKFLOW", matches = ".*") void test3() throws Exception { - URI uri = URI.create("hfs:file:/Users/nsano/Downloads/googlechrome-80.dmg"); - FileSystem fs = new HfsFileSystemProvider().newFileSystem(uri, Collections.EMPTY_MAP); + URL url = HfsFileSystemProviderTest.class.getResource("/test.dmg"); +Debug.println("file: " + url.getPath()); + URI uri = URI.create("hfs:file:" + url.getPath()); + FileSystem fs = new HfsFileSystemProvider().newFileSystem(uri, Collections.emptyMap()); + Files.list(fs.getRootDirectories().iterator().next()).forEach(System.err::println); + } + + @Test + @EnabledIf("localPropertiesExists") + @Disabled("doesn't work") + void test() throws Exception { + Path path = Paths.get("/Users/nsano/src/vavi/vavi-nio-file-apfs/src/test/resources/apfs.dmg"); +Debug.println("file: " + path); + URI uri = URI.create("hfs:file:" + path); + FileSystem fs = new HfsFileSystemProvider().newFileSystem(uri, Collections.emptyMap()); + Files.list(fs.getRootDirectories().iterator().next()).forEach(System.err::println); + } + + //---- + + @Property(name = "test.dmg") + String file; + + @BeforeEach + void setup() throws IOException { + if (localPropertiesExists()) { + PropsEntity.Util.bind(this); + } + } + + /** */ + public static void main(String[] args) throws Exception { + HfsFileSystemProviderTest app = new HfsFileSystemProviderTest(); + app.setup(); +Debug.println("file: " + app.file); + URI uri = URI.create("hfs:file:" + app.file); + FileSystem fs = new HfsFileSystemProvider().newFileSystem(uri, Collections.emptyMap()); Files.list(fs.getRootDirectories().iterator().next()).forEach(System.err::println); } } diff --git a/vavi-nio-file-hfs/src/test/java/vavi/nio/file/hfs/Main4.java b/vavi-nio-file-hfs/src/test/java/vavi/nio/file/hfs/Main4.java new file mode 100644 index 00000000..3ad5748c --- /dev/null +++ b/vavi-nio-file-hfs/src/test/java/vavi/nio/file/hfs/Main4.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2021 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package vavi.nio.file.hfs; + +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.condition.EnabledIf; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import com.github.fge.filesystem.driver.CachedFileSystemDriver; + +import vavi.net.fuse.Base; +import vavi.net.fuse.Fuse; +import vavi.util.properties.annotation.Property; +import vavi.util.properties.annotation.PropsEntity; + + +/** + * Main4. (fuse) + * + * @author Naohide Sano (umjammer) + * @version 0.00 2021/11/23 umjammer initial version
+ */ +@EnabledIf("localPropertiesExists") +@PropsEntity(url = "file://${user.dir}/local.properties") +public class Main4 { + + static { + System.setProperty("vavi.util.logging.VaviFormatter.extraClassMethod", "co\\.paralleluniverse\\.fuse\\.LoggedFuseFilesystem#log"); + } + + static boolean localPropertiesExists() { + return Files.exists(Paths.get("local.properties")); + } + + @Property + String diskImage; + @Property + String mountPoint; + + FileSystem fs; + Map options; + + @BeforeEach + public void before() throws Exception { + PropsEntity.Util.bind(this); + + URI uri = URI.create("hfs:file:" + diskImage); + + Map env = new HashMap<>(); + env.put(CachedFileSystemDriver.ENV_IGNORE_APPLE_DOUBLE, true); // mandatory + + fs = FileSystems.newFileSystem(uri, env); +//Files.list(fs.getRootDirectories().iterator().next()).forEach(System.err::println); + + options = new HashMap<>(); + options.put("fsname", "hfs_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 + @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); + + Base.testFuse(fs, mountPoint, options); + + fs.close(); + } + + // + + public static void main(String[] args) throws Exception { +// System.setProperty("vavi.net.fuse.FuseProvider.class", "vavi.net.fuse.javafs.JavaFSFuseProvider"); +// System.setProperty("vavi.net.fuse.FuseProvider.class", "vavi.net.fuse.jnrfuse.JnrFuseFuseProvider"); + System.setProperty("vavi.net.fuse.FuseProvider.class", "vavi.net.fuse.fusejna.FuseJnaFuseProvider"); + + Main4 app = new Main4(); + app.before(); + + try (Fuse fuse = Fuse.getFuse()) { + fuse.mount(app.fs, app.mountPoint, app.options); +while (true) { // for jnrfuse + Thread.yield(); +} + } + } +} + +/* */ diff --git a/vavi-nio-file-hfs/src/test/resources/test.dmg b/vavi-nio-file-hfs/src/test/resources/test.dmg new file mode 100644 index 00000000..84c9ca09 Binary files /dev/null and b/vavi-nio-file-hfs/src/test/resources/test.dmg differ diff --git a/vavi-nio-file-onedrive/pom.xml b/vavi-nio-file-onedrive/pom.xml index 4ca45e36..87f119b4 100644 --- a/vavi-nio-file-onedrive/pom.xml +++ b/vavi-nio-file-onedrive/pom.xml @@ -5,7 +5,7 @@ vavi vavi-apps-fuse - 0.1.6 + 0.1.7 vavi-nio-file-onedrive @@ -19,6 +19,13 @@ https://github.com/umjammer/vavi-nio-file-onedrive/issues + + + jitpack.io + https://jitpack.io + + + @@ -32,7 +39,7 @@ - com.github.umjammer.vavi-net-auth + ${vavi-net-auth.groupId} vavi-net-auth-microsoft ${vavi-net-auth.version} @@ -41,7 +48,12 @@ com.github.umjammer OneDriveJavaSDK - 0.0.7v + 0.0.8v + + + + vavi + vavi-nio-file-commons @@ -62,7 +74,6 @@ org.junit.jupiter junit-jupiter-params - 5.3.0 test @@ -88,5 +99,11 @@ test-jar test + + com.github.budhash + cliche + 153113a + test + diff --git a/vavi-nio-file-onedrive/src/main/java/vavi/nio/file/onedrive/OneDriveFileAttributesFactory.java b/vavi-nio-file-onedrive/src/main/java/vavi/nio/file/onedrive/OneDriveFileAttributesFactory.java index a1e866dc..1b4a20ad 100644 --- a/vavi-nio-file-onedrive/src/main/java/vavi/nio/file/onedrive/OneDriveFileAttributesFactory.java +++ b/vavi-nio-file-onedrive/src/main/java/vavi/nio/file/onedrive/OneDriveFileAttributesFactory.java @@ -6,7 +6,7 @@ package vavi.nio.file.onedrive; -import com.github.fge.filesystem.attributes.FileAttributesFactory; +import com.github.fge.filesystem.driver.ExtendedFileSystemDriverBase.ExtendsdFileAttributesFactory; import de.tuberlin.onedrivesdk.common.OneItem; @@ -17,7 +17,7 @@ * @author Naohide Sano (umjammer) * @version 0.00 2016/03/11 umjammer initial version
*/ -public final class OneDriveFileAttributesFactory extends FileAttributesFactory { +public final class OneDriveFileAttributesFactory extends ExtendsdFileAttributesFactory { public OneDriveFileAttributesFactory() { setMetadataClass(OneItem.class); diff --git a/vavi-nio-file-onedrive/src/main/java/vavi/nio/file/onedrive/OneDriveFileSystemDriver.java b/vavi-nio-file-onedrive/src/main/java/vavi/nio/file/onedrive/OneDriveFileSystemDriver.java index 83c7b1ae..0f0b5b01 100644 --- a/vavi-nio-file-onedrive/src/main/java/vavi/nio/file/onedrive/OneDriveFileSystemDriver.java +++ b/vavi-nio-file-onedrive/src/main/java/vavi/nio/file/onedrive/OneDriveFileSystemDriver.java @@ -10,46 +10,35 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.nio.file.AccessDeniedException; -import java.nio.file.AccessMode; import java.nio.file.CopyOption; -import java.nio.file.DirectoryNotEmptyException; -import java.nio.file.DirectoryStream; -import java.nio.file.FileAlreadyExistsException; import java.nio.file.FileStore; import java.nio.file.Files; -import java.nio.file.NoSuchFileException; -import java.nio.file.NotDirectoryException; import java.nio.file.OpenOption; import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.nio.file.attribute.FileAttribute; -import java.nio.file.spi.FileSystemProvider; -import java.util.ArrayList; +import java.nio.file.WatchEvent.Kind; +import java.nio.file.WatchService; import java.util.List; import java.util.Map; +import java.util.NoSuchElementException; import java.util.Set; import javax.annotation.Nonnull; import javax.annotation.ParametersAreNonnullByDefault; -import org.json.simple.parser.ParseException; - -import com.github.fge.filesystem.driver.ExtendedFileSystemDriverBase; -import com.github.fge.filesystem.exceptions.IsDirectoryException; +import com.github.fge.filesystem.driver.CachedFileSystemDriver; import com.github.fge.filesystem.provider.FileSystemFactoryProvider; -import vavi.nio.file.Cache; import vavi.nio.file.Util; import vavi.util.Debug; +import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE; import static vavi.nio.file.Util.toFilenameString; import static vavi.nio.file.Util.toPathString; +import static vavi.nio.file.onedrive.OneDriveFileSystemProvider.ENV_USE_SYSTEM_WATCHER; import de.tuberlin.onedrivesdk.OneDriveException; import de.tuberlin.onedrivesdk.OneDriveSDK; import de.tuberlin.onedrivesdk.common.OneItem; -import de.tuberlin.onedrivesdk.downloadFile.OneDownload; import de.tuberlin.onedrivesdk.file.OneFile; import de.tuberlin.onedrivesdk.folder.OneFolder; import de.tuberlin.onedrivesdk.uploadFile.OneUpload; @@ -62,98 +51,109 @@ * @version 0.00 2016/03/11 umjammer initial version
*/ @ParametersAreNonnullByDefault -public final class OneDriveFileSystemDriver extends ExtendedFileSystemDriverBase { +public final class OneDriveFileSystemDriver extends CachedFileSystemDriver { private final OneDriveSDK client; - private boolean ignoreAppleDouble = false; + private OneDriveWatchService systemWatcher; @SuppressWarnings("unchecked") public OneDriveFileSystemDriver(final FileStore fileStore, final FileSystemFactoryProvider provider, final OneDriveSDK client, - final Map env) { + final Map env) throws IOException { super(fileStore, provider); this.client = client; - ignoreAppleDouble = (Boolean) ((Map) env).getOrDefault("ignoreAppleDouble", Boolean.FALSE); -//System.err.println("ignoreAppleDouble: " + ignoreAppleDouble); + setEnv(env); + boolean useSystemWatcher = (Boolean) ((Map) env).getOrDefault(ENV_USE_SYSTEM_WATCHER, false); + + if (useSystemWatcher) { + systemWatcher = new OneDriveWatchService(client); + systemWatcher.setNotificationListener(this::processNotification); + } } - /** */ - private Cache cache = new Cache() { - /** - * TODO when the parent is not cached - * @see #ignoreAppleDouble - * @throws NoSuchFileException must be thrown when the path is not found in this cache - */ - public OneItem getEntry(Path path) throws IOException { + /** for system watcher */ + private void processNotification(String id, Kind kind) { + if (ENTRY_DELETE == kind) { try { - if (cache.containsFile(path)) { - return cache.getFile(path); - } else { - if (ignoreAppleDouble && path.getFileName() != null && Util.isAppleDouble(path)) { - throw new NoSuchFileException("ignore apple double file: " + path); - } - - OneItem entry = client.getItemByPath(toPathString(path)); - cache.putFile(path, entry); - return entry; - } - } catch (OneDriveException e) { - // TODO focus only file not found - // cache - if (cache.containsFile(path)) { + Path path = cache.getEntry(e -> id.equals(e.getId())); + cache.removeEntry(path); + } catch (NoSuchElementException e) { +Debug.println("NOTIFICATION: already deleted: " + id); + } + } else { + try { + try { + Path path = cache.getEntry(e -> id.equals(e.getId())); +Debug.println("NOTIFICATION: maybe updated: " + path); cache.removeEntry(path); + cache.getEntry(path); + } catch (NoSuchElementException e) { + OneFile entry = client.getFileById(id); + Path parent = cache.getEntry(f -> { try { return entry.getParentFolder().getId().equals(f.getId()); } catch (IOException g) { g.printStackTrace(); return false; }}); + Path path = parent.resolve(entry.getName()); +Debug.println("NOTIFICATION: maybe created: " + path); + cache.addEntry(path, OneItem.class.cast(entry)); } - - throw (IOException) new NoSuchFileException("path: " + path).initCause(e); + } catch (NoSuchElementException e) { +Debug.println("NOTIFICATION: parent not found: " + e); + } catch (IOException e) { + e.printStackTrace(); } } - }; + } + + /** */ + private static OneFile asFile(OneItem entry) { + return OneFile.class.cast(entry); + } + + /** */ + private static OneFolder asFolder(OneItem entry) { + return OneFolder.class.cast(entry); + } - @Nonnull @Override - public InputStream newInputStream(final Path path, final Set options) throws IOException { - try { - final OneItem entry = cache.getEntry(path); - // TODO: metadata driver - if (entry.isFolder()) { - throw new IsDirectoryException("path: " + path); - } + protected String getFilenameString(OneItem entry) { + return entry.getName(); + } - final OneDownload downloader = OneFile.class.cast(entry).download(); - return downloader.getDownloadedInputStream(); - } catch (OneDriveException e) { - throw new IOException("path: " + path, e); - } + @Override + protected boolean isFolder(OneItem entry) { + return entry.isFolder(); } - @Nonnull @Override - public OutputStream newOutputStream(final Path path, final Set options) throws IOException { - final OneItem entry; - try { - entry = cache.getEntry(path); + protected OneItem getRootEntry(Path root) throws IOException { + return client.getItemByPath(toPathString(root)); + } - if (entry.isFolder()) { - throw new IsDirectoryException("path: " + path); - } else { - throw new FileAlreadyExistsException("path: " + path); - } - } catch (NoSuchFileException e) { -System.out.println("newOutputStream: " + e.getMessage()); -//new Exception("*** DUMMY ***").printStackTrace(); + @Override + protected OneItem getEntry(OneItem parentEntry, Path path)throws IOException { + try { + return client.getItemByPath(toPathString(path)); + } catch (OneDriveException e) { + return null; } + } + + @Override + protected InputStream downloadEntry(OneItem entry, Path path, Set options) throws IOException { + return asFile(entry).download().getDownloadedInputStream(); + } + @Override + protected OutputStream uploadEntry(OneItem parentEntry, Path path, Set options) throws IOException { OneDriveUploadOption uploadOption = Util.getOneOfOptions(OneDriveUploadOption.class, options); if (uploadOption != null) { // java.nio.file is highly abstracted, so here source information is lost. // but onedrive graph api requires content length for upload. - // so reluctantly we provide {@link OneDriveUploadOpenOption} for {@link java.nio.file.Files#copy} options. + // so reluctantly we provide {@link OneDriveUploadOption} for {@link java.nio.file.Files#copy} options. Path source = uploadOption.getSource(); Debug.println("upload w/ option: " + source); - return uploadEntry(path, (int) Files.size(source)); + return uploadEntry(parentEntry, path, (int) Files.size(source)); } else { Debug.println("upload w/o option"); return new Util.OutputStreamForUploading() { // TODO used for getting file length @@ -161,7 +161,7 @@ public OutputStream newOutputStream(final Path path, final Set { - cache.addEntry(path, newEntry); - }); - return new BufferedOutputStream(uploader.upload(), Util.BUFFER_SIZE); - } catch (OneDriveException e) { - throw new IOException(e); - } + private OutputStream uploadEntry(OneItem parentEntry, Path path, int size) throws IOException { + OneUpload uploader = asFolder(parentEntry).upload(toFilenameString(path), size, newEntry -> { + updateEntry(path, newEntry); + }); + return new BufferedOutputStream(uploader.upload(), Util.BUFFER_SIZE); } - @Nonnull @Override - public DirectoryStream newDirectoryStream(final Path dir, - final DirectoryStream.Filter filter) throws IOException { - try { - return Util.newDirectoryStream(getDirectoryEntries(dir), filter); - } catch (OneDriveException e) { - throw new IOException("dir: " + dir, e); - } + protected List getDirectoryEntries(OneItem dirEntry, Path dir) throws IOException { + return asFolder(dirEntry).getChildren(); } @Override - public void createDirectory(final Path dir, final FileAttribute... attrs) throws IOException { - try { - OneItem parentEntry = cache.getEntry(dir.getParent()); - - // TODO: how to diagnose? - OneFolder dirEntry = OneFolder.class.cast(parentEntry).createFolder(toFilenameString(dir)); - - cache.addEntry(dir, OneItem.class.cast(dirEntry)); - } catch (OneDriveException e) { - throw new IOException("dir: "+ dir, e); - } + protected OneItem createDirectoryEntry(OneItem parentEntry, Path dir) throws IOException { + // TODO: how to diagnose? + return OneItem.class.cast(asFolder(parentEntry).createFolder(toFilenameString(dir))); } @Override - public void delete(final Path path) throws IOException { - try { - removeEntry(path); - } catch (OneDriveException e) { - throw new IOException("path: " + path, e); - } + protected boolean hasChildren(OneItem dirEntry, Path dir) throws IOException { + return client.getFolderByPath(toPathString(dir)).getChildren().size() > 0; } @Override - public void copy(final Path source, final Path target, final Set options) throws IOException { - try { - if (cache.existsEntry(target)) { - if (options != null && options.stream().anyMatch(o -> o.equals(StandardCopyOption.REPLACE_EXISTING))) { - removeEntry(target); - } else { - throw new FileAlreadyExistsException(target.toString()); - } - } - copyEntry(source, target); - } catch (OneDriveException e) { -e.printStackTrace(); - throw new IOException("source: "+ source + ", target: " + target, e); - } + protected void removeEntry(OneItem entry, Path path) throws IOException { + // TODO: unknown what happens when a move operation is performed + // and the target already exists + entry.delete(); } @Override - public void move(final Path source, final Path target, final Set options) throws IOException { - try { - if (cache.existsEntry(target)) { - if (cache.getEntry(target).isFolder()) { - if (options != null && options.stream().anyMatch(o -> o.equals(StandardCopyOption.REPLACE_EXISTING))) { - // replace the target - if (cache.getChildCount(target) > 0) { - throw new DirectoryNotEmptyException(target.toString()); - } else { - removeEntry(target); - moveEntry(source, target, false); - } - } else { - // move into the target - // TODO SPEC is FileAlreadyExistsException ? - moveEntry(source, target, true); - } - } else { - if (options != null && options.stream().anyMatch(o -> o.equals(StandardCopyOption.REPLACE_EXISTING))) { - removeEntry(target); - moveEntry(source, target, false); - } else { - throw new FileAlreadyExistsException(target.toString()); - } - } - } else { - if (source.getParent().equals(target.getParent())) { - // rename - renameEntry(source, target); - } else { - moveEntry(source, target, false); - } - } - } catch (OneDriveException e) { -e.printStackTrace(); - throw new IOException("source: " + source + ", target: " + target, e); - } - } - /** - * Check access modes for a path on this filesystem - *

- * If no modes are provided to check for, this simply checks for the - * existence of the path. - *

- * - * @param path the path to check - * @param modes the modes to check for, if any - * @throws IOException filesystem level error, or a plain I/O error - * if you use this with javafs (jnr-fuse), you should throw {@link NoSuchFileException} when the file not found. - * @see FileSystemProvider#checkAccess(Path, AccessMode...) - */ - @Override - protected void checkAccessImpl(final Path path, final AccessMode... modes) throws IOException { - final OneItem entry = cache.getEntry(path); - - if (!entry.isFile()) { - return; - } - - // TODO: assumed; not a file == directory - for (final AccessMode mode : modes) { - if (mode == AccessMode.EXECUTE) { - throw new AccessDeniedException(path.toString()); - } - } + protected OneItem copyEntry(OneItem sourceEntry, OneItem targetParentEntry, Path source, Path target, Set options) throws IOException { + OneFile newEntry = asFile(sourceEntry).copy(asFolder(targetParentEntry), toFilenameString(target)); +Debug.println(newEntry.getParentFolder().getName() + "/" + newEntry.getName()); + return OneItem.class.cast(newEntry); } @Override - public void close() throws IOException { - // TODO: what to do here? OneDriveClient does not implement Closeable :( + protected OneItem moveEntry(OneItem sourceEntry, OneItem targetParentEntry, Path source, Path target, boolean targetIsParent) throws IOException { + return asFile(sourceEntry).move(asFolder(targetParentEntry)); } - /** - * @throws IOException if you use this with javafs (jnr-fuse), you should throw {@link NoSuchFileException} when the file not found. - */ - @Nonnull @Override - protected Object getPathMetadataImpl(final Path path) throws IOException { - return cache.getEntry(path); - } - - /** */ - private List getDirectoryEntries(Path dir) throws IOException, OneDriveException { - final OneItem entry = cache.getEntry(dir); - - if (!entry.isFolder()) { - throw new NotDirectoryException(dir.toString()); - } - - List list = null; - if (cache.containsFolder(dir)) { - list = cache.getFolder(dir); - } else { - final List children = OneFolder.class.cast(entry).getChildren(); - list = new ArrayList<>(children.size()); - - for (final OneItem child : children) { - Path childPath = dir.resolve(child.getName()); - list.add(childPath); -//System.err.println("child: " + childPath.toRealPath().toString()); - - cache.putFile(childPath, child); - } - - cache.putFolder(dir, list); - } - - return list; - } - - /** */ - private void removeEntry(Path path) throws IOException, OneDriveException { - OneItem entry = cache.getEntry(path); - if (entry.isFolder()) { - // TODO use cache ??? - final List children = client.getFolderByPath(toPathString(path)).getChildren(); - - if (children.size() > 0) { - throw new DirectoryNotEmptyException(path.toString()); - } - } - - // TODO: unknown what happens when a move operation is performed - // and the target already exists - entry.delete(); - - cache.removeEntry(path); + protected OneItem moveFolderEntry(OneItem sourceEntry, OneItem targetParentEntry, Path source, Path target, boolean targetIsParent) throws IOException { + OneItem newEntry = asFolder(sourceEntry).move(asFolder(targetParentEntry)); +Debug.println(newEntry.getParentFolder().getName() + "/" + newEntry.getName()); + return newEntry; } - /** */ - private void copyEntry(final Path source, final Path target) throws IOException, OneDriveException { - try { - OneItem sourceEntry = cache.getEntry(source); - OneItem targetParentEntry = cache.getEntry(target.getParent()); - if (sourceEntry.isFile()) { - OneFile newEntry = OneFile.class.cast(sourceEntry).copy(OneFolder.class.cast(targetParentEntry), toFilenameString(target)); -Debug.println(newEntry.getParentFolder().getName() + "/" + newEntry.getName()); - cache.addEntry(target, OneItem.class.cast(newEntry)); - } else if (sourceEntry.isFolder()) { - throw new IsDirectoryException("source can not be a folder: " + source); - } - } catch (ParseException | InterruptedException e) { - throw new IOException(e); - } + @Override + protected OneItem renameEntry(OneItem sourceEntry, OneItem targetParentEntry, Path source, Path target) throws IOException { + return sourceEntry.rename(asFolder(targetParentEntry), toFilenameString(target)); } - /** - * @param targetIsParent if the target is folder - */ - private void moveEntry(final Path source, final Path target, boolean targetIsParent) throws IOException, OneDriveException { - try { - OneItem sourceEntry = cache.getEntry(source); - OneItem targetParentEntry = cache.getEntry(targetIsParent ? target : target.getParent()); - if (sourceEntry.isFile()) { - OneItem newEntry = OneFile.class.cast(sourceEntry).move(OneFolder.class.cast(targetParentEntry)); - cache.removeEntry(source); - if (targetIsParent) { - cache.addEntry(target.resolve(source.getFileName()), newEntry); - } else { - cache.addEntry(target, newEntry); - } - } else if (sourceEntry.isFolder()) { - OneItem newEntry = OneFolder.class.cast(sourceEntry).move(OneFolder.class.cast(targetParentEntry)); -Debug.println(newEntry.getParentFolder().getName() + "/" + newEntry.getName()); - cache.moveEntry(source, target, newEntry); - } - } catch (ParseException | InterruptedException e) { - throw new IOException(e); - } + @Override + public void close() throws IOException { + client.disconnect(); } - /** */ - private void renameEntry(final Path source, final Path target) throws IOException, OneDriveException { + @Nonnull + @Override + public WatchService newWatchService() { try { - OneItem sourceEntry = cache.getEntry(source); - OneItem targetEntry = cache.getEntry(target.getParent()); - - OneItem newEntry = sourceEntry.rename(OneFolder.class.cast(targetEntry), toFilenameString(target)); - - cache.removeEntry(source); - cache.addEntry(target, newEntry); - } catch (ParseException | InterruptedException e) { - throw new IOException(e); + return new OneDriveWatchService(client); + } catch (IOException e) { + throw new IllegalStateException(e); } } } diff --git a/vavi-nio-file-onedrive/src/main/java/vavi/nio/file/onedrive/OneDriveFileSystemProvider.java b/vavi-nio-file-onedrive/src/main/java/vavi/nio/file/onedrive/OneDriveFileSystemProvider.java index 40f42689..c8fd37fb 100644 --- a/vavi-nio-file-onedrive/src/main/java/vavi/nio/file/onedrive/OneDriveFileSystemProvider.java +++ b/vavi-nio-file-onedrive/src/main/java/vavi/nio/file/onedrive/OneDriveFileSystemProvider.java @@ -26,6 +26,8 @@ public final class OneDriveFileSystemProvider extends FileSystemProviderBase { public static final String ENV_APP_CREDENTIAL = "app_credential"; + public static final String ENV_USE_SYSTEM_WATCHER = "use_system_watcher"; + public OneDriveFileSystemProvider() { super(new OneDriveFileSystemRepository()); } diff --git a/vavi-nio-file-onedrive/src/main/java/vavi/nio/file/onedrive/OneDriveFileSystemRepository.java b/vavi-nio-file-onedrive/src/main/java/vavi/nio/file/onedrive/OneDriveFileSystemRepository.java index 8c8db68a..084db41c 100644 --- a/vavi-nio-file-onedrive/src/main/java/vavi/nio/file/onedrive/OneDriveFileSystemRepository.java +++ b/vavi-nio-file-onedrive/src/main/java/vavi/nio/file/onedrive/OneDriveFileSystemRepository.java @@ -44,7 +44,7 @@ public final class OneDriveFileSystemRepository extends FileSystemRepositoryBase { public OneDriveFileSystemRepository() { - super("onedrive", new OneDriveFileSystemFactoryProvider()); + super("onedrive1", new OneDriveFileSystemFactoryProvider()); } /** @@ -88,7 +88,7 @@ public FileSystemDriver createDriver(final URI uri, final Map env) th OneDriveSDK client = OneDriveFactory.createOneDriveSDK(appCredential.getClientId(), appCredential.getClientSecret(), appCredential.getRedirectUrl(), - OneDriveScope.OFFLINE_ACCESS); + OneDriveScope.valueOfByMicrosoftScopeString(appCredential.getScope())); String url = client.getAuthenticationURL(); MicrosoftOAuth2 oauth2 = new MicrosoftOAuth2(wrap(appCredential, url), userCredential.getId()); @@ -123,7 +123,7 @@ public FileSystemDriver createDriver(final URI uri, final Map env) th return new OneDriveFileSystemDriver(fileStore, factoryProvider, client, env); } catch (OneDriveException e) { - throw new IllegalStateException(e); + throw new IOException(e); } } } diff --git a/vavi-nio-file-onedrive/src/main/java/vavi/nio/file/onedrive/OneDriveUploadOption.java b/vavi-nio-file-onedrive/src/main/java/vavi/nio/file/onedrive/OneDriveUploadOption.java index 55482580..b832f58d 100644 --- a/vavi-nio-file-onedrive/src/main/java/vavi/nio/file/onedrive/OneDriveUploadOption.java +++ b/vavi-nio-file-onedrive/src/main/java/vavi/nio/file/onedrive/OneDriveUploadOption.java @@ -14,6 +14,10 @@ /** * OneDriveUploadOption. *

+ * in {@link com.github.fge.filesystem.options.FileSystemOptionsFactory}, + * instances of this class are compared by contains method. so i override + * {@link #equals(Object)} and {@link #hashCode()}. + *

* TODO CopyOption doesn't work. * * @author Naohide Sano (umjammer) @@ -21,6 +25,9 @@ */ public class OneDriveUploadOption implements OpenOption, CopyOption { + /** */ + private static final long serialVersionUID = -3760090552182064957L; + /** */ private Path source; @@ -41,7 +48,7 @@ public boolean equals(Object other) { @Override public int hashCode() { - return Long.hashCode(-3760090552182064957L); // TODO ad-hoc + return Long.hashCode(serialVersionUID); // TODO ad-hoc } } diff --git a/vavi-nio-file-onedrive/src/main/java/vavi/nio/file/onedrive/OneDriveWatchService.java b/vavi-nio-file-onedrive/src/main/java/vavi/nio/file/onedrive/OneDriveWatchService.java new file mode 100644 index 00000000..fcfda7a8 --- /dev/null +++ b/vavi-nio-file-onedrive/src/main/java/vavi/nio/file/onedrive/OneDriveWatchService.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2020 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package vavi.nio.file.onedrive; + +import java.io.IOException; + +import vavi.nio.file.watch.webhook.WebHookBaseWatchService; +import vavi.util.Debug; + +import de.tuberlin.onedrivesdk.OneDriveSDK; + + +/** + * OneDriveWatchService. + *

+ * system properties + *

    + *
  • vavi.nio.file.watch.webhook.NotificationProvider.onedrive + *
+ *

+ * + * @author Naohide Sano (umjammer) + * @version 0.00 2020/07/22 umjammer initial version
+ */ +public class OneDriveWatchService extends WebHookBaseWatchService { + + private static final String WEBHOOK_NOTIFICATION_PROVIDER = + System.getProperty("vavi.nio.file.watch.webhook.NotificationProvider.onedrive", ".onedrive.webhook.websocket"); + +// private OneDriveSDK client; + +// private String savedStartPageToken; + + /** */ + public OneDriveWatchService(OneDriveSDK client) throws IOException { +// this.client = client; + + setupNotification(this, WEBHOOK_NOTIFICATION_PROVIDER); + } + + @Override + protected void onNotifyMessage(String notification) throws IOException { +Debug.println(">> notification: done"); + } + + @Override + public void close() throws IOException { + if (isOpen()) { + super.close(); + } + } +} diff --git a/vavi-nio-file-onedrive/src/main/java/vavi/nio/file/onedrive/webhook/websocket/OneDriveWebSocketNotification.java b/vavi-nio-file-onedrive/src/main/java/vavi/nio/file/onedrive/webhook/websocket/OneDriveWebSocketNotification.java new file mode 100644 index 00000000..ac7d9d42 --- /dev/null +++ b/vavi-nio-file-onedrive/src/main/java/vavi/nio/file/onedrive/webhook/websocket/OneDriveWebSocketNotification.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2020 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package vavi.nio.file.onedrive.webhook.websocket; + +import java.io.IOException; +import java.net.URI; +import java.util.function.Consumer; + +import javax.websocket.ClientEndpoint; +import javax.websocket.Session; + +import vavi.nio.file.watch.webhook.websocket.BasicAuthorizationConfigurator; +import vavi.nio.file.watch.webhook.websocket.StringWebSocketNotification; + + +/** + * OneDriveWebSocketNotification. + *

+ * environment variables + *

    + *
  • VAVI_APPS_WEBHOOK_WEBSOCKET_BASE_URL + *
  • VAVI_APPS_WEBHOOK_WEBSOCKET_ONEDRIVE_PATH + *
+ * + * @author Naohide Sano (umjammer) + * @version 0.00 2020/07/23 umjammer initial version
+ */ +@ClientEndpoint(configurator = BasicAuthorizationConfigurator.class) +public class OneDriveWebSocketNotification extends StringWebSocketNotification { + + private static final String websocketBaseUrl = System.getenv("VAVI_APPS_WEBHOOK_WEBSOCKET_BASE_URL"); + private static final String websocketPath = System.getenv("VAVI_APPS_WEBHOOK_WEBSOCKET_ONEDRIVE_PATH"); + + private static final URI uri = URI.create(websocketBaseUrl + websocketPath); + + private Consumer callback; + + /** + * @param args + */ + public OneDriveWebSocketNotification(Consumer callback, Object... args) throws IOException { + super(uri, args); + this.callback = callback; + } + + @Override + public void onOpenImpl(Session session) throws IOException { + } + + @Override + protected void onNotifyMessageImpl(String notification) throws IOException { + callback.accept(notification); + } + + @Override + protected void onCloseImpl(Session session) throws IOException { + } +} + +/* */ diff --git a/vavi-nio-file-onedrive/src/main/java/vavi/nio/file/onedrive/webhook/websocket/OneDriveWebSocketNotificationProvider.java b/vavi-nio-file-onedrive/src/main/java/vavi/nio/file/onedrive/webhook/websocket/OneDriveWebSocketNotificationProvider.java new file mode 100644 index 00000000..0e9c94b8 --- /dev/null +++ b/vavi-nio-file-onedrive/src/main/java/vavi/nio/file/onedrive/webhook/websocket/OneDriveWebSocketNotificationProvider.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2020 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package vavi.nio.file.onedrive.webhook.websocket; + +import java.io.IOException; +import java.util.function.Consumer; + +import vavi.nio.file.watch.webhook.Notification; +import vavi.nio.file.watch.webhook.NotificationProvider; + + +/** + * OneDriveWebSocketNotificationProvider. + * + * @author Naohide Sano (umjammer) + * @version 0.00 2020/07/23 umjammer initial version
+ */ +public class OneDriveWebSocketNotificationProvider implements NotificationProvider { + + @Override + public Notification getNotification(Consumer callback, Object... args) throws IOException { + return Notification.class.cast(new OneDriveWebSocketNotification(Consumer.class.cast(callback), args)); + } +} + +/* */ diff --git a/vavi-nio-file-onedrive/src/main/resources/META-INF/services/java.nio.file.spi.FileSystemProvider b/vavi-nio-file-onedrive/src/main/resources/META-INF/services/java.nio.file.spi.FileSystemProvider index 6cb6a07f..7a82ad29 100644 --- a/vavi-nio-file-onedrive/src/main/resources/META-INF/services/java.nio.file.spi.FileSystemProvider +++ b/vavi-nio-file-onedrive/src/main/resources/META-INF/services/java.nio.file.spi.FileSystemProvider @@ -1 +1 @@ -#vavi.nio.file.onedrive.OneDriveFileSystemProvider +vavi.nio.file.onedrive.OneDriveFileSystemProvider diff --git a/vavi-nio-file-onedrive/src/main/resources/META-INF/services/vavi.nio.file.watch.webhook.NotificationProvider b/vavi-nio-file-onedrive/src/main/resources/META-INF/services/vavi.nio.file.watch.webhook.NotificationProvider new file mode 100644 index 00000000..6cb0a51f --- /dev/null +++ b/vavi-nio-file-onedrive/src/main/resources/META-INF/services/vavi.nio.file.watch.webhook.NotificationProvider @@ -0,0 +1 @@ +vavi.nio.file.onedrive.webhook.websocket.OneDriveWebSocketNotificationProvider diff --git a/vavi-nio-file-onedrive/src/test/java/vavi/nio/file/onedrive/App.java b/vavi-nio-file-onedrive/src/test/java/vavi/nio/file/onedrive/App.java index 62e4bfb9..763d707b 100644 --- a/vavi-nio-file-onedrive/src/test/java/vavi/nio/file/onedrive/App.java +++ b/vavi-nio-file-onedrive/src/test/java/vavi/nio/file/onedrive/App.java @@ -19,6 +19,9 @@ import java.util.concurrent.Executors; import java.util.concurrent.Future; +import com.budhash.cliche.Command; +import com.budhash.cliche.Param; +import com.budhash.cliche.ShellFactory; import com.google.common.collect.Maps; import vavi.net.auth.WithTotpUserCredential; @@ -30,9 +33,6 @@ import static vavi.net.auth.oauth2.OAuth2AppCredential.wrap; -import asg.cliche.Command; -import asg.cliche.Param; -import asg.cliche.ShellFactory; import de.tuberlin.onedrivesdk.OneDriveException; import de.tuberlin.onedrivesdk.OneDriveFactory; import de.tuberlin.onedrivesdk.OneDriveSDK; diff --git a/vavi-nio-file-onedrive/src/test/java/vavi/nio/file/onedrive/Main.java b/vavi-nio-file-onedrive/src/test/java/vavi/nio/file/onedrive/Main.java index 9fce9c0e..dbd99cea 100644 --- a/vavi-nio-file-onedrive/src/test/java/vavi/nio/file/onedrive/Main.java +++ b/vavi-nio-file-onedrive/src/test/java/vavi/nio/file/onedrive/Main.java @@ -26,8 +26,8 @@ public class Main { void test01() throws Exception { String email = System.getenv("MICROSOFT_TEST_ACCOUNT"); - URI uri = URI.create("onedrive:///?id=" + email); + URI uri = URI.create("onedrive1:///?id=" + email); - testAll(new OneDriveFileSystemProvider().newFileSystem(uri, Collections.EMPTY_MAP)); + testAll(new OneDriveFileSystemProvider().newFileSystem(uri, Collections.emptyMap())); } } \ No newline at end of file diff --git a/vavi-nio-file-onedrive/src/test/java/vavi/nio/file/onedrive/Main2.java b/vavi-nio-file-onedrive/src/test/java/vavi/nio/file/onedrive/Main2.java index d68302c4..1f6ac487 100644 --- a/vavi-nio-file-onedrive/src/test/java/vavi/nio/file/onedrive/Main2.java +++ b/vavi-nio-file-onedrive/src/test/java/vavi/nio/file/onedrive/Main2.java @@ -27,8 +27,8 @@ public class Main2 { void test01() throws Exception { String email = System.getenv("MICROSOFT_TEST_ACCOUNT"); - URI uri = URI.create("onedrive:///?id=" + email); - FileSystem fs = new OneDriveFileSystemProvider().newFileSystem(uri, Collections.EMPTY_MAP); + URI uri = URI.create("onedrive1:///?id=" + email); + FileSystem fs = new OneDriveFileSystemProvider().newFileSystem(uri, Collections.emptyMap()); testLargeFile(fs, OneDriveUploadOption.class); } diff --git a/vavi-nio-file-onedrive/src/test/java/vavi/nio/file/onedrive/Main3.java b/vavi-nio-file-onedrive/src/test/java/vavi/nio/file/onedrive/Main3.java index 6a7ef269..acde8c5c 100644 --- a/vavi-nio-file-onedrive/src/test/java/vavi/nio/file/onedrive/Main3.java +++ b/vavi-nio-file-onedrive/src/test/java/vavi/nio/file/onedrive/Main3.java @@ -28,8 +28,8 @@ public final class Main3 { void test01() throws Exception { String email = System.getenv("MICROSOFT_TEST_ACCOUNT"); - URI uri = URI.create("onedrive:///?id=" + email); - FileSystem fs = new OneDriveFileSystemProvider().newFileSystem(uri, Collections.EMPTY_MAP); + URI uri = URI.create("onedrive1:///?id=" + email); + FileSystem fs = new OneDriveFileSystemProvider().newFileSystem(uri, Collections.emptyMap()); testMoveFolder(fs); } diff --git a/vavi-nio-file-onedrive/src/test/java/vavi/nio/file/onedrive/Main4.java b/vavi-nio-file-onedrive/src/test/java/vavi/nio/file/onedrive/Main4.java index 2bd24341..c253e7ba 100644 --- a/vavi-nio-file-onedrive/src/test/java/vavi/nio/file/onedrive/Main4.java +++ b/vavi-nio-file-onedrive/src/test/java/vavi/nio/file/onedrive/Main4.java @@ -78,7 +78,7 @@ public static void main(final String... args) throws IOException { Map env = new HashMap<>(); env.put("ignoreAppleDouble", true); - URI uri = URI.create("onedrive:///?id=" + email); + URI uri = URI.create("onedrive1:///?id=" + email); FileSystem fs = new OneDriveFileSystemProvider().newFileSystem(uri, env); diff --git a/vavi-nio-file-onedrive/src/test/java/vavi/nio/file/onedrive/Main5.java b/vavi-nio-file-onedrive/src/test/java/vavi/nio/file/onedrive/Main5.java index 3b83ef01..d23d1ad3 100644 --- a/vavi-nio-file-onedrive/src/test/java/vavi/nio/file/onedrive/Main5.java +++ b/vavi-nio-file-onedrive/src/test/java/vavi/nio/file/onedrive/Main5.java @@ -35,13 +35,13 @@ public class Main5 { void test01() throws Exception { String email = System.getenv("TEST5_ACCOUNT"); - URI uri = URI.create("onedrive:///?id=" + email); + URI uri = URI.create("onedrive1:///?id=" + email); Path src; Path dstDir; Path dst; String a, b; - try (FileSystem onedrivefs = new OneDriveFileSystemProvider().newFileSystem(uri, Collections.EMPTY_MAP)) { + try (FileSystem onedrivefs = new OneDriveFileSystemProvider().newFileSystem(uri, Collections.emptyMap())) { src = Paths.get("src/test/resources/Hello.java"); dstDir = onedrivefs.getPath("/").resolve("TEST_FUSE_5"); @@ -60,7 +60,7 @@ void test01() throws Exception { a = Util.toFilenameString(Files.list(dstDir).findFirst().get()); } - try (FileSystem onedrivefs = new OneDriveFileSystemProvider().newFileSystem(uri, Collections.EMPTY_MAP)) { + try (FileSystem onedrivefs = new OneDriveFileSystemProvider().newFileSystem(uri, Collections.emptyMap())) { dstDir = onedrivefs.getPath("/").resolve("TEST_FUSE_5"); dst = dstDir.resolve("テ゚ト 001"); diff --git a/vavi-nio-file-onedrive/src/test/java/vavi/nio/file/onedrive/OneDriveFS.java b/vavi-nio-file-onedrive/src/test/java/vavi/nio/file/onedrive/OneDriveFS.java index bb3d877b..1758b2a9 100644 --- a/vavi-nio-file-onedrive/src/test/java/vavi/nio/file/onedrive/OneDriveFS.java +++ b/vavi-nio-file-onedrive/src/test/java/vavi/nio/file/onedrive/OneDriveFS.java @@ -37,7 +37,7 @@ public static void main(String[] args) throws Exception { String email = args[1]; - URI uri = URI.create("onedrive:///?id=" + email); + URI uri = URI.create("onedrive1:///?id=" + email); OAuth2AppCredential appCredential = new MicrosoftLocalAppCredential(); PropsEntity.Util.bind(appCredential); @@ -48,7 +48,7 @@ public static void main(String[] args) throws Exception { FileSystem fs = new OneDriveFileSystemProvider().newFileSystem(uri, env); - Fuse.getFuse().mount(fs, args[0], Collections.EMPTY_MAP); + Fuse.getFuse().mount(fs, args[0], Collections.emptyMap()); } } diff --git a/vavi-nio-file-onedrive/src/test/java/vavi/nio/file/onedrive/Rename.java b/vavi-nio-file-onedrive/src/test/java/vavi/nio/file/onedrive/Rename.java index 2c857502..f9bb7f29 100644 --- a/vavi-nio-file-onedrive/src/test/java/vavi/nio/file/onedrive/Rename.java +++ b/vavi-nio-file-onedrive/src/test/java/vavi/nio/file/onedrive/Rename.java @@ -41,7 +41,7 @@ public static void main(final String... args) throws IOException { // Create the necessary elements to create a filesystem. // Note: the URI _must_ have a scheme of "onedrive", and // _must_ be hierarchical. - URI uri = URI.create("onedrive:///?id=" + email); + URI uri = URI.create("onedrive1:///?id=" + email); OAuth2AppCredential appCredential = new MicrosoftLocalAppCredential(); PropsEntity.Util.bind(appCredential); diff --git a/vavi-nio-file-onedrive/src/test/java/vavi/nio/file/onedrive/WebHookApiTest.java b/vavi-nio-file-onedrive/src/test/java/vavi/nio/file/onedrive/WebHookApiTest.java new file mode 100644 index 00000000..06430db8 --- /dev/null +++ b/vavi-nio-file-onedrive/src/test/java/vavi/nio/file/onedrive/WebHookApiTest.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2020 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package vavi.nio.file.onedrive; + +import java.io.IOException; +import java.net.URI; + +import vavi.net.auth.oauth2.microsoft.MicrosoftLocalAppCredential; +import vavi.net.auth.oauth2.microsoft.MicrosoftOAuth2; +import vavi.net.auth.web.microsoft.MicrosoftLocalUserCredential; +import vavi.util.Debug; + +import static vavi.net.auth.oauth2.OAuth2AppCredential.wrap; + +import de.tuberlin.onedrivesdk.OneDriveException; +import de.tuberlin.onedrivesdk.OneDriveFactory; +import de.tuberlin.onedrivesdk.OneDriveSDK; +import de.tuberlin.onedrivesdk.common.OneDriveScope; +import de.tuberlin.onedrivesdk.common.OneItem; +import de.tuberlin.onedrivesdk.common.Subscription; +import de.tuberlin.onedrivesdk.folder.OneFolder; +import de.tuberlin.onedrivesdk.networking.OneDriveAuthenticationException; + + +/** + * WebHookApiTest. onedrive + * + * @author Naohide Sano (umjammer) + * @version 0.00 2020/07/03 umjammer initial version
+ * @see "https://app.box.com/developers/console/app/216798/webhooks" + * @see "https://developer.box.com/guides/webhooks/" + */ +public class WebHookApiTest { + + static final String VAVI_APPS_WEBHOOK_SECRET = System.getenv("VAVI_APPS_WEBHOOK_SECRET"); + static String webhooktUrl = System.getenv("VAVI_APPS_WEBHOOK_WEBHOOK_ONEDRIVE_URL"); + static String websocketBaseUrl = System.getenv("VAVI_APPS_WEBHOOK_WEBSOCKET_BASE_URL"); + static String websocketPath = System.getenv("VAVI_APPS_WEBHOOK_WEBSOCKET_ONEDRIVE_PATH"); + static String email = System.getenv("MICROSOFT_TEST_ACCOUNT"); + + /** + * @param args + */ + public static void main(String[] args) throws Exception { + + MicrosoftLocalUserCredential userCredential = new MicrosoftLocalUserCredential(email); + MicrosoftLocalAppCredential appCredential = new MicrosoftLocalAppCredential(); + + OneDriveSDK client = null; + try { + client = OneDriveFactory.createOneDriveSDK(appCredential.getClientId(), + appCredential.getClientSecret(), + appCredential.getRedirectUrl(), + OneDriveScope.OFFLINE_ACCESS); // TODO out source + String url = client.getAuthenticationURL(); + + MicrosoftOAuth2 oauth2 = new MicrosoftOAuth2(wrap(appCredential, url), userCredential.getId()); + String code = null; + String refreshToken = oauth2.readRefreshToken(); + if (refreshToken == null || refreshToken.isEmpty()) { + code = oauth2.authorize(userCredential); + client.authenticate(code); + } else { + try { + client.authenticateWithRefreshToken(refreshToken); + } catch (OneDriveAuthenticationException e) { +Debug.println("refreshToken: timeout?"); + code = oauth2.authorize(userCredential); + client.authenticate(code); + } + } + + } catch (OneDriveException e) { + throw new IOException(e); + } + + // create + URI uri = URI.create(webhooktUrl); + // Listen for file upload events in the specified folder + OneFolder rootFolder = client.getRootFolder(); + for (OneItem i : rootFolder.getChildren()) { + if (i.getName().equals("TEST_WEBHOOK")) { +System.out.println("rmdir " + i.getName()); + i.delete(); + } + } +System.out.println("mkdir " + "TEST_WEBHOOK"); + OneFolder newFolder = rootFolder.createFolder("TEST_WEBHOOK"); + // cannot set to root folder! +System.out.println("[create] webhook"); + Subscription subscription = client.subscribe(uri.toURL().toString(), VAVI_APPS_WEBHOOK_SECRET); +Debug.println(subscription.getId()); + +System.out.println("mkdir " + "TEST_WEBHOOK/" + "NEW FOLDER"); + OneFolder subFolder = newFolder.createFolder("NEW FOLDER"); + + Thread.sleep(5000); + + // update +System.out.println("[update] webhook"); + subscription = subscription.update(); + + // delete +System.out.println("[delete] webhook"); + subscription.delete(); + +System.out.println("rmdir " + subFolder.getName()); + subFolder.delete(); +System.out.println("rmdir " + newFolder.getName()); + newFolder.delete(); + } +} + +/* */ diff --git a/vavi-nio-file-onedrive/src/test/java/vavi/nio/file/onedrive/WebHookTest3.java b/vavi-nio-file-onedrive/src/test/java/vavi/nio/file/onedrive/WebHookTest3.java new file mode 100644 index 00000000..595ea7b1 --- /dev/null +++ b/vavi-nio-file-onedrive/src/test/java/vavi/nio/file/onedrive/WebHookTest3.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2020 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package vavi.nio.file.onedrive; + +import java.io.IOException; +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collections; + +import vavi.net.auth.oauth2.microsoft.MicrosoftLocalAppCredential; +import vavi.net.auth.oauth2.microsoft.MicrosoftOAuth2; +import vavi.net.auth.web.microsoft.MicrosoftLocalUserCredential; +import vavi.util.Debug; + +import static vavi.net.auth.oauth2.OAuth2AppCredential.wrap; + +import de.tuberlin.onedrivesdk.OneDriveException; +import de.tuberlin.onedrivesdk.OneDriveFactory; +import de.tuberlin.onedrivesdk.OneDriveSDK; +import de.tuberlin.onedrivesdk.common.OneDriveScope; +import de.tuberlin.onedrivesdk.networking.OneDriveAuthenticationException; + + +/** + * WebHookTest3. onedrive, using now construction libraries. + * + * @depends "file://${HOME}.vavifuse/onedrive/?" + * + * @author Naohide Sano (umjammer) + * @version 0.00 2016/02/29 umjammer initial version
+ * @see "https://stackoverflow.com/a/25794109/6102938" + */ +public class WebHookTest3 { + + static String email = System.getenv("MICROSOFT_TEST_ACCOUNT"); + + /** + * @param args 0: email + */ + public static void main(String[] args) throws Exception { + WebHookTest3 app = new WebHookTest3(); + app.test(); + } + + void test() throws Exception { + MicrosoftLocalUserCredential userCredential = new MicrosoftLocalUserCredential(email); + MicrosoftLocalAppCredential appCredential = new MicrosoftLocalAppCredential(); + + OneDriveSDK client = null; + try { + client = OneDriveFactory.createOneDriveSDK(appCredential.getClientId(), + appCredential.getClientSecret(), + appCredential.getRedirectUrl(), + OneDriveScope.OFFLINE_ACCESS); // TODO out source + String url = client.getAuthenticationURL(); + + MicrosoftOAuth2 oauth2 = new MicrosoftOAuth2(wrap(appCredential, url), userCredential.getId()); + String code = null; + String refreshToken = oauth2.readRefreshToken(); + if (refreshToken == null || refreshToken.isEmpty()) { + code = oauth2.authorize(userCredential); + client.authenticate(code); + } else { + try { + client.authenticateWithRefreshToken(refreshToken); + } catch (OneDriveAuthenticationException e) { +Debug.println("refreshToken: timeout?"); + code = oauth2.authorize(userCredential); + client.authenticate(code); + } + } + + } catch (OneDriveException e) { + throw new IOException(e); + } + + OneDriveWatchService service = new OneDriveWatchService(client); +Debug.println("WEBSOCKET: start: " + service); + try { + URI uri = URI.create("onedrive1:///?id=" + email); + FileSystem fs = FileSystems.newFileSystem(uri, Collections.emptyMap()); + + Path tmpDir = fs.getPath("tmp"); + if (!Files.exists(tmpDir)) { +System.out.println("rmdir " + tmpDir); + Files.createDirectory(tmpDir); + } + Path remote = tmpDir.resolve("Test+Watch"); + if (Files.exists(remote)) { +System.out.println("rm " + remote); + Files.delete(remote); + } + Path source = Paths.get("src/test/resources", "Hello.java"); +System.out.println("cp " + source + " " + remote); + Files.copy(source, remote); + +System.out.println("rm " + remote); + Files.delete(remote); + + Thread.sleep(10000); + + fs.close(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + service.close(); + } +Debug.println("APP: done"); + OneDriveWatchService.dispose(); +//Thread.getAllStackTraces().keySet().forEach(System.err::println); + } +} diff --git a/vavi-nio-file-onedrive/src/test/resources/log4j.properties b/vavi-nio-file-onedrive/src/test/resources/log4j.properties deleted file mode 100644 index b46df5a7..00000000 --- a/vavi-nio-file-onedrive/src/test/resources/log4j.properties +++ /dev/null @@ -1,44 +0,0 @@ -# -# Copyright (c) 2004 by Naohide Sano, All rights reserved. -# -# Written by Naohide Sano -# - -# -# ALL, DEBUG, INFO, WARN, ERROR, FATAL and OFF -# -log4j.rootLogger=ALL, ROOT -log4j.appender.ROOT=org.apache.log4j.ConsoleAppender -log4j.appender.ROOT.target=System.err -log4j.appender.ROOT.layout=org.apache.log4j.PatternLayout -log4j.appender.ROOT.layout.ConversionPattern=%d{ISO8601} [%p] %m%n\tat %t%x %c.%M(%F:%L)\n -log4j.appender.ROOT.encoding=${file.encoding} - -# -log4j.logger.org.apache=ERROR, APACHE -log4j.additivity.org.apache=false -log4j.appender.APACHE=org.apache.log4j.ConsoleAppender -log4j.appender.APACHE.target=System.err -log4j.appender.APACHE.layout=org.apache.log4j.PatternLayout -log4j.appender.APACHE.layout.ConversionPattern=%d{ISO8601} [%p] %m%n\tat %t%x %c.%M(%F:%L)\n -log4j.appender.APACHE.encoding=${file.encoding} - -# -log4j.logger.org.apache.http=DEBUG, HTTPCLIENT -log4j.additivity.org.apache=false -log4j.appender.HTTPCLIENT=org.apache.log4j.ConsoleAppender -log4j.appender.HTTPCLIENT.target=System.err -log4j.appender.HTTPCLIENT.layout=org.apache.log4j.PatternLayout -log4j.appender.HTTPCLIENT.layout.ConversionPattern=%d{ISO8601} [%p] %m%n\tat %t%x %c.%M(%F:%L)\n -log4j.appender.HTTPCLIENT.encoding=${file.encoding} - -# -log4j.logger.net.java.sen=ERROR, SEN -log4j.additivity.org.apache=false -log4j.appender.SEN=org.apache.log4j.ConsoleAppender -log4j.appender.SEN.target=System.err -log4j.appender.SEN.layout=org.apache.log4j.PatternLayout -log4j.appender.SEN.layout.ConversionPattern=%d{ISO8601} [%p] %m%n\tat %t%x %c.%M(%F:%L)\n -log4j.appender.SEN.encoding=${file.encoding} - -# diff --git a/vavi-nio-file-onedrive/src/test/resources/log4j2.xml b/vavi-nio-file-onedrive/src/test/resources/log4j2.xml index daa4f7f6..38c7b1ce 100644 --- a/vavi-nio-file-onedrive/src/test/resources/log4j2.xml +++ b/vavi-nio-file-onedrive/src/test/resources/log4j2.xml @@ -1,13 +1,14 @@ + - + diff --git a/vavi-nio-file-onedrive/src/test/resources/onedrive.properties b/vavi-nio-file-onedrive/src/test/resources/onedrive.properties index 298c42c4..3f300320 100644 --- a/vavi-nio-file-onedrive/src/test/resources/onedrive.properties +++ b/vavi-nio-file-onedrive/src/test/resources/onedrive.properties @@ -1 +1 @@ -authenticatorClassName=vavi.net.auth.oauth2.microsoft.MicrosoftLocalAuthenticator +authenticatorClassName=vavi.net.auth.oauth2.microsoft.MicrosoftBasicAuthenticator diff --git a/vavi-nio-file-onedrive3/pom.xml b/vavi-nio-file-onedrive3/pom.xml index fc985f29..beae35fe 100644 --- a/vavi-nio-file-onedrive3/pom.xml +++ b/vavi-nio-file-onedrive3/pom.xml @@ -4,7 +4,7 @@ vavi-apps-fuse vavi - 0.1.6 + 0.1.7 vavi-nio-file-onedrive3 @@ -20,8 +20,8 @@ - maven.cyberduck.io-release - http://repo.maven.cyberduck.io.s3.amazonaws.com/releases + jitpack.io + https://jitpack.io @@ -38,16 +38,21 @@ - com.github.umjammer.vavi-net-auth + ${vavi-net-auth.groupId} vavi-net-auth-microsoft ${vavi-net-auth.version} - ch.iterate.onedrive + com.github.iterate-ch onedrive-java-client - 2.0.16 + onedrive-java-client-3.1.8 + + + + vavi + vavi-nio-file-commons @@ -68,7 +73,6 @@ org.junit.jupiter junit-jupiter-params - 5.3.0 test diff --git a/vavi-nio-file-onedrive3/src/main/java/org/nuxeo/onedrive/client/OneDriveSubscriptions.java b/vavi-nio-file-onedrive3/src/main/java/org/nuxeo/onedrive/client/OneDriveSubscriptions.java new file mode 100644 index 00000000..cf9c25bc --- /dev/null +++ b/vavi-nio-file-onedrive3/src/main/java/org/nuxeo/onedrive/client/OneDriveSubscriptions.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2020 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package org.nuxeo.onedrive.client; + +import java.io.IOException; +import java.net.URL; + +import com.eclipsesource.json.JsonObject; +import com.eclipsesource.json.JsonValue; +import com.eclipsesource.json.ParseException; + + +/** + * OneDriveSubscriptions. + * + * @author Naohide Sano (umjammer) + * @version 0.00 2020/07/05 umjammer initial version
+ */ +public class OneDriveSubscriptions extends OneDriveResource { + private OneDriveSubscriptions(OneDriveAPI api) { + super(api); + } + + public Metadata getMetadata(OneDriveExpand... expands) throws IOException { + QueryStringBuilder query = new QueryStringBuilder().set("expand", expands); + final URL url = getMetadataUrl().build(getApi().getBaseURL(), query, getItemIdentifier()); + OneDriveJsonRequest request = new OneDriveJsonRequest(url, "POST"); + OneDriveJsonResponse response = request.sendRequest(getApi().getExecutor()); + JsonObject jsonObject = response.getContent(); + response.close(); + return new OneDriveSubscriptions.Metadata(jsonObject); + } + + public URLTemplate getMetadataUrl() { + return new URLTemplate(getPath()); + } + + public String getPath() { + return "/subscriptions"; + } + + @Override + public String getFullyQualifiedPath() { + return getPath(); + } + + public class Metadata extends OneDriveResource.Metadata { + public String id; + public String resource; + public String changeType; + public String clientState; + public String notificationUrl; + public String expirationDateTime; + public String applicationId; + public String creatorId; + + public Metadata(final JsonObject json) { + super(json); + } + + @Override + public OneDriveResource getResource() { + return OneDriveSubscriptions.this; + } + + @Override + protected void parseMember(JsonObject.Member member) { + super.parseMember(member); + try { + JsonValue value = member.getValue(); + String memberName = member.getName(); + if ("id".equals(memberName)) { + id = value.asString(); + } else if ("resource".equals(memberName)) { + resource = value.asString(); + } else if ("changeType".equals(memberName)) { + changeType = value.asString(); + } else if ("clientState".equals(memberName)) { + clientState = value.asString(); + } else if ("notificationUrl".equals(memberName)) { + notificationUrl = value.asString(); + } else if ("expirationDateTime".equals(memberName)) { + expirationDateTime = value.asString(); + } else if ("applicationId".equals(memberName)) { + applicationId = value.asString(); + } else if ("creatorId".equals(memberName)) { + creatorId = value.asString(); + } + } catch (ParseException e) { + throw new OneDriveRuntimeException(new OneDriveAPIException(e.getMessage(), e)); + } + } + + public String getResource_() { + return resource; + } + + public String getChangeType() { + return changeType; + } + + public String getClientState() { + return clientState; + } + + public String getNotificationUrl() { + return notificationUrl; + } + + public String getExpirationDateTime() { + return expirationDateTime; + } + + public String getApplicationId() { + return applicationId; + } + + public String getCreatorId() { + return creatorId; + } + + @Override + public String getId() { + return id; + } + } +} diff --git a/vavi-nio-file-onedrive3/src/main/java/org/nuxeo/onedrive/client/OneDriveSubscriptionsIterator.java b/vavi-nio-file-onedrive3/src/main/java/org/nuxeo/onedrive/client/OneDriveSubscriptionsIterator.java new file mode 100644 index 00000000..ae75c8b7 --- /dev/null +++ b/vavi-nio-file-onedrive3/src/main/java/org/nuxeo/onedrive/client/OneDriveSubscriptionsIterator.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2020 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package org.nuxeo.onedrive.client; + +import java.net.URL; +import java.util.Iterator; +import java.util.Objects; + +import com.eclipsesource.json.JsonObject; + + +/** + * OneDriveSubscriptionsIterator. + * + * @author Naohide Sano (umjammer) + * @version 0.00 2020/07/05 umjammer initial version
+ */ +public class OneDriveSubscriptionsIterator implements Iterator { + + private final OneDriveAPI api; + + private final JsonObjectIterator jsonObjectIterator; + + public OneDriveSubscriptionsIterator(OneDriveAPI api, URL url) { + this.api = Objects.requireNonNull(api); + this.jsonObjectIterator = new JsonObjectIterator(api, url) { + + @Override + protected void onResponse(JsonObject response) { + OneDriveSubscriptionsIterator.this.onResponse(response); + } + + }; + } + + @Override + public boolean hasNext() throws OneDriveRuntimeException { + return jsonObjectIterator.hasNext(); + } + + @Override + public OneDriveSubscriptions.Metadata next() throws OneDriveRuntimeException { + return null; //OneDriveSubscriptions.parseJson(api, jsonObjectIterator.next()); + } + + /** + * @since 1.1 + */ + protected void onResponse(JsonObject response) { + // Hook method + } + +} diff --git a/vavi-nio-file-onedrive3/src/main/java/org/nuxeo/onedrive/client/OneDriveSubscriptionsOperation.java b/vavi-nio-file-onedrive3/src/main/java/org/nuxeo/onedrive/client/OneDriveSubscriptionsOperation.java new file mode 100644 index 00000000..720d2879 --- /dev/null +++ b/vavi-nio-file-onedrive3/src/main/java/org/nuxeo/onedrive/client/OneDriveSubscriptionsOperation.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2020 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package org.nuxeo.onedrive.client; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimeZone; + +import com.eclipsesource.json.JsonObject; + + +/** + * OneDriveSubscriptionsOperation. + * + * @author Naohide Sano (umjammer) + * @version 0.00 2020/07/05 umjammer initial version
+ */ +public class OneDriveSubscriptionsOperation { + private final JsonObject jsonObject = new JsonObject(); + + public void subscribe(String notificationUrl, String clientState) { + jsonObject.add("changeType", "updated"); + jsonObject.add("notificationUrl", notificationUrl); + jsonObject.add("resource", "me/drive/root"); + jsonObject.add("expirationDateTime", getExpireTime()); + jsonObject.add("clientState", clientState); + } + + JsonObject build() { + return jsonObject; + } + + static String getExpireTime() { + long time = System.currentTimeMillis() + 3 * 24 * 60 * 60 * 1000; + DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + df.setTimeZone(TimeZone.getTimeZone("GMT")); + return df.format(new Date(time)); + } +} diff --git a/vavi-nio-file-onedrive3/src/main/java/vavi/nio/file/onedrive3/OneDriveBasicFileAttributesProvider.java b/vavi-nio-file-onedrive3/src/main/java/vavi/nio/file/onedrive3/OneDriveBasicFileAttributesProvider.java index 9d2927af..c59c2292 100644 --- a/vavi-nio-file-onedrive3/src/main/java/vavi/nio/file/onedrive3/OneDriveBasicFileAttributesProvider.java +++ b/vavi-nio-file-onedrive3/src/main/java/vavi/nio/file/onedrive3/OneDriveBasicFileAttributesProvider.java @@ -20,7 +20,7 @@ import javax.annotation.Nonnull; -import org.nuxeo.onedrive.client.OneDriveItem; +import org.nuxeo.onedrive.client.types.DriveItem; import com.github.fge.filesystem.attributes.provider.BasicFileAttributesProvider; @@ -37,9 +37,9 @@ */ public final class OneDriveBasicFileAttributesProvider extends BasicFileAttributesProvider implements PosixFileAttributes { - private final OneDriveItem.Metadata entry; + private final DriveItem.Metadata entry; - public OneDriveBasicFileAttributesProvider(@Nonnull final OneDriveItem.Metadata entry) throws IOException { + public OneDriveBasicFileAttributesProvider(@Nonnull final DriveItem.Metadata entry) throws IOException { this.entry = Objects.requireNonNull(entry); } diff --git a/vavi-nio-file-onedrive3/src/main/java/vavi/nio/file/onedrive3/OneDriveFileAttributesFactory.java b/vavi-nio-file-onedrive3/src/main/java/vavi/nio/file/onedrive3/OneDriveFileAttributesFactory.java index b4e20c62..64d5a2a0 100644 --- a/vavi-nio-file-onedrive3/src/main/java/vavi/nio/file/onedrive3/OneDriveFileAttributesFactory.java +++ b/vavi-nio-file-onedrive3/src/main/java/vavi/nio/file/onedrive3/OneDriveFileAttributesFactory.java @@ -6,9 +6,9 @@ package vavi.nio.file.onedrive3; -import org.nuxeo.onedrive.client.OneDriveItem; +import org.nuxeo.onedrive.client.types.DriveItem; -import com.github.fge.filesystem.attributes.FileAttributesFactory; +import com.github.fge.filesystem.driver.ExtendedFileSystemDriverBase.ExtendsdFileAttributesFactory; /** @@ -17,10 +17,10 @@ * @author Naohide Sano (umjammer) * @version 0.00 2016/03/11 umjammer initial version
*/ -public final class OneDriveFileAttributesFactory extends FileAttributesFactory { +public final class OneDriveFileAttributesFactory extends ExtendsdFileAttributesFactory { public OneDriveFileAttributesFactory() { - setMetadataClass(OneDriveItem.Metadata.class); + setMetadataClass(DriveItem.Metadata.class); addImplementation("basic", OneDriveBasicFileAttributesProvider.class); } } diff --git a/vavi-nio-file-onedrive3/src/main/java/vavi/nio/file/onedrive3/OneDriveFileStore.java b/vavi-nio-file-onedrive3/src/main/java/vavi/nio/file/onedrive3/OneDriveFileStore.java index 5197f394..012808d8 100644 --- a/vavi-nio-file-onedrive3/src/main/java/vavi/nio/file/onedrive3/OneDriveFileStore.java +++ b/vavi-nio-file-onedrive3/src/main/java/vavi/nio/file/onedrive3/OneDriveFileStore.java @@ -9,8 +9,9 @@ import java.io.IOException; import java.nio.file.FileStore; -import org.nuxeo.onedrive.client.OneDriveDrive; -import org.nuxeo.onedrive.client.OneDriveDrive.Metadata; +import org.nuxeo.onedrive.client.types.Drive; +import org.nuxeo.onedrive.client.types.Quota; +import org.nuxeo.onedrive.client.types.Drive.Metadata; import com.github.fge.filesystem.attributes.FileAttributesFactory; import com.github.fge.filesystem.filestore.FileStoreBase; @@ -26,14 +27,14 @@ */ public final class OneDriveFileStore extends FileStoreBase { - private final OneDriveDrive drive; + private final Drive.Metadata drive; /** * Constructor * * @param drive the (valid) OneDrive client to use */ - public OneDriveFileStore(final OneDriveDrive drive, final FileAttributesFactory factory) { + public OneDriveFileStore(final Drive.Metadata drive, final FileAttributesFactory factory) { super("onedrive", factory, false); this.drive = drive; } @@ -47,7 +48,7 @@ public OneDriveFileStore(final OneDriveDrive drive, final FileAttributesFactory */ @Override public long getTotalSpace() throws IOException { - final Metadata quota = getMetadata(); + final Quota quota = getQuota(); return quota == null ? 0 : quota.getTotal(); } @@ -69,7 +70,7 @@ public long getTotalSpace() throws IOException { */ @Override public long getUsableSpace() throws IOException { - final Metadata quota = getMetadata(); + final Quota quota = getQuota(); if (quota == null) { return 0; } else { @@ -93,7 +94,7 @@ public long getUsableSpace() throws IOException { */ @Override public long getUnallocatedSpace() throws IOException { - final Metadata quota = getMetadata(); + final Quota quota = getQuota(); if (quota == null) { return 0; } else { @@ -102,14 +103,14 @@ public long getUnallocatedSpace() throws IOException { } /** */ - private Metadata cache; // TODO refresh + private Quota cache; // TODO refresh /** */ - private Metadata getMetadata() throws IOException { + private Quota getQuota() throws IOException { if (cache != null) { return cache; } else { - cache = drive.getMetadata(); + cache = drive.getQuota(); return cache; } } diff --git a/vavi-nio-file-onedrive3/src/main/java/vavi/nio/file/onedrive3/OneDriveFileSystemDriver.java b/vavi-nio-file-onedrive3/src/main/java/vavi/nio/file/onedrive3/OneDriveFileSystemDriver.java index 2879db56..57a0b83f 100644 --- a/vavi-nio-file-onedrive3/src/main/java/vavi/nio/file/onedrive3/OneDriveFileSystemDriver.java +++ b/vavi-nio-file-onedrive3/src/main/java/vavi/nio/file/onedrive3/OneDriveFileSystemDriver.java @@ -12,54 +12,46 @@ import java.io.InputStream; import java.io.OutputStream; import java.net.URLEncoder; -import java.nio.file.AccessDeniedException; -import java.nio.file.AccessMode; import java.nio.file.CopyOption; -import java.nio.file.DirectoryNotEmptyException; -import java.nio.file.DirectoryStream; -import java.nio.file.FileAlreadyExistsException; import java.nio.file.FileStore; -import java.nio.file.Files; -import java.nio.file.NoSuchFileException; -import java.nio.file.NotDirectoryException; import java.nio.file.OpenOption; import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.nio.file.attribute.FileAttribute; -import java.nio.file.spi.FileSystemProvider; +import java.nio.file.WatchEvent.Kind; +import java.nio.file.WatchService; import java.time.Instant; import java.time.ZoneOffset; -import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Optional; +import java.util.NoSuchElementException; import java.util.Set; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.stream.Collectors; import java.util.stream.StreamSupport; import javax.annotation.Nonnull; import javax.annotation.ParametersAreNonnullByDefault; +import org.nuxeo.onedrive.client.CopyOperation; +import org.nuxeo.onedrive.client.Files; import org.nuxeo.onedrive.client.OneDriveAPI; -import org.nuxeo.onedrive.client.OneDriveCopyOperation; -import org.nuxeo.onedrive.client.OneDriveDrive; -import org.nuxeo.onedrive.client.OneDriveFile; -import org.nuxeo.onedrive.client.OneDriveFolder; -import org.nuxeo.onedrive.client.OneDriveItem; -import org.nuxeo.onedrive.client.OneDriveItem.ItemIdentifierType; import org.nuxeo.onedrive.client.OneDriveLongRunningAction; -import org.nuxeo.onedrive.client.OneDrivePatchOperation; -import org.nuxeo.onedrive.client.OneDriveUploadSession; -import org.nuxeo.onedrive.client.facets.FileSystemInfoFacet; +import org.nuxeo.onedrive.client.PatchOperation; +import org.nuxeo.onedrive.client.UploadSession; +import org.nuxeo.onedrive.client.types.Drive; +import org.nuxeo.onedrive.client.types.DriveItem; +import org.nuxeo.onedrive.client.types.FileSystemInfo; -import com.github.fge.filesystem.driver.ExtendedFileSystemDriverBase; -import com.github.fge.filesystem.exceptions.IsDirectoryException; +import com.github.fge.filesystem.driver.CachedFileSystemDriver; import com.github.fge.filesystem.provider.FileSystemFactoryProvider; -import vavi.nio.file.Cache; import vavi.nio.file.Util; import vavi.util.Debug; +import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE; import static vavi.nio.file.Util.toFilenameString; +import static vavi.nio.file.onedrive3.OneDriveFileSystemProvider.ENV_USE_SYSTEM_WATCHER; /** @@ -69,95 +61,101 @@ * @version 0.00 2016/03/11 umjammer initial version
*/ @ParametersAreNonnullByDefault -public final class OneDriveFileSystemDriver extends ExtendedFileSystemDriverBase { +public final class OneDriveFileSystemDriver extends CachedFileSystemDriver { private final OneDriveAPI client; - private final OneDriveDrive drive; - private boolean ignoreAppleDouble = false; + private final Drive.Metadata drive; + + private Runnable closer; + private OneDriveWatchService systemWatcher; @SuppressWarnings("unchecked") public OneDriveFileSystemDriver(final FileStore fileStore, final FileSystemFactoryProvider provider, final OneDriveAPI client, - final OneDriveDrive drive, - final Map env) { + Runnable closer, + final Drive.Metadata drive, + final Map env) throws IOException { super(fileStore, provider); this.client = client; - ignoreAppleDouble = (Boolean) ((Map) env).getOrDefault("ignoreAppleDouble", Boolean.FALSE); -//System.err.println("ignoreAppleDouble: " + ignoreAppleDouble); + this.closer = closer; + setEnv(env); this.drive = drive; - } + boolean useSystemWatcher = (Boolean) ((Map) env).getOrDefault(ENV_USE_SYSTEM_WATCHER, false); - /** */ - private Cache cache = new Cache() { - /** - * @see #ignoreAppleDouble - * @throws NoSuchFileException must be thrown when the path is not found in this cache - */ - public OneDriveItem.Metadata getEntry(Path path) throws IOException { - if (cache.containsFile(path)) { - return cache.getFile(path); - } else { - if (ignoreAppleDouble && path.getFileName() != null && Util.isAppleDouble(path)) { - throw new NoSuchFileException("ignore apple double file: " + path); - } + if (useSystemWatcher) { + systemWatcher = new OneDriveWatchService(client); + systemWatcher.setNotificationListener(this::processNotification); + } + } -// String pathString = toPathString(path); -//Debug.println("path: " + pathString); - OneDriveItem.Metadata entry; - if (path.getNameCount() == 0) { - entry = drive.getRoot().getMetadata(); - cache.putFile(path, entry); - return entry; - } else { - List siblings = getDirectoryEntries(path.getParent(), false); - Optional found = siblings.stream().filter(p -> path.getFileName().equals(p.getFileName())).findFirst(); - if (found.isPresent()) { - return cache.getEntry(found.get()); - } else { - throw new NoSuchFileException(path.toString()); - } + /** for system watcher */ + private void processNotification(String id, Kind kind) { + if (ENTRY_DELETE == kind) { + try { + Path path = cache.getEntry(e -> id.equals(e.getId())); + cache.removeEntry(path); + } catch (NoSuchElementException e) { +Debug.println("NOTIFICATION: already deleted: " + id); + } + } else { + try { + try { + Path path = cache.getEntry(e -> id.equals(e.getId())); +Debug.println("NOTIFICATION: maybe updated: " + path); + cache.removeEntry(path); + cache.getEntry(path); + } catch (NoSuchElementException e) { +// TODO impl +// OneDriveItem.Metadata entry = drive.getApi().getMetadata(id); +// Path parent = cache.getEntry(f -> entry.getParentReference().getId().equals(f.getId())); +// Path path = parent.resolve(entry.getName()); +//Debug.println("NOTIFICATION: maybe created: " + path); +// cache.addEntry(path, entry); } + } catch (NoSuchElementException e) { +Debug.println("NOTIFICATION: parent not found: " + e); + } catch (IOException e) { + e.printStackTrace(); } } - }; + } - @Nonnull - @Override - public InputStream newInputStream(final Path path, final Set options) throws IOException { - final OneDriveItem.Metadata entry = cache.getEntry(path); + /** */ + private static DriveItem asDriveItem(DriveItem.Metadata entry) { + return DriveItem.class.cast(entry.getItem()); + } - if (entry.isFolder()) { - throw new IsDirectoryException("path: " + path); - } + @Override + protected String getFilenameString(DriveItem.Metadata entry) { + return entry.getName(); + } - return new BufferedInputStream(OneDriveFile.class.cast(entry.getResource()).download(), Util.BUFFER_SIZE); + @Override + protected boolean isFolder(DriveItem.Metadata entry) { + return entry.isFolder(); } - @Nonnull @Override - public OutputStream newOutputStream(final Path path, final Set options) throws IOException { - OneDriveItem.Metadata entry = null; - try { - entry = cache.getEntry(path); + protected DriveItem.Metadata getRootEntry(Path root) throws IOException { + return new Drive(client, drive.getId()).getRoot().getMetadata(); + } - if (entry.isFolder()) { - throw new IsDirectoryException("path: " + path); - } else { - throw new FileAlreadyExistsException("path: " + path); - } - } catch (NoSuchFileException e) { -Debug.println("newOutputStream: " + e.getMessage()); - } + @Override + protected InputStream downloadEntry(DriveItem.Metadata entry, Path path, Set options) throws IOException { + return new BufferedInputStream(Files.download(asDriveItem(entry)), Util.BUFFER_SIZE); + } + @Override + protected OutputStream uploadEntry(DriveItem.Metadata parentEntry, Path path, Set options) throws IOException { OneDriveUploadOption uploadOption = Util.getOneOfOptions(OneDriveUploadOption.class, options); if (uploadOption != null) { // java.nio.file is highly abstracted, so here source information is lost. // but onedrive graph api requires content length for upload. - // so reluctantly we provide {@link OneDriveUploadOpenOption} for {@link java.nio.file.Files#copy} options. + // so reluctantly we provide {@link OneDriveUploadOption} for {@link java.nio.file.Files#copy} options. Path source = uploadOption.getSource(); Debug.println("upload w/ option: " + source); - return uploadEntry(path, (int) Files.size(source)); + return uploadEntry(parentEntry, path, (int) java.nio.file.Files.size(source)); } else { Debug.println("upload w/o option"); return new Util.OutputStreamForUploading() { // TODO used for only getting file length @@ -165,7 +163,7 @@ public OutputStream newOutputStream(final Path path, final Set { - cache.addEntry(path, newEntry); + updateEntry(path, newEntry); }), Util.BUFFER_SIZE); } - /** */ + /** ms-graph doesn't accept '+' in a path string */ private String toItemPathString(String pathString) throws IOException { return URLEncoder.encode(pathString, "utf-8").replace("+", "%20"); } - @Nonnull @Override - public DirectoryStream newDirectoryStream(final Path dir, - final DirectoryStream.Filter filter) throws IOException { - return Util.newDirectoryStream(getDirectoryEntries(dir, true), filter); + protected List getDirectoryEntries(DriveItem.Metadata dirEntry, Path dir) throws IOException { + Iterator iterator = Files.getFiles(asDriveItem(dirEntry)); + Spliterator spliterator = Spliterators.spliteratorUnknownSize(iterator, 0); + return StreamSupport.stream(spliterator, false).collect(Collectors.toList()); } - @Override - public void createDirectory(final Path dir, final FileAttribute... attrs) throws IOException { - OneDriveItem.Metadata parentEntry = cache.getEntry(dir.getParent()); - + protected DriveItem.Metadata createDirectoryEntry(DriveItem.Metadata parentEntry, Path dir) throws IOException { // TODO: how to diagnose? - OneDriveFolder.Metadata dirEntry = OneDriveFolder.class.cast(parentEntry.getResource()).create(toFilenameString(dir)); + return Files.createFolder(asDriveItem(parentEntry), toFilenameString(dir)); + } - cache.addEntry(dir, dirEntry); + @Override + protected boolean hasChildren(DriveItem.Metadata dirEntry, Path dir) throws IOException { + return getDirectoryEntries(dir, false).size() > 0; } @Override - public void delete(final Path path) throws IOException { - removeEntry(path); + protected void removeEntry(DriveItem.Metadata entry, Path path) throws IOException { + // TODO: unknown what happens when a move operation is performed + // and the target already exists + Files.delete(asDriveItem(entry)); } @Override - public void copy(final Path source, final Path target, final Set options) throws IOException { - if (cache.existsEntry(target)) { - if (options != null && options.stream().anyMatch(o -> o.equals(StandardCopyOption.REPLACE_EXISTING))) { - removeEntry(target); - } else { - throw new FileAlreadyExistsException(target.toString()); - } - } - copyEntry(source, target); + protected DriveItem.Metadata copyEntry(DriveItem.Metadata sourceEntry, DriveItem.Metadata targetParentEntry, Path source, Path target, Set options) throws IOException { + CopyOperation operation = new CopyOperation(); + operation.rename(toFilenameString(target)); +Debug.println("target: " + targetParentEntry.getName()); + operation.copy(asDriveItem(targetParentEntry)); + OneDriveLongRunningAction action = Files.copy(asDriveItem(sourceEntry), operation); + action.await(statusObject -> { +Debug.printf("Copy Progress Operation %s progress %.0f %%, status %s", +statusObject.getOperation(), +statusObject.getPercentage(), +statusObject.getStatus()); + }); + return getEntry(null, target); } @Override - public void move(final Path source, final Path target, final Set options) throws IOException { - if (cache.existsEntry(target)) { - if (cache.getEntry(target).isFolder()) { - if (options != null && options.stream().anyMatch(o -> o.equals(StandardCopyOption.REPLACE_EXISTING))) { - // replace the target - if (cache.getChildCount(target) > 0) { - throw new DirectoryNotEmptyException(target.toString()); - } else { - removeEntry(target); - moveEntry(source, target, false); - } - } else { - // move into the target - // TODO SPEC is FileAlreadyExistsException ? - moveEntry(source, target, true); - } - } else { - if (options != null && options.stream().anyMatch(o -> o.equals(StandardCopyOption.REPLACE_EXISTING))) { - removeEntry(target); - moveEntry(source, target, false); - } else { - throw new FileAlreadyExistsException(target.toString()); - } - } + protected DriveItem.Metadata moveEntry(DriveItem.Metadata sourceEntry, DriveItem.Metadata targetParentEntry, Path source, Path target, boolean targetIsParent) throws IOException { + PatchOperation operation = new PatchOperation(); + operation.rename(targetIsParent ? toFilenameString(source) : toFilenameString(target)); + operation.move(asDriveItem(targetParentEntry)); + final FileSystemInfo info = new FileSystemInfo(); + info.setLastModifiedDateTime(Instant.ofEpochMilli(sourceEntry.getLastModifiedDateTime().toEpochSecond()).atOffset(ZoneOffset.UTC)); + operation.facet("fileSystemInfo", info); + Files.patch(asDriveItem(sourceEntry), operation); + if (targetIsParent) { + return getEntry(null, target.resolve(source.getFileName())); } else { - if (source.getParent().equals(target.getParent())) { - // rename - renameEntry(source, target); - } else { - moveEntry(source, target, false); - } + return getEntry(null, target); } } - /** - * Check access modes for a path on this filesystem - *

- * If no modes are provided to check for, this simply checks for the - * existence of the path. - *

- * - * @param path the path to check - * @param modes the modes to check for, if any - * @throws IOException filesystem level error, or a plain I/O error - * if you use this with javafs (jnr-fuse), you should throw {@link NoSuchFileException} when the file not found. - * @see FileSystemProvider#checkAccess(Path, AccessMode...) - */ @Override - protected void checkAccessImpl(final Path path, final AccessMode... modes) throws IOException { - final OneDriveItem.Metadata entry = cache.getEntry(path); - - if (!entry.isFile()) { - return; - } + protected DriveItem.Metadata moveFolderEntry(DriveItem.Metadata sourceEntry, DriveItem.Metadata targetParentEntry, Path source, Path target, boolean targetIsParent) throws IOException { + PatchOperation operation = new PatchOperation(); + operation.rename(toFilenameString(target)); + operation.move(asDriveItem(targetParentEntry)); + final FileSystemInfo info = new FileSystemInfo(); + info.setLastModifiedDateTime(Instant.ofEpochMilli(sourceEntry.getLastModifiedDateTime().toEpochSecond()).atOffset(ZoneOffset.UTC)); + operation.facet("fileSystemInfo", info); + Files.patch(asDriveItem(sourceEntry), operation); + return getEntry(null, target); + } - // TODO: assumed; not a file == directory - for (final AccessMode mode : modes) { - if (mode == AccessMode.EXECUTE) { - throw new AccessDeniedException(path.toString()); - } - } + @Override + protected DriveItem.Metadata renameEntry(DriveItem.Metadata sourceEntry, DriveItem.Metadata targetParentEntry, Path source, Path target) throws IOException { + PatchOperation operation = new PatchOperation(); + operation.rename(toFilenameString(target)); + Files.patch(asDriveItem(sourceEntry), operation); + return getEntry(null, target); } @Override public void close() throws IOException { - // TODO: what to do here? OneDriveClient does not implement Closeable :( + closer.run(); } - /** - * @throws IOException if you use this with javafs (jnr-fuse), you should throw {@link NoSuchFileException} when the file not found. - */ @Nonnull @Override - protected Object getPathMetadataImpl(final Path path) throws IOException { - return cache.getEntry(path); - } - - /** */ - private List getDirectoryEntries(Path dir, boolean useCache) throws IOException { - final OneDriveItem.Metadata entry = cache.getEntry(dir); - - if (!entry.isFolder()) { - throw new NotDirectoryException(dir.toString()); - } - - List list = null; - if (useCache && cache.containsFolder(dir)) { - list = cache.getFolder(dir); - } else { - list = new ArrayList<>(); - - for (final OneDriveItem.Metadata child : OneDriveFolder.class.cast(entry.getResource()).getChildren()) { - Path childPath = dir.resolve(child.getName()); - list.add(childPath); -//System.err.println("child: " + childPath.toRealPath().toString()); - - cache.putFile(childPath, child); - } - - cache.putFolder(dir, list); - } - - return list; - } - - /** for created entry */ - private OneDriveItem.Metadata getEntry(Path path) throws IOException { - final OneDriveItem.Metadata parentEntry = cache.getEntry(path.getParent()); - - Optional found = StreamSupport.stream(OneDriveFolder.class.cast(parentEntry.getResource()).getChildren().spliterator(), false) - .filter(child -> { - try { -System.err.println(child.getName() + ", " + toFilenameString(path)); - return child.getName().equals(toFilenameString(path)); - } catch (IOException e) { - throw new IllegalStateException(e); - } - }).findFirst(); - if (found.isPresent()) { - return found.get(); - } else { - throw new NoSuchFileException(path.toString()); - } - } - - /** */ - private void removeEntry(Path path) throws IOException { - OneDriveItem.Metadata entry = cache.getEntry(path); - if (entry.isFolder()) { - if (getDirectoryEntries(path, false).size() > 0) { - throw new DirectoryNotEmptyException(path.toString()); - } - } - - // TODO: unknown what happens when a move operation is performed - // and the target already exists - entry.getResource().delete(); - - cache.removeEntry(path); - } - - /** */ - private void copyEntry(final Path source, final Path target) throws IOException { - OneDriveItem.Metadata sourceEntry = cache.getEntry(source); - OneDriveItem.Metadata targetParentEntry = cache.getEntry(target.getParent()); - if (sourceEntry.isFile()) { - OneDriveCopyOperation operation = new OneDriveCopyOperation(); - operation.rename(toFilenameString(target)); - operation.copy(OneDriveFolder.class.cast(targetParentEntry.getResource())); - OneDriveLongRunningAction action = sourceEntry.getResource().copy(operation); - action.await(statusObject -> { -Debug.printf("Copy Progress Operation %s progress %.0f %%, status %s", - statusObject.getOperation(), - statusObject.getPercentage(), - statusObject.getStatus()); - }); - cache.addEntry(target, getEntry(target)); - } else if (sourceEntry.isFolder()) { - throw new IsDirectoryException("source can not be a folder: " + source); - } - } - - /** - * @param targetIsParent if the target is folder - */ - private void moveEntry(final Path source, final Path target, boolean targetIsParent) throws IOException { - OneDriveItem.Metadata sourceEntry = cache.getEntry(source); - OneDriveItem.Metadata targetParentEntry = cache.getEntry(targetIsParent ? target : target.getParent()); - if (sourceEntry.isFile()) { - OneDrivePatchOperation operation = new OneDrivePatchOperation(); - operation.rename(targetIsParent ? toFilenameString(source) : toFilenameString(target)); - operation.move(OneDriveFolder.class.cast(targetParentEntry.getResource())); - final FileSystemInfoFacet info = new FileSystemInfoFacet(); - info.setLastModifiedDateTime(Instant.ofEpochMilli(sourceEntry.getLastModifiedDateTime().toEpochSecond()).atOffset(ZoneOffset.UTC)); - operation.facet("fileSystemInfo", info); - sourceEntry.getResource().patch(operation); - cache.removeEntry(source); - if (targetIsParent) { - cache.addEntry(target.resolve(source.getFileName()), getEntry(target.resolve(source.getFileName()))); - } else { - cache.addEntry(target, getEntry(target)); - } - } else if (sourceEntry.isFolder()) { - OneDrivePatchOperation operation = new OneDrivePatchOperation(); - operation.rename(toFilenameString(target)); - operation.move(OneDriveFolder.class.cast(targetParentEntry.getResource())); - final FileSystemInfoFacet info = new FileSystemInfoFacet(); - info.setLastModifiedDateTime(Instant.ofEpochMilli(sourceEntry.getLastModifiedDateTime().toEpochSecond()).atOffset(ZoneOffset.UTC)); - operation.facet("fileSystemInfo", info); - sourceEntry.getResource().patch(operation); - cache.moveEntry(source, target, getEntry(target)); + public WatchService newWatchService() { + try { + return new OneDriveWatchService(client); + } catch (IOException e) { + throw new IllegalStateException(e); } } - - /** */ - private void renameEntry(final Path source, final Path target) throws IOException { - OneDriveItem.Metadata sourceEntry = cache.getEntry(source); - - OneDrivePatchOperation operation = new OneDrivePatchOperation(); - operation.rename(toFilenameString(target)); - sourceEntry.getResource().patch(operation); - cache.removeEntry(source); - cache.addEntry(target, getEntry(target)); - } } diff --git a/vavi-nio-file-onedrive3/src/main/java/vavi/nio/file/onedrive3/OneDriveFileSystemProvider.java b/vavi-nio-file-onedrive3/src/main/java/vavi/nio/file/onedrive3/OneDriveFileSystemProvider.java index 3e4974d9..6850b7a4 100644 --- a/vavi-nio-file-onedrive3/src/main/java/vavi/nio/file/onedrive3/OneDriveFileSystemProvider.java +++ b/vavi-nio-file-onedrive3/src/main/java/vavi/nio/file/onedrive3/OneDriveFileSystemProvider.java @@ -26,6 +26,8 @@ public final class OneDriveFileSystemProvider extends FileSystemProviderBase { public static final String ENV_APP_CREDENTIAL = "app_credential"; + public static final String ENV_USE_SYSTEM_WATCHER = "use_system_watcher"; + public OneDriveFileSystemProvider() { super(new OneDriveFileSystemRepository()); } diff --git a/vavi-nio-file-onedrive3/src/main/java/vavi/nio/file/onedrive3/OneDriveFileSystemRepository.java b/vavi-nio-file-onedrive3/src/main/java/vavi/nio/file/onedrive3/OneDriveFileSystemRepository.java index 5a753be1..10ad7cc1 100644 --- a/vavi-nio-file-onedrive3/src/main/java/vavi/nio/file/onedrive3/OneDriveFileSystemRepository.java +++ b/vavi-nio-file-onedrive3/src/main/java/vavi/nio/file/onedrive3/OneDriveFileSystemRepository.java @@ -16,12 +16,13 @@ import javax.annotation.Nonnull; import javax.annotation.ParametersAreNonnullByDefault; +import org.nuxeo.onedrive.client.Drives; import org.nuxeo.onedrive.client.JavaNetRequestExecutor; import org.nuxeo.onedrive.client.OneDriveAPI; import org.nuxeo.onedrive.client.OneDriveBasicAPI; -import org.nuxeo.onedrive.client.OneDriveDrive; import org.nuxeo.onedrive.client.RequestExecutor; import org.nuxeo.onedrive.client.RequestHeader; +import org.nuxeo.onedrive.client.types.Drive; import com.github.fge.filesystem.driver.FileSystemDriver; import com.github.fge.filesystem.provider.FileSystemRepositoryBase; @@ -44,7 +45,7 @@ public final class OneDriveFileSystemRepository extends FileSystemRepositoryBase /** */ public OneDriveFileSystemRepository() { - super("onedrive", new OneDriveFileSystemFactoryProvider()); + super("onedrive3", new OneDriveFileSystemFactoryProvider()); } /** @@ -84,7 +85,8 @@ public FileSystemDriver createDriver(final URI uri, final Map env) th } // 3. process - String accessToken = new MicrosoftGraphOAuth2(appCredential, true).authorize(userCredential); + MicrosoftGraphOAuth2 oAuth2 = new MicrosoftGraphOAuth2(appCredential, true); + String accessToken = oAuth2.authorize(userCredential); //Debug.println("accessToken: " + accessToken); RequestExecutor executor = new JavaNetRequestExecutor(accessToken) { @@ -130,8 +132,8 @@ public String getEmailURL() { } }; - OneDriveDrive drive = OneDriveDrive.getDefaultDrive(client); + Drive.Metadata drive = Drives.getDrives(client).next(); final OneDriveFileStore fileStore = new OneDriveFileStore(drive, factoryProvider.getAttributesFactory()); - return new OneDriveFileSystemDriver(fileStore, factoryProvider, client, drive, env); + return new OneDriveFileSystemDriver(fileStore, factoryProvider, client, () -> oAuth2.close(), drive, env); } } diff --git a/vavi-nio-file-onedrive3/src/main/java/vavi/nio/file/onedrive3/OneDriveOutputStream.java b/vavi-nio-file-onedrive3/src/main/java/vavi/nio/file/onedrive3/OneDriveOutputStream.java index 279a9ba0..283d134c 100644 --- a/vavi-nio-file-onedrive3/src/main/java/vavi/nio/file/onedrive3/OneDriveOutputStream.java +++ b/vavi-nio-file-onedrive3/src/main/java/vavi/nio/file/onedrive3/OneDriveOutputStream.java @@ -14,9 +14,9 @@ import java.util.function.Consumer; import java.util.logging.Level; -import org.nuxeo.onedrive.client.OneDriveFile; import org.nuxeo.onedrive.client.OneDriveJsonObject; -import org.nuxeo.onedrive.client.OneDriveUploadSession; +import org.nuxeo.onedrive.client.UploadSession; +import org.nuxeo.onedrive.client.types.DriveItem; import vavi.util.Debug; @@ -47,15 +47,15 @@ */ public final class OneDriveOutputStream extends OutputStream { - private final OneDriveUploadSession upload; + private final UploadSession upload; private final Path file; private final AtomicBoolean close = new AtomicBoolean(); private long offset = 0L; private int length; - private OneDriveFile.Metadata entry; - private Consumer consumer; + private DriveItem.Metadata entry; + private Consumer consumer; - public OneDriveOutputStream(final OneDriveUploadSession upload, final Path file, int length, Consumer consumer) { + public OneDriveOutputStream(final UploadSession upload, final Path file, int length, Consumer consumer) { this.upload = upload; this.file = file; this.length = length; @@ -79,8 +79,8 @@ public void write(final byte[] b, final int off, final int len) throws IOExcepti } Debug.printf("header %s", header); OneDriveJsonObject object = upload.uploadFragment(header, content); - if (OneDriveFile.Metadata.class.isInstance(object)) { - entry = OneDriveFile.Metadata.class.cast(object); + if (DriveItem.Metadata.class.isInstance(object)) { + entry = DriveItem.Metadata.class.cast(object); Debug.printf("Completed upload for %s", file); } else { Debug.printf(Level.FINE, "Uploaded fragment %s for file %s", header, file); diff --git a/vavi-nio-file-onedrive3/src/main/java/vavi/nio/file/onedrive3/OneDriveUploadOption.java b/vavi-nio-file-onedrive3/src/main/java/vavi/nio/file/onedrive3/OneDriveUploadOption.java index 38f17dae..ce3ab8f9 100644 --- a/vavi-nio-file-onedrive3/src/main/java/vavi/nio/file/onedrive3/OneDriveUploadOption.java +++ b/vavi-nio-file-onedrive3/src/main/java/vavi/nio/file/onedrive3/OneDriveUploadOption.java @@ -15,6 +15,10 @@ * OneDriveUploadOption. *

* for large file. + *

+ * in {@link com.github.fge.filesystem.options.FileSystemOptionsFactory}, + * instances of this class are compared by contains method. so i override + * {@link #equals(Object)} and {@link #hashCode()}. *

* TODO CopyOption doesn't work. * @@ -23,6 +27,9 @@ */ public class OneDriveUploadOption implements OpenOption, CopyOption { + /** */ + private static final long serialVersionUID = 5575546140441990410L; + /** */ private Path source; @@ -43,7 +50,7 @@ public boolean equals(Object other) { @Override public int hashCode() { - return Long.hashCode(5575546140441990410L); // TODO ad-hoc + return Long.hashCode(serialVersionUID); // TODO ad-hoc } } diff --git a/vavi-nio-file-onedrive3/src/main/java/vavi/nio/file/onedrive3/OneDriveWatchService.java b/vavi-nio-file-onedrive3/src/main/java/vavi/nio/file/onedrive3/OneDriveWatchService.java new file mode 100644 index 00000000..499ed531 --- /dev/null +++ b/vavi-nio-file-onedrive3/src/main/java/vavi/nio/file/onedrive3/OneDriveWatchService.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2020 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package vavi.nio.file.onedrive3; + +import java.io.IOException; + +import org.nuxeo.onedrive.client.OneDriveAPI; + +import vavi.nio.file.watch.webhook.WebHookBaseWatchService; +import vavi.util.Debug; + + +/** + * OneDriveWatchService. + *

+ * system properties + *

    + *
  • vavi.nio.file.watch.webhook.NotificationProvider.onedrive3 + *
+ *

+ * + * @author Naohide Sano (umjammer) + * @version 0.00 2020/07/23 umjammer initial version
+ */ +public class OneDriveWatchService extends WebHookBaseWatchService { + + private static final String WEBHOOK_NOTIFICATION_PROVIDER = + System.getProperty("vavi.nio.file.watch.webhook.NotificationProvider.onedrive3", ".onedrive3.webhook.websocket"); + +// private OneDriveAPI client; + +// private String savedStartPageToken; + + /** */ + public OneDriveWatchService(OneDriveAPI client) throws IOException { +// this.client = client; + + setupNotification(this, WEBHOOK_NOTIFICATION_PROVIDER); + } + + @Override + protected void onNotifyMessage(String notification) throws IOException { +Debug.println(">> notification: done"); + } + + @Override + public void close() throws IOException { + if (isOpen()) { + super.close(); + } + } +} diff --git a/vavi-nio-file-onedrive3/src/main/java/vavi/nio/file/onedrive3/webhook/websocket/MicrosoftWebSocketNotification.java b/vavi-nio-file-onedrive3/src/main/java/vavi/nio/file/onedrive3/webhook/websocket/MicrosoftWebSocketNotification.java new file mode 100644 index 00000000..f4c09b11 --- /dev/null +++ b/vavi-nio-file-onedrive3/src/main/java/vavi/nio/file/onedrive3/webhook/websocket/MicrosoftWebSocketNotification.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2020 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package vavi.nio.file.onedrive3.webhook.websocket; + +import java.io.IOException; +import java.net.URI; +import java.util.function.Consumer; + +import javax.websocket.ClientEndpoint; +import javax.websocket.Session; + +import vavi.nio.file.watch.webhook.websocket.BasicAuthorizationConfigurator; +import vavi.nio.file.watch.webhook.websocket.StringWebSocketNotification; + + +/** + * MicrosoftWebSocketNotification. + *

+ * environment variables + *

    + *
  • VAVI_APPS_WEBHOOK_WEBSOCKET_BASE_URL + *
  • VAVI_APPS_WEBHOOK_WEBSOCKET_MICROSOFT_PATH + *
+ * + * @author Naohide Sano (umjammer) + * @version 0.00 2020/07/23 umjammer initial version
+ */ +@ClientEndpoint(configurator = BasicAuthorizationConfigurator.class) +public class MicrosoftWebSocketNotification extends StringWebSocketNotification { + + private static final String websocketBaseUrl = System.getenv("VAVI_APPS_WEBHOOK_WEBSOCKET_BASE_URL"); + private static final String websocketPath = System.getenv("VAVI_APPS_WEBHOOK_WEBSOCKET_MICROSOFT_PATH"); + + private static final URI uri = URI.create(websocketBaseUrl + websocketPath); + + private Consumer callback; + + /** + * @param args + */ + public MicrosoftWebSocketNotification(Consumer callback, Object... args) throws IOException { + super(uri, args); + this.callback = callback; + } + + @Override + public void onOpenImpl(Session session) throws IOException { + } + + @Override + protected void onNotifyMessageImpl(String notification) throws IOException { + callback.accept(notification); + } + + @Override + protected void onCloseImpl(Session session) throws IOException { + } +} + +/* */ diff --git a/vavi-nio-file-onedrive3/src/main/java/vavi/nio/file/onedrive3/webhook/websocket/MicrosoftWebSocketNotificationProvider.java b/vavi-nio-file-onedrive3/src/main/java/vavi/nio/file/onedrive3/webhook/websocket/MicrosoftWebSocketNotificationProvider.java new file mode 100644 index 00000000..799e3e11 --- /dev/null +++ b/vavi-nio-file-onedrive3/src/main/java/vavi/nio/file/onedrive3/webhook/websocket/MicrosoftWebSocketNotificationProvider.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2020 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package vavi.nio.file.onedrive3.webhook.websocket; + +import java.io.IOException; +import java.util.function.Consumer; + +import vavi.nio.file.watch.webhook.Notification; +import vavi.nio.file.watch.webhook.NotificationProvider; + + +/** + * MicrosoftWebSocketNotificationProvider. + * + * @author Naohide Sano (umjammer) + * @version 0.00 2020/07/23 umjammer initial version
+ */ +public class MicrosoftWebSocketNotificationProvider implements NotificationProvider { + + @Override + public Notification getNotification(Consumer callback, Object... args) throws IOException { + return Notification.class.cast(new MicrosoftWebSocketNotification(Consumer.class.cast(callback), args)); + } +} + +/* */ diff --git a/vavi-nio-file-onedrive3/src/main/resources/META-INF/services/java.nio.file.spi.FileSystemProvider b/vavi-nio-file-onedrive3/src/main/resources/META-INF/services/java.nio.file.spi.FileSystemProvider index a3dc7617..7b51461b 100644 --- a/vavi-nio-file-onedrive3/src/main/resources/META-INF/services/java.nio.file.spi.FileSystemProvider +++ b/vavi-nio-file-onedrive3/src/main/resources/META-INF/services/java.nio.file.spi.FileSystemProvider @@ -1 +1 @@ -#vavi.nio.file.onedrive3.OneDriveFileSystemProvider +vavi.nio.file.onedrive3.OneDriveFileSystemProvider diff --git a/vavi-nio-file-onedrive3/src/main/resources/META-INF/services/vavi.nio.file.watch.webhook.NotificationProvider b/vavi-nio-file-onedrive3/src/main/resources/META-INF/services/vavi.nio.file.watch.webhook.NotificationProvider new file mode 100644 index 00000000..58c62b5f --- /dev/null +++ b/vavi-nio-file-onedrive3/src/main/resources/META-INF/services/vavi.nio.file.watch.webhook.NotificationProvider @@ -0,0 +1 @@ +vavi.nio.file.onedrive3.webhook.websocket.MicrosoftWebSocketNotificationProvider diff --git a/vavi-nio-file-onedrive3/src/test/java/vavi/nio/file/onedrive3/Main.java b/vavi-nio-file-onedrive3/src/test/java/vavi/nio/file/onedrive3/Main.java index 668261e8..8a394290 100644 --- a/vavi-nio-file-onedrive3/src/test/java/vavi/nio/file/onedrive3/Main.java +++ b/vavi-nio-file-onedrive3/src/test/java/vavi/nio/file/onedrive3/Main.java @@ -24,10 +24,10 @@ public class Main { @Test void test01() throws Exception { - String email = System.getenv("MICROSOFT3_TEST_ACCOUNT"); + String email = System.getenv("MICROSOFT_TEST_ACCOUNT"); - URI uri = URI.create("onedrive:///?id=" + email); + URI uri = URI.create("onedrive3:///?id=" + email); - testAll(new OneDriveFileSystemProvider().newFileSystem(uri, Collections.EMPTY_MAP)); + testAll(new OneDriveFileSystemProvider().newFileSystem(uri, Collections.emptyMap())); } } \ No newline at end of file diff --git a/vavi-nio-file-onedrive3/src/test/java/vavi/nio/file/onedrive3/Main2.java b/vavi-nio-file-onedrive3/src/test/java/vavi/nio/file/onedrive3/Main2.java index 692f5f56..7161c1a0 100644 --- a/vavi-nio-file-onedrive3/src/test/java/vavi/nio/file/onedrive3/Main2.java +++ b/vavi-nio-file-onedrive3/src/test/java/vavi/nio/file/onedrive3/Main2.java @@ -25,10 +25,10 @@ public class Main2 { @Test void test01() throws Exception { - String email = System.getenv("MICROSOFT3_TEST_ACCOUNT"); + String email = System.getenv("MICROSOFT_TEST_ACCOUNT"); - URI uri = URI.create("onedrive:///?id=" + email); - FileSystem fs = new OneDriveFileSystemProvider().newFileSystem(uri, Collections.EMPTY_MAP); + URI uri = URI.create("onedrive3:///?id=" + email); + FileSystem fs = new OneDriveFileSystemProvider().newFileSystem(uri, Collections.emptyMap()); testLargeFile(fs, OneDriveUploadOption.class); } diff --git a/vavi-nio-file-onedrive3/src/test/java/vavi/nio/file/onedrive3/Main3.java b/vavi-nio-file-onedrive3/src/test/java/vavi/nio/file/onedrive3/Main3.java index 114ef7d0..029eb3a0 100644 --- a/vavi-nio-file-onedrive3/src/test/java/vavi/nio/file/onedrive3/Main3.java +++ b/vavi-nio-file-onedrive3/src/test/java/vavi/nio/file/onedrive3/Main3.java @@ -25,10 +25,10 @@ public class Main3 { @Test void test01() throws Exception { - String email = System.getenv("MICROSOFT3_TEST_ACCOUNT"); + String email = System.getenv("MICROSOFT_TEST_ACCOUNT"); - URI uri = URI.create("onedrive:///?id=" + email); - FileSystem fs = new OneDriveFileSystemProvider().newFileSystem(uri, Collections.EMPTY_MAP); + URI uri = URI.create("onedrive3:///?id=" + email); + FileSystem fs = new OneDriveFileSystemProvider().newFileSystem(uri, Collections.emptyMap()); testMoveFolder(fs); } diff --git a/vavi-nio-file-onedrive3/src/test/java/vavi/nio/file/onedrive3/Main4.java b/vavi-nio-file-onedrive3/src/test/java/vavi/nio/file/onedrive3/Main4.java index b71cb5ea..f8d3f6ea 100644 --- a/vavi-nio-file-onedrive3/src/test/java/vavi/nio/file/onedrive3/Main4.java +++ b/vavi-nio-file-onedrive3/src/test/java/vavi/nio/file/onedrive3/Main4.java @@ -77,7 +77,7 @@ public void test01(String providerClassName) throws Exception { public static void main(final String... args) throws IOException { String email = args[1]; - URI uri = URI.create("onedrive:///?id=" + email); + URI uri = URI.create("onedrive3:///?id=" + email); OAuth2AppCredential appCredential = new MicrosoftGraphLocalAppCredential(); diff --git a/vavi-nio-file-onedrive3/src/test/java/vavi/nio/file/onedrive3/Main5.java b/vavi-nio-file-onedrive3/src/test/java/vavi/nio/file/onedrive3/Main5.java index a642233c..f044884d 100644 --- a/vavi-nio-file-onedrive3/src/test/java/vavi/nio/file/onedrive3/Main5.java +++ b/vavi-nio-file-onedrive3/src/test/java/vavi/nio/file/onedrive3/Main5.java @@ -35,13 +35,13 @@ public class Main5 { void test01() throws Exception { String email = System.getenv("TEST5_ACCOUNT"); - URI uri = URI.create("onedrive:///?id=" + email); + URI uri = URI.create("onedrive3:///?id=" + email); Path src; Path dstDir; Path dst; String a, b; - try (FileSystem onedrivefs = new OneDriveFileSystemProvider().newFileSystem(uri, Collections.EMPTY_MAP)) { + try (FileSystem onedrivefs = new OneDriveFileSystemProvider().newFileSystem(uri, Collections.emptyMap())) { src = Paths.get("src/test/resources/Hello.java"); dstDir = onedrivefs.getPath("/").resolve("TEST_FUSE_5"); @@ -60,7 +60,7 @@ void test01() throws Exception { a = Util.toFilenameString(Files.list(dstDir).findFirst().get()); } - try (FileSystem onedrivefs = new OneDriveFileSystemProvider().newFileSystem(uri, Collections.EMPTY_MAP)) { + try (FileSystem onedrivefs = new OneDriveFileSystemProvider().newFileSystem(uri, Collections.emptyMap())) { dstDir = onedrivefs.getPath("/").resolve("TEST_FUSE_5"); dst = dstDir.resolve("テ゚ト 001"); diff --git a/vavi-nio-file-onedrive3/src/test/java/vavi/nio/file/onedrive3/WebHookApiTest.java b/vavi-nio-file-onedrive3/src/test/java/vavi/nio/file/onedrive3/WebHookApiTest.java new file mode 100644 index 00000000..90ae7e71 --- /dev/null +++ b/vavi-nio-file-onedrive3/src/test/java/vavi/nio/file/onedrive3/WebHookApiTest.java @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2020 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package vavi.nio.file.onedrive3; + +import java.io.IOException; +import java.net.URI; +import java.net.URL; +import java.util.Iterator; +import java.util.Set; + +import org.nuxeo.onedrive.client.Drives; +import org.nuxeo.onedrive.client.Files; +import org.nuxeo.onedrive.client.JavaNetRequestExecutor; +import org.nuxeo.onedrive.client.OneDriveAPI; +import org.nuxeo.onedrive.client.OneDriveBasicAPI; +import org.nuxeo.onedrive.client.types.Drive; +import org.nuxeo.onedrive.client.types.DriveItem; +import org.nuxeo.onedrive.client.RequestExecutor; +import org.nuxeo.onedrive.client.RequestHeader; + +import vavi.net.auth.oauth2.microsoft.MicrosoftGraphLocalAppCredential; +import vavi.net.auth.oauth2.microsoft.MicrosoftGraphOAuth2; +import vavi.net.auth.web.microsoft.MicrosoftLocalUserCredential; + + +/** + * WebHookApiTest. + * + * @author Naohide Sano (umjammer) + * @version 0.00 2020/07/03 umjammer initial version
+ * @see "https://docs.microsoft.com/graph/api/subscription-post-subscriptions?view=graph-rest-1.0&tabs=http" + */ +public class WebHookApiTest { + + static final String VAVI_APPS_WEBHOOK_SECRET = System.getenv("VAVI_APPS_WEBHOOK_SECRET"); + static String websocketBaseUrl = System.getenv("VAVI_APPS_WEBHOOK_WEBSOCKET_BASE_URL"); + static String websocketPath = System.getenv("VAVI_APPS_WEBHOOK_WEBSOCKET_MICROSOFT_PATH"); + + /** + * @param args + */ + public static void main(String[] args) throws Exception { + String email = System.getenv("MICROSOFT3_TEST_ACCOUNT"); + + MicrosoftLocalUserCredential userCredential = new MicrosoftLocalUserCredential(email); + MicrosoftGraphLocalAppCredential appCredential = new MicrosoftGraphLocalAppCredential(); + + @SuppressWarnings("resource") + String accessToken = new MicrosoftGraphOAuth2(appCredential, true).authorize(userCredential); +//Debug.println("accessToken: " + accessToken); + + RequestExecutor executor = new JavaNetRequestExecutor(accessToken) { + @Override + public void addAuthorizationHeader(final Set headers) { + super.addAuthorizationHeader(headers); + // HttpURLConnection adds "accept" header which is unavailable to onedrive. + headers.add(new RequestHeader("Accept", "application/json")); + } + + @Override + public Upload doPatch(URL url, Set headers) throws IOException { + headers.add(new RequestHeader("X-HTTP-Method-Override", "PATCH")); + headers.add(new RequestHeader("X-HTTP-Method", "PATCH")); + return super.doPost(url, headers); + } + }; + + OneDriveAPI client = new OneDriveBasicAPI(executor) { + @Override + public RequestExecutor getExecutor() { + return executor; + } + + @Override + public boolean isBusinessConnection() { + return false; + } + + @Override + public boolean isGraphConnection() { + return true; + } + + @Override + public String getBaseURL() { + return String.format("https://graph.microsoft.com%s", "/v1.0"); + } + + @Override + public String getEmailURL() { + return String.format("https://graph.microsoft.com%s", "/v1.0/me"); + } + }; + + Drive.Metadata drive = Drives.getDrives(client).next(); + + // create + URI uri = URI.create(websocketBaseUrl + websocketPath); + // Listen for file upload events in the specified folder + DriveItem rootFolder = new Drive(client, drive.getId()).getRoot(); + Iterator i = Files.getFiles(rootFolder); + while (i.hasNext()) { + DriveItem.Metadata child = i.next(); + if (child.getName().equals("TEST_WEBHOOK")) { +System.out.println("rmdir " + child.getName()); + Files.delete(DriveItem.class.cast(child.getItem())); + } + } +System.out.println("mkdir " + "TEST_WEBHOOK"); + DriveItem.Metadata newFolder = Files.createFolder(rootFolder, "TEST_WEBHOOK"); + +System.out.println("[create] webhook"); +// Subscription preSubscription = new Subscription(); +//// preSubscription.changeType = "created,updated,deleted"; // root is only supported update +// preSubscription.changeType = "created,updated,deleted"; +// preSubscription.notificationUrl = uri.toString(); +// preSubscription.resource = "me/drive/root"; +// preSubscription.expirationDateTime = calendar; +// preSubscription.clientState = VAVI_APPS_WEBHOOK_SECRET; +// Subscription subscription = graphClient.subscriptions().buildRequest().post(preSubscription); +//Debug.println(subscription.id); + +System.out.println("[ls] webhook"); +// for (Subscription s : subscPages.getCurrentPage()) { +//System.out.println(s); +// } + +System.out.println("mkdir " + "TEST_WEBHOOK/" + "NEW FOLDER"); + DriveItem.Metadata subFolder = Files.createFolder(DriveItem.class.cast(newFolder), "NEW FOLDER"); + + // update +System.out.println("[update] webhook"); +// subscription.expirationDateTime = calendar; +// subscription = drive.subscriptions(subscription.id).buildRequest().patch(subscription); + + // delete +System.out.println("[delete] webhook"); +// drive.subscriptions(subscription.id).buildRequest().delete(); + +System.out.println("rmdir " + subFolder.getName()); + Files.delete(DriveItem.class.cast(subFolder)); +System.out.println("rmdir " + newFolder.getName()); + Files.delete(DriveItem.class.cast(newFolder)); + } +} + +/* */ diff --git a/vavi-nio-file-onedrive4/pom.xml b/vavi-nio-file-onedrive4/pom.xml index 90bdcbbe..1673ddcf 100644 --- a/vavi-nio-file-onedrive4/pom.xml +++ b/vavi-nio-file-onedrive4/pom.xml @@ -5,7 +5,7 @@ vavi vavi-apps-fuse - 0.1.6 + 0.1.7 vavi-nio-file-onedrive4 @@ -32,7 +32,7 @@
- com.github.umjammer.vavi-net-auth + ${vavi-net-auth.groupId} vavi-net-auth-microsoft ${vavi-net-auth.version} @@ -41,7 +41,18 @@ com.microsoft.graph microsoft-graph - 1.4.0 + 2.0.0 + + + com.google.guava + guava + + + + + + vavi + vavi-nio-file-commons @@ -67,7 +78,6 @@ org.junit.jupiter junit-jupiter-params - 5.3.0 test diff --git a/vavi-nio-file-onedrive4/readme.md b/vavi-nio-file-onedrive4/readme.md new file mode 100644 index 00000000..90184d0c --- /dev/null +++ b/vavi-nio-file-onedrive4/readme.md @@ -0,0 +1,38 @@ +# vavi-nio-file-onedirve4 + +nio filsystem provider for onedrive + +## feature + + * based on the official sdk + * implements upload w/ monitor that *official sdk doesn't support* + * OnedriveUploadOption for large file uploading + * description as "user:attribute" + * thumbnail as "user:attribute" (works but where can we see those?) + +## issue + +official graph sdk version currently (2022-03-02) is upper 5.15.0. +but i don't use those new versions for this project. +because after `com.microsoft.graph:microsoft-graph:2.0.2`, +the sdk deprecated `com.microsoft.graph.http.IConnection` +and start using `okhttp3.Response` instead of it. +this is not oop. (beginner should not use generics and method reference) +`IConnection` is designed for library independency. +why the official sdk doesn't implement okhttp3 library as `IConnection`? +for performance reason? i don't think so. because +network transportation is very slower than wrapping object. + +i tried to adapt v5.x.x to this project. see branch `graph-sdk-5`. +but it was abandoned. i don't want to make any more effort to adapt +this project to the bad api's library. +(it's so hard to adapt lra monitor to official sdk.) + +when the official sdk stops supporting v2, +it's a time to die this project. + +there are two other onedrive providers. +so it's no problem! + + * [vavi-nio-file-onedrive](../vavi-nio-file-onedrive) (based on [onedrivejavasdk](https://github.com/umjammer/OneDriveJavaSDK)) + * [vavi-nio-file-onedrive3](../vavi-nio-file-onedrive3) (based on [onedrive-java-client](https://github.com/iterate-ch/onedrive-java-client)) \ No newline at end of file diff --git a/vavi-nio-file-onedrive4/src/main/java/vavi/nio/file/onedrive4/OneDriveBasicFileAttributesProvider.java b/vavi-nio-file-onedrive4/src/main/java/vavi/nio/file/onedrive4/OneDriveBasicFileAttributesProvider.java index 5f57d5b1..54a7c8fc 100644 --- a/vavi-nio-file-onedrive4/src/main/java/vavi/nio/file/onedrive4/OneDriveBasicFileAttributesProvider.java +++ b/vavi-nio-file-onedrive4/src/main/java/vavi/nio/file/onedrive4/OneDriveBasicFileAttributesProvider.java @@ -22,6 +22,8 @@ import com.github.fge.filesystem.attributes.provider.BasicFileAttributesProvider; import com.microsoft.graph.models.extensions.DriveItem; +import vavi.nio.file.onedrive4.OneDriveFileAttributesFactory.Metadata; + /** * {@link BasicFileAttributes} implementation for OneDrive @@ -37,8 +39,8 @@ public final class OneDriveBasicFileAttributesProvider extends BasicFileAttribut private final DriveItem entry; - public OneDriveBasicFileAttributesProvider(@Nonnull final DriveItem entry) throws IOException { - this.entry = Objects.requireNonNull(entry); + public OneDriveBasicFileAttributesProvider(@Nonnull final Metadata entry) throws IOException { + this.entry = Objects.requireNonNull(entry).driveItem; } /** @@ -54,7 +56,7 @@ public OneDriveBasicFileAttributesProvider(@Nonnull final DriveItem entry) throw */ @Override public FileTime lastModifiedTime() { - return FileTime.fromMillis(entry.lastModifiedDateTime.getTimeInMillis()); + return FileTime.fromMillis(entry.lastModifiedDateTime.toInstant().toEpochMilli()); } /** diff --git a/vavi-nio-file-onedrive4/src/main/java/vavi/nio/file/onedrive4/OneDriveFileAttributesFactory.java b/vavi-nio-file-onedrive4/src/main/java/vavi/nio/file/onedrive4/OneDriveFileAttributesFactory.java index 4cc2e6cd..be76167e 100644 --- a/vavi-nio-file-onedrive4/src/main/java/vavi/nio/file/onedrive4/OneDriveFileAttributesFactory.java +++ b/vavi-nio-file-onedrive4/src/main/java/vavi/nio/file/onedrive4/OneDriveFileAttributesFactory.java @@ -6,7 +6,7 @@ package vavi.nio.file.onedrive4; -import com.github.fge.filesystem.attributes.FileAttributesFactory; +import com.github.fge.filesystem.driver.ExtendedFileSystemDriverBase.ExtendsdFileAttributesFactory; import com.microsoft.graph.models.extensions.DriveItem; @@ -16,10 +16,20 @@ * @author Naohide Sano (umjammer) * @version 0.00 2016/03/11 umjammer initial version
*/ -public final class OneDriveFileAttributesFactory extends FileAttributesFactory { +public final class OneDriveFileAttributesFactory extends ExtendsdFileAttributesFactory { + + static class Metadata { + OneDriveFileSystemDriver driver; + DriveItem driveItem; + Metadata(OneDriveFileSystemDriver driver, DriveItem driveItem) { + this.driver = driver; + this.driveItem = driveItem; + } + } public OneDriveFileAttributesFactory() { - setMetadataClass(DriveItem.class); + setMetadataClass(Metadata.class); addImplementation("basic", OneDriveBasicFileAttributesProvider.class); + addImplementation("user", OneDriveUserDefinedFileAttributesProvider.class); } } diff --git a/vavi-nio-file-onedrive4/src/main/java/vavi/nio/file/onedrive4/OneDriveFileSystemDriver.java b/vavi-nio-file-onedrive4/src/main/java/vavi/nio/file/onedrive4/OneDriveFileSystemDriver.java index 11910f8d..5748db4e 100644 --- a/vavi-nio-file-onedrive4/src/main/java/vavi/nio/file/onedrive4/OneDriveFileSystemDriver.java +++ b/vavi-nio-file-onedrive4/src/main/java/vavi/nio/file/onedrive4/OneDriveFileSystemDriver.java @@ -11,31 +11,22 @@ import java.io.InputStream; import java.io.OutputStream; import java.net.URLEncoder; -import java.nio.file.AccessDeniedException; -import java.nio.file.AccessMode; import java.nio.file.CopyOption; -import java.nio.file.DirectoryNotEmptyException; -import java.nio.file.DirectoryStream; -import java.nio.file.FileAlreadyExistsException; import java.nio.file.FileStore; import java.nio.file.Files; -import java.nio.file.NoSuchFileException; -import java.nio.file.NotDirectoryException; import java.nio.file.OpenOption; import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.nio.file.attribute.FileAttribute; -import java.nio.file.spi.FileSystemProvider; +import java.nio.file.WatchEvent.Kind; +import java.nio.file.WatchService; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.NoSuchElementException; import java.util.Set; +import java.util.logging.Level; -import javax.annotation.Nonnull; -import javax.annotation.ParametersAreNonnullByDefault; - -import com.github.fge.filesystem.driver.ExtendedFileSystemDriverBase; -import com.github.fge.filesystem.exceptions.IsDirectoryException; +import com.github.fge.filesystem.driver.CachedFileSystemDriver; import com.github.fge.filesystem.provider.FileSystemFactoryProvider; import com.google.common.io.ByteStreams; import com.microsoft.graph.concurrency.ChunkedUploadProvider; @@ -52,20 +43,26 @@ import com.microsoft.graph.models.extensions.Folder; import com.microsoft.graph.models.extensions.IGraphServiceClient; import com.microsoft.graph.models.extensions.ItemReference; +import com.microsoft.graph.models.extensions.ThumbnailSet; import com.microsoft.graph.models.extensions.UploadSession; +import com.microsoft.graph.options.QueryOption; import com.microsoft.graph.requests.extensions.IDriveItemCollectionPage; import com.microsoft.graph.requests.extensions.IDriveItemCopyRequest; +import com.microsoft.graph.requests.extensions.IThumbnailSetCollectionPage; -import vavi.nio.file.Cache; import vavi.nio.file.Util; +import vavi.nio.file.onedrive4.OneDriveFileAttributesFactory.Metadata; import vavi.nio.file.onedrive4.graph.LraMonitorProvider; import vavi.nio.file.onedrive4.graph.LraMonitorResponseHandler; import vavi.nio.file.onedrive4.graph.LraMonitorResult; import vavi.nio.file.onedrive4.graph.LraSession; +import vavi.nio.file.onedrive4.graph.ThumbnailUploadProvider; import vavi.util.Debug; +import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE; import static vavi.nio.file.Util.toFilenameString; import static vavi.nio.file.Util.toPathString; +import static vavi.nio.file.onedrive4.OneDriveFileSystemProvider.ENV_USE_SYSTEM_WATCHER; /** @@ -74,82 +71,92 @@ * @author Naohide Sano (umjammer) * @version 0.00 2016/03/11 umjammer initial version
*/ -@ParametersAreNonnullByDefault -public final class OneDriveFileSystemDriver extends ExtendedFileSystemDriverBase { +public final class OneDriveFileSystemDriver extends CachedFileSystemDriver { private final IGraphServiceClient client; - private boolean ignoreAppleDouble = false; + + private Runnable closer; + private OneDriveWatchService systemWatcher; @SuppressWarnings("unchecked") - public OneDriveFileSystemDriver(final FileStore fileStore, - final FileSystemFactoryProvider provider, - final IGraphServiceClient client, - final Map env) { + public OneDriveFileSystemDriver(FileStore fileStore, + FileSystemFactoryProvider provider, + IGraphServiceClient client, + Runnable closer, + Map env) throws IOException { super(fileStore, provider); this.client = client; - ignoreAppleDouble = (Boolean) ((Map) env).getOrDefault("ignoreAppleDouble", Boolean.FALSE); -//System.err.println("ignoreAppleDouble: " + ignoreAppleDouble); - } - - /** ugly */ - private boolean isFile(DriveItem entry) { - return entry.file != null; - } + this.closer = closer; + setEnv(env); + boolean useSystemWatcher = (Boolean) ((Map) env).getOrDefault(ENV_USE_SYSTEM_WATCHER, false); - /** ugly */ - private boolean isFolder(DriveItem entry) { - return entry.folder != null; + if (useSystemWatcher) { + systemWatcher = new OneDriveWatchService(client); + systemWatcher.setNotificationListener(this::processNotification); + } } - /** */ - private Cache cache = new Cache() { - /** - * TODO when the parent is not cached - * @see #ignoreAppleDouble - * @throws NoSuchFileException must be thrown when the path is not found in this cache - */ - public DriveItem getEntry(Path path) throws IOException { - if (cache.containsFile(path)) { - return cache.getFile(path); - } else { - if (ignoreAppleDouble && path.getFileName() != null && Util.isAppleDouble(path)) { - throw new NoSuchFileException("ignore apple double file: " + path); - } - - String pathString = toPathString(path); -//Debug.println("path: " + pathString); + /** for system watcher */ + private void processNotification(String id, Kind kind) { + if (ENTRY_DELETE == kind) { + try { + Path path = cache.getEntry(e -> id.equals(e.id)); + cache.removeEntry(path); + } catch (NoSuchElementException e) { +Debug.println("NOTIFICATION: already deleted: " + id); + } + } else { + try { try { - DriveItem entry; - if (pathString.equals("/")) { - entry = client.drive().root().buildRequest().get(); - } else { - entry = client.drive().root().itemWithPath(toItemPathString(pathString.substring(1))).buildRequest().get(); - } - cache.putFile(path, entry); - return entry; - } catch (GraphServiceException e) { - if (e.getMessage().startsWith("Error code: itemNotFound")) { - if (cache.containsFile(path)) { - cache.removeEntry(path); - } - throw new NoSuchFileException(pathString); - } else { - throw e; - } + Path path = cache.getEntry(e -> id.equals(e.id)); +Debug.println("NOTIFICATION: maybe updated: " + path); + cache.removeEntry(path); + cache.getEntry(path); + } catch (NoSuchElementException e) { + DriveItem entry = client.drive().items(id).buildRequest().get(); + Path parent = cache.getEntry(f -> entry.parentReference.id.equals(f.id)); + Path path = parent.resolve(entry.name); +Debug.println("NOTIFICATION: maybe created: " + path); + cache.addEntry(path, entry); } + } catch (NoSuchElementException e) { +Debug.println("NOTIFICATION: parent not found: " + e); + } catch (IOException e) { + e.printStackTrace(); } } - }; + } + + @Override + protected boolean isFolder(DriveItem entry) { + return entry.folder != null; + } - @Nonnull @Override - public InputStream newInputStream(final Path path, final Set options) throws IOException { - final DriveItem entry = cache.getEntry(path); + protected String getFilenameString(DriveItem entry) { + return entry.name; + } - if (isFolder(entry)) { - throw new IsDirectoryException(path.toString()); + @Override + protected DriveItem getRootEntry(Path root) throws IOException { + return client.drive().root().buildRequest().get(); + } + + @Override + protected DriveItem getEntry(DriveItem parentEntry, Path path)throws IOException { + try { + return client.drive().root().itemWithPath(toItemPathString(toPathString(path))).buildRequest().get(); + } catch (GraphServiceException e) { + if (e.getMessage().startsWith("Error code: itemNotFound")) { + return null; + } else { + throw new IOException(e); + } } + } + @Override + protected InputStream downloadEntry(DriveItem entry, Path path, Set options) throws IOException { try { return client.drive().items(entry.id).content().buildRequest().get(); } catch (ClientException e) { @@ -157,26 +164,13 @@ public InputStream newInputStream(final Path path, final Set options) throws IOException { - try { - DriveItem entry = cache.getEntry(path); - - if (isFolder(entry)) { - throw new IsDirectoryException(path.toString()); - } else { - throw new FileAlreadyExistsException(path.toString()); - } - } catch (NoSuchFileException e) { -Debug.println("newOutputStream: " + e.getMessage()); - } - + protected OutputStream uploadEntry(DriveItem parentEntry, Path path, Set options) throws IOException { OneDriveUploadOption uploadOption = Util.getOneOfOptions(OneDriveUploadOption.class, options); if (uploadOption != null) { // java.nio.file is highly abstracted, so here source information is lost. // but onedrive graph api requires content length for upload. - // so reluctantly we provide {@link OneDriveUploadOpenOption} for {@link java.nio.file.Files#copy} options. + // so reluctantly we provide {@link OneDriveUploadOption} for {@link java.nio.file.Files#copy} options. Path source = uploadOption.getSource(); Debug.println("upload w/ option: " + source); @@ -209,7 +203,7 @@ public void progress(final long current, final long max) { } @Override public void success(final DriveItem result) { - cache.addEntry(path, result); + updateEntry(path, result); Debug.println("upload done: " + result.name); } @Override @@ -223,7 +217,7 @@ public void failure(final ClientException ex) { protected void onClosed() throws IOException { InputStream is = getInputStream(); DriveItem newEntry = client.drive().root().itemWithPath(toItemPathString(toPathString(path))).content().buildRequest().put(ByteStreams.toByteArray(is)); // TODO depends on guava - cache.addEntry(path, newEntry); + updateEntry(path, newEntry); } }; } @@ -242,7 +236,7 @@ public void progress(final long current, final long max) { } @Override public void success(final DriveItem result) { - cache.addEntry(path, result); + updateEntry(path, result); Debug.println("upload done: " + result.name); } @Override @@ -252,253 +246,165 @@ public void failure(final ClientException ex) { }); } else { DriveItem newEntry = client.drive().root().itemWithPath(toItemPathString(toPathString(path))).content().buildRequest().put(ByteStreams.toByteArray(is)); // TODO depends on guava - cache.addEntry(path, newEntry); + updateEntry(path, newEntry); } } - /** */ + /** ms-graph doesn't accept '+' in a path string */ private String toItemPathString(String pathString) throws IOException { - return URLEncoder.encode(pathString, "utf-8").replace("+", "%20"); + return URLEncoder.encode(pathString.replaceFirst("^\\/", ""), "utf-8").replace("+", "%20"); } - @Nonnull @Override - public DirectoryStream newDirectoryStream(final Path dir, - final DirectoryStream.Filter filter) throws IOException { - return Util.newDirectoryStream(getDirectoryEntries(dir, true), filter); + protected List getDirectoryEntries(DriveItem dirEntry, Path dir) throws IOException { + List list = new ArrayList<>(dirEntry.folder.childCount); + + IDriveItemCollectionPage pages = client.drive().items(dirEntry.id).children().buildRequest().get(); + while (pages != null) { + for (final DriveItem child : pages.getCurrentPage()) { + list.add(child); +//System.err.println("child: " + childPath.toRealPath().toString()); + } + pages = pages.getNextPage() != null ? pages.getNextPage().buildRequest().get() : null; + } + + return list; } @Override - public void createDirectory(final Path dir, final FileAttribute... attrs) throws IOException { - DriveItem parentEntry = cache.getEntry(dir.getParent()); - - // TODO: how to diagnose? + protected DriveItem createDirectoryEntry(DriveItem parentEntry, Path dir) throws IOException { DriveItem preEntry = new DriveItem(); preEntry.name = toFilenameString(dir); preEntry.folder = new Folder(); DriveItem newEntry = client.drive().items(parentEntry.id).children().buildRequest().post(preEntry); Debug.println(newEntry.id + ", " + newEntry.name + ", folder: " + isFolder(newEntry) + ", " + newEntry.hashCode()); - cache.addEntry(dir, newEntry); + return newEntry; } @Override - public void delete(final Path path) throws IOException { - removeEntry(path); + protected boolean hasChildren(DriveItem dirEntry, Path path) throws IOException { + IDriveItemCollectionPage pages = client.drive().items(dirEntry.id).children().buildRequest().get(); + return pages.getCurrentPage().size() > 0; } @Override - public void copy(final Path source, final Path target, final Set options) throws IOException { - if (cache.existsEntry(target)) { - if (options != null && options.stream().anyMatch(o -> o.equals(StandardCopyOption.REPLACE_EXISTING))) { - removeEntry(target); - } else { - throw new FileAlreadyExistsException(target.toString()); - } - } - copyEntry(source, target); + protected void removeEntry(DriveItem entry, Path path) throws IOException { + client.drive().items(entry.id).buildRequest().delete(); } @Override - public void move(final Path source, final Path target, final Set options) throws IOException { - if (cache.existsEntry(target)) { - if (isFolder(cache.getEntry(target))) { - if (options != null && options.stream().anyMatch(o -> o.equals(StandardCopyOption.REPLACE_EXISTING))) { - // replace the target - if (cache.getChildCount(target) > 0) { - throw new DirectoryNotEmptyException(target.toString()); - } else { - removeEntry(target); - moveEntry(source, target, false); - } - } else { - // move into the target - // TODO SPEC is FileAlreadyExistsException ? - moveEntry(source, target, true); - } - } else { - if (options != null && options.stream().anyMatch(o -> o.equals(StandardCopyOption.REPLACE_EXISTING))) { - removeEntry(target); - moveEntry(source, target, false); - } else { - throw new FileAlreadyExistsException(target.toString()); - } + protected DriveItem copyEntry(DriveItem sourceEntry, DriveItem targetParentEntry, Path source, Path target, Set options) throws IOException { + ItemReference ir = new ItemReference(); + ir.id = targetParentEntry.id; + IDriveItemCopyRequest request = client.drive().items(sourceEntry.id).copy(toFilenameString(target), ir).buildRequest(); + BaseRequest.class.cast(request).setHttpMethod(HttpMethod.POST); // #send() hides IDriveItemCopyRequest fields already set above. + DriveItemCopyBody body = new DriveItemCopyBody(); // ditto + body.name = toFilenameString(target); // ditto + body.parentReference = ir; // ditto + LraMonitorResponseHandler handler = new LraMonitorResponseHandler<>(); + @SuppressWarnings({ "unchecked", "rawtypes" }) // TODO + LraSession copySession = client.getHttpProvider().send((IHttpRequest) request, LraMonitorResult.class, body, (IStatefulResponseHandler) handler).getSession(); + LraMonitorProvider copyMonitorProvider = new LraMonitorProvider<>(copySession, client, DriveItem.class); + copyMonitorProvider.monitor(new IProgressCallback() { + @Override + public void progress(final long current, final long max) { +Debug.println("copy progress: " + current + "/" + max); } - } else { - if (source.getParent().equals(target.getParent())) { - // rename - renameEntry(source, target); - } else { - moveEntry(source, target, false); + @Override + public void success(final DriveItem result) { +Debug.println("copy done: " + result.id); + updateEntry(target, result); } - } + @Override + public void failure(final ClientException ex) { + throw new IllegalStateException(ex); + } + }); + // null means that copy is async, cache by your self + // @see IProgressCallback#success() + return null; } - /** - * Check access modes for a path on this filesystem - *

- * If no modes are provided to check for, this simply checks for the - * existence of the path. - *

- * - * @param path the path to check - * @param modes the modes to check for, if any - * @throws IOException filesystem level error, or a plain I/O error - * if you use this with javafs (jnr-fuse), you should throw {@link NoSuchFileException} when the file not found. - * @see FileSystemProvider#checkAccess(Path, AccessMode...) - */ @Override - protected void checkAccessImpl(final Path path, final AccessMode... modes) throws IOException { - final DriveItem entry = cache.getEntry(path); - - if (!isFile(entry)) { - return; - } - - // TODO: assumed; not a file == directory - for (final AccessMode mode : modes) { - if (mode == AccessMode.EXECUTE) { - throw new AccessDeniedException(path.toString()); - } - } + protected DriveItem moveEntry(DriveItem sourceEntry, DriveItem targetParentEntry, Path source, Path target, boolean targetIsParent) throws IOException { + DriveItem preEntry = new DriveItem(); + preEntry.name = toFilenameString(target); + preEntry.parentReference = new ItemReference(); + preEntry.parentReference.id = targetParentEntry.id; + return client.drive().items(sourceEntry.id).buildRequest().patch(preEntry); } @Override - public void close() throws IOException { - // TODO: what to do here? OneDriveClient does not implement Closeable :( + protected DriveItem moveFolderEntry(DriveItem sourceEntry, DriveItem targetParentEntry, Path source, Path target, boolean targetIsParent) throws IOException { + DriveItem preEntry = new DriveItem(); + preEntry.name = toFilenameString(target); + preEntry.parentReference = new ItemReference(); + preEntry.parentReference.id = targetParentEntry.id; + return client.drive().items(sourceEntry.id).buildRequest().patch(preEntry); } - /** - * @throws IOException you should throw {@link NoSuchFileException} when the file not found. - */ - @Nonnull @Override - protected Object getPathMetadataImpl(final Path path) throws IOException { - return cache.getEntry(path); + protected DriveItem renameEntry(DriveItem sourceEntry, DriveItem targetParentEntry, Path source, Path target) throws IOException { + DriveItem preEntry = new DriveItem(); + preEntry.name = toFilenameString(target); + return client.drive().items(sourceEntry.id).buildRequest().patch(preEntry); } - /** */ - private List getDirectoryEntries(Path dir, boolean useCache) throws IOException { - final DriveItem entry = cache.getEntry(dir); - - if (!isFolder(entry)) { -//System.err.println(entry.name + ", " + entry.id + ", " + entry.hashCode()); - throw new NotDirectoryException(dir.toString()); - } - - List list = null; - if (useCache && cache.containsFolder(dir)) { - list = cache.getFolder(dir); - } else { - list = new ArrayList<>(entry.folder.childCount); - - IDriveItemCollectionPage pages = client.drive().items(entry.id).children().buildRequest().get(); - while (pages != null) { - for (final DriveItem child : pages.getCurrentPage()) { - Path childPath = dir.resolve(child.name); - list.add(childPath); -//System.err.println("child: " + childPath.toRealPath().toString()); - - cache.putFile(childPath, child); - } - pages = pages.getNextPage() != null ? pages.getNextPage().buildRequest().get() : null; - } - cache.putFolder(dir, list); - } + @Override + protected Object getPathMetadata(DriveItem entry) throws IOException { + return new Metadata(this, entry); + } - return list; + @Override + public void close() throws IOException { + closer.run(); } - /** */ - private void removeEntry(Path path) throws IOException { - DriveItem entry = cache.getEntry(path); - if (isFolder(entry)) { - if (getDirectoryEntries(path, false).size() > 0) { - throw new DirectoryNotEmptyException(path.toString()); - } + @Override + public WatchService newWatchService() { + try { + return new OneDriveWatchService(client); + } catch (IOException e) { + throw new IllegalStateException(e); } + } - // TODO: unknown what happens when a move operation is performed - // and the target already exists - client.drive().items(entry.id).buildRequest().delete(); + // + // user:attributes + // + /** attributes user:description */ + void patchEntryDescription(DriveItem sourceEntry, String description) throws IOException { + DriveItem preEntry = new DriveItem(); + preEntry.description = description; + DriveItem newEntry = client.drive().items(sourceEntry.id).buildRequest().patch(preEntry); + Path path = cache.getEntry(sourceEntry); cache.removeEntry(path); - } - - /** */ - private void copyEntry(final Path source, final Path target) throws IOException { - DriveItem sourceEntry = cache.getEntry(source); - DriveItem targetParentEntry = cache.getEntry(target.getParent()); - if (isFile(sourceEntry)) { - ItemReference ir = new ItemReference(); - ir.id = targetParentEntry.id; - IDriveItemCopyRequest request = client.drive().items(sourceEntry.id).copy(toFilenameString(target), ir).buildRequest(); - BaseRequest.class.cast(request).setHttpMethod(HttpMethod.POST); // #send() hides IDriveItemCopyRequest fields already set above. - DriveItemCopyBody body = new DriveItemCopyBody(); // ditto - body.name = toFilenameString(target); // ditto - body.parentReference = ir; // ditto - LraMonitorResponseHandler handler = new LraMonitorResponseHandler<>(); - @SuppressWarnings({ "unchecked", "rawtypes" }) // TODO - LraSession copySession = client.getHttpProvider().send((IHttpRequest) request, LraMonitorResult.class, body, (IStatefulResponseHandler) handler).getSession(); - LraMonitorProvider copyMonitorProvider = new LraMonitorProvider<>(copySession, client, DriveItem.class); - copyMonitorProvider.monitor(new IProgressCallback() { - @Override - public void progress(final long current, final long max) { -Debug.println("copy progress: " + current + "/" + max); - } - @Override - public void success(final DriveItem result) { -Debug.println("copy done: " + result.id); - cache.addEntry(target, result); - } - @Override - public void failure(final ClientException ex) { - throw new IllegalStateException(ex); - } - }); - } else if (isFolder(sourceEntry)) { - // TODO java spec. allows empty folder - throw new IsDirectoryException("source can not be a folder: " + source); - } + cache.addEntry(path, newEntry); } /** - * @param targetIsParent if the target is folder + * attributes user:thumbnail + * @param image currently only jpeg is available. */ - private void moveEntry(final Path source, final Path target, boolean targetIsParent) throws IOException { - DriveItem sourceEntry = cache.getEntry(source); - DriveItem targetParentEntry = cache.getEntry(targetIsParent ? target : target.getParent()); - if (isFile(sourceEntry)) { - DriveItem preEntry = new DriveItem(); - preEntry.name = targetIsParent ? toFilenameString(source) : toFilenameString(target); - preEntry.parentReference = new ItemReference(); - preEntry.parentReference.id = targetParentEntry.id; - DriveItem patchedEntry = client.drive().items(sourceEntry.id).buildRequest().patch(preEntry); - cache.removeEntry(source); - if (targetIsParent) { - cache.addEntry(target.resolve(source.getFileName()), patchedEntry); - } else { - cache.addEntry(target, patchedEntry); - } - } else if (isFolder(sourceEntry)) { - DriveItem preEntry = new DriveItem(); - preEntry.name = toFilenameString(target); - preEntry.folder = new Folder(); - preEntry.parentReference = new ItemReference(); - preEntry.parentReference.id = targetParentEntry.id; - DriveItem patchedEntry = client.drive().items(sourceEntry.id).buildRequest().patch(preEntry); - cache.moveEntry(source, target, patchedEntry); - } + void setThumbnail(DriveItem sourceEntry, byte[] image) throws IOException { + ThumbnailUploadProvider provider = new ThumbnailUploadProvider(sourceEntry, client); + provider.upload(image); +Debug.println(Level.INFO, "thumbnail updated: " + sourceEntry.name + ", size: " + image.length); } - /** */ - private void renameEntry(final Path source, final Path target) throws IOException { - DriveItem sourceEntry = cache.getEntry(source); -//Debug.println(sourceEntry.id + ", " + sourceEntry.name); - - DriveItem preEntry = new DriveItem(); - preEntry.name = toFilenameString(target); - DriveItem patchedEntry = client.drive().items(sourceEntry.id).buildRequest().patch(preEntry); - cache.removeEntry(source); - cache.addEntry(target, patchedEntry); + /** + * attributes user:thumbnail + * @return url + * @see "https://stackoverflow.com/a/45027853" + */ + String getThumbnail(DriveItem sourceEntry) throws IOException { + IThumbnailSetCollectionPage page = client.drive().items(sourceEntry.id) + .thumbnails() + .buildRequest(Arrays.asList(new QueryOption("select", "source"))) + .get(); + ThumbnailSet set = page.getCurrentPage().get(0); +Debug.println(Level.INFO, "thumbnail url: " + set.source.url); + return set.source.url; } } diff --git a/vavi-nio-file-onedrive4/src/main/java/vavi/nio/file/onedrive4/OneDriveFileSystemProvider.java b/vavi-nio-file-onedrive4/src/main/java/vavi/nio/file/onedrive4/OneDriveFileSystemProvider.java index 62b69198..e61f9482 100644 --- a/vavi-nio-file-onedrive4/src/main/java/vavi/nio/file/onedrive4/OneDriveFileSystemProvider.java +++ b/vavi-nio-file-onedrive4/src/main/java/vavi/nio/file/onedrive4/OneDriveFileSystemProvider.java @@ -26,6 +26,8 @@ public final class OneDriveFileSystemProvider extends FileSystemProviderBase { public static final String ENV_APP_CREDENTIAL = "app_credential"; + public static final String ENV_USE_SYSTEM_WATCHER = "use_system_watcher"; + public OneDriveFileSystemProvider() { super(new OneDriveFileSystemRepository()); } diff --git a/vavi-nio-file-onedrive4/src/main/java/vavi/nio/file/onedrive4/OneDriveFileSystemRepository.java b/vavi-nio-file-onedrive4/src/main/java/vavi/nio/file/onedrive4/OneDriveFileSystemRepository.java index a75f3a55..4fd3d688 100644 --- a/vavi-nio-file-onedrive4/src/main/java/vavi/nio/file/onedrive4/OneDriveFileSystemRepository.java +++ b/vavi-nio-file-onedrive4/src/main/java/vavi/nio/file/onedrive4/OneDriveFileSystemRepository.java @@ -80,7 +80,8 @@ public FileSystemDriver createDriver(final URI uri, final Map env) th } // 3. process - String accessToken = new MicrosoftGraphOAuth2(appCredential, true).authorize(userCredential); + MicrosoftGraphOAuth2 oAuth2 = new MicrosoftGraphOAuth2(appCredential, true); + String accessToken = oAuth2.authorize(userCredential); //Debug.println("accessToken: " + accessToken); IAuthenticationProvider authenticationProvider = new IAuthenticationProvider() { @@ -95,6 +96,6 @@ public void authenticateRequest(IHttpRequest request) { .buildClient(); final OneDriveFileStore fileStore = new OneDriveFileStore(graphClient, factoryProvider.getAttributesFactory()); - return new OneDriveFileSystemDriver(fileStore, factoryProvider, graphClient, env); + return new OneDriveFileSystemDriver(fileStore, factoryProvider, graphClient, () -> oAuth2.close(), env); } } diff --git a/vavi-nio-file-onedrive4/src/main/java/vavi/nio/file/onedrive4/OneDriveUploadOption.java b/vavi-nio-file-onedrive4/src/main/java/vavi/nio/file/onedrive4/OneDriveUploadOption.java index 73e32e31..83f38eb3 100644 --- a/vavi-nio-file-onedrive4/src/main/java/vavi/nio/file/onedrive4/OneDriveUploadOption.java +++ b/vavi-nio-file-onedrive4/src/main/java/vavi/nio/file/onedrive4/OneDriveUploadOption.java @@ -14,6 +14,10 @@ /** * OneDriveUploadOption. *

+ * in {@link com.github.fge.filesystem.options.FileSystemOptionsFactory}, + * instances of this class are compared by contains method. so i override + * {@link #equals(Object)} and {@link #hashCode()}. + *

* TODO CopyOption doesn't work. * * @author Naohide Sano (umjammer) @@ -21,6 +25,9 @@ */ public class OneDriveUploadOption implements OpenOption, CopyOption { + /** */ + private static final long serialVersionUID = -3760090552182064957L; + /** */ private Path source; @@ -41,7 +48,7 @@ public boolean equals(Object other) { @Override public int hashCode() { - return Long.hashCode(-3760090552182064957L); // TODO ad-hoc + return Long.hashCode(serialVersionUID); // TODO ad-hoc } } diff --git a/vavi-nio-file-onedrive4/src/main/java/vavi/nio/file/onedrive4/OneDriveUserDefinedFileAttributesProvider.java b/vavi-nio-file-onedrive4/src/main/java/vavi/nio/file/onedrive4/OneDriveUserDefinedFileAttributesProvider.java new file mode 100644 index 00000000..1f7c8b43 --- /dev/null +++ b/vavi-nio-file-onedrive4/src/main/java/vavi/nio/file/onedrive4/OneDriveUserDefinedFileAttributesProvider.java @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2020 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package vavi.nio.file.onedrive4; + +import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +import com.github.fge.filesystem.attributes.provider.UserDefinedFileAttributesProvider; +import com.microsoft.graph.models.extensions.DriveItem; + +import vavi.nio.file.onedrive4.OneDriveFileAttributesFactory.Metadata; +import vavi.util.Debug; + + +/** + * OneDriveUserDefinedFileAttributesProvider. + * + * @author Naohide Sano (umjammer) + * @version 0.00 2020/09/08 umjammer initial version
+ */ +public class OneDriveUserDefinedFileAttributesProvider extends UserDefinedFileAttributesProvider { + + /** driver & file entry */ + private final Metadata entry; + + /** */ + public OneDriveUserDefinedFileAttributesProvider(Metadata entry) throws IOException { + this.entry = entry; + } + + /** pre-listed */ + private static final List list = Arrays.stream(UserAttributes.values()).map(e -> e.name()).collect(Collectors.toList()); + + @Override + public List list() throws IOException { + return list; + } + + @Override + public int size(String name) throws IOException { + return UserAttributes.valueOf(name).size(entry); + } + + @Override + public int read(String name, ByteBuffer dst) throws IOException { + return UserAttributes.valueOf(name).read(entry, dst); + } + + @Override + public int write(final String name, final ByteBuffer src) throws IOException { + return UserAttributes.valueOf(name).write(entry, src); + } + + private interface UserAttribute { + int size(T entry) throws IOException; + int read(T entry, ByteBuffer dst) throws IOException; + int write(T entry, ByteBuffer src) throws IOException; + } + + private enum UserAttributes implements UserAttribute { + description { + public int size(Metadata entry) throws IOException { + String description = entry.driveItem.description; +Debug.println("size " + name() + ": " + description); + return description == null ? 0 : description.getBytes().length; + } + public int read(Metadata entry, ByteBuffer dst) throws IOException { + String description = entry.driveItem.description; +Debug.println("read " + name() + ": " + description); + if (description != null) { + dst.put(description.getBytes()); + } + return dst.array().length; + } + public int write(Metadata entry, ByteBuffer src) throws IOException { + String description = new String(src.array()); +Debug.println("write " + name() + ": " + description); + entry.driver.patchEntryDescription(entry.driveItem, description); + return description.getBytes().length; + } + }, + /** whole image file */ + thumbnail { + /** */ + Map thumbnailCache = new ConcurrentHashMap<>(); // TODO LRU + + /** */ + private String getUrl(Metadata entry) throws IOException { + String url = thumbnailCache.get(entry.driveItem); + if (url != null) { + return url; + } else { + url = entry.driver.getThumbnail(entry.driveItem); + if (url != null) { + thumbnailCache.put(entry.driveItem, url); + } + return url; + } + } + + /** */ + private byte[] getThumbnail(Metadata entry) throws IOException { + String url = getUrl(entry); + InputStream is = new BufferedInputStream(new URL(url).openStream()); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + byte[] buffer = new byte[8024]; + int l = 0; + while ((l = is.read(buffer)) != -1) { + baos.write(buffer, 0, l); + } + return baos.toByteArray(); + } + + @Override + public int size(Metadata entry) throws IOException { + String url = getUrl(entry); + if (url != null) { + byte[] thumbnail = getThumbnail(entry); + if (thumbnail != null) { + return thumbnail.length; + } + } + return 0; + } + + @Override + public int read(Metadata entry, ByteBuffer dst) throws IOException { + String url = getUrl(entry); + if (url != null) { + byte[] thumbnail = getThumbnail(entry); + if (thumbnail != null) { + dst.put(thumbnail); + return thumbnail.length; + } + } + return 0; + } + + @Override + public int write(Metadata entry, ByteBuffer src) throws IOException { + byte[] thumbnail = src.array(); + entry.driver.setThumbnail(entry.driveItem, thumbnail); + return thumbnail.length; + } + }; + } +} + +/* */ diff --git a/vavi-nio-file-onedrive4/src/main/java/vavi/nio/file/onedrive4/OneDriveWatchService.java b/vavi-nio-file-onedrive4/src/main/java/vavi/nio/file/onedrive4/OneDriveWatchService.java new file mode 100644 index 00000000..c59e1543 --- /dev/null +++ b/vavi-nio-file-onedrive4/src/main/java/vavi/nio/file/onedrive4/OneDriveWatchService.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2020 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package vavi.nio.file.onedrive4; + +import java.io.IOException; + +import com.microsoft.graph.models.extensions.IGraphServiceClient; + +import vavi.nio.file.watch.webhook.WebHookBaseWatchService; +import vavi.util.Debug; + + +/** + * OneDriveWatchService. + *

+ * system properties + *

    + *
  • vavi.nio.file.watch.webhook.NotificationProvider.onedrive4 + *
+ *

+ * + * @author Naohide Sano (umjammer) + * @version 0.00 2020/07/23 umjammer initial version
+ */ +public class OneDriveWatchService extends WebHookBaseWatchService { + + private static final String WEBHOOK_NOTIFICATION_PROVIDER = + System.getProperty("vavi.nio.file.watch.webhook.NotificationProvider.onedrive4", ".onedrive4.webhook.websocket"); + +// private IGraphServiceClient client; + +// private String savedStartPageToken; + + /** */ + public OneDriveWatchService(IGraphServiceClient client) throws IOException { +// this.client = client; + + setupNotification(this, WEBHOOK_NOTIFICATION_PROVIDER); + } + + @Override + protected void onNotifyMessage(String notification) throws IOException { +Debug.println(">> notification: done"); + } + + @Override + public void close() throws IOException { + if (isOpen()) { + super.close(); + } + } +} diff --git a/vavi-nio-file-onedrive4/src/main/java/vavi/nio/file/onedrive4/graph/LraMonitorResponseHandler.java b/vavi-nio-file-onedrive4/src/main/java/vavi/nio/file/onedrive4/graph/LraMonitorResponseHandler.java index d5959944..d47ab9d4 100644 --- a/vavi-nio-file-onedrive4/src/main/java/vavi/nio/file/onedrive4/graph/LraMonitorResponseHandler.java +++ b/vavi-nio-file-onedrive4/src/main/java/vavi/nio/file/onedrive4/graph/LraMonitorResponseHandler.java @@ -18,6 +18,9 @@ import com.microsoft.graph.logger.ILogger; import com.microsoft.graph.serializer.ISerializer; +import okhttp3.Response; // TODO WTF??? why not be encapsulated in IConnection??? who did this??? crazy! + + /** * Handles the stateful response from the OneDrive LRA session * @@ -33,7 +36,44 @@ public class LraMonitorResponseHandler */ @Override public void configConnection(final IConnection connection) { - return; + } + + @Override + public void configConnection(Response response) { + } + + @Override + public LraMonitorResult generateResult(IHttpRequest request, + Response response, + ISerializer serializer, + ILogger logger) throws Exception { + // TODO all about response can be encapsulated into IConnection! + // why i need to write twice... + if (response.code() == HttpResponseCode.HTTP_ACCEPTED) { +logger.logDebug("LRA has been accepted by the server."); + final LraSession session = new LraSession(); + session.monitorUrl = response.header("Location"); + return new LraMonitorResult(session); + } else if (response.code() == HttpResponseCode.HTTP_SEE_OTHER) { +logger.logDebug("see other url."); + String seeOtherUrl = response.header("Location"); + return new LraMonitorResult(seeOtherUrl); + } else if (response.code() == HttpResponseCode.HTTP_CREATED + || response.code() == HttpResponseCode.HTTP_OK) { +logger.logDebug("LRA session is completed, drive item returned."); + String rawJson = response.body().string(); + MonitorObject monitoredItem = serializer.deserializeObject(rawJson, MonitorObject.class); + + return new LraMonitorResult(monitoredItem); + } else if (response.code() >= HttpResponseCode.HTTP_CLIENT_ERROR) { +logger.logDebug("LRA error during monitor, see detail on result error"); + return new LraMonitorResult( + GraphServiceException.createFromConnection(request, null, serializer, + response, logger)); + } else { +logger.logDebug("unhandled response code: " + response.code()); + return null; + } } /** diff --git a/vavi-nio-file-onedrive4/src/main/java/vavi/nio/file/onedrive4/graph/MyLogger.java b/vavi-nio-file-onedrive4/src/main/java/vavi/nio/file/onedrive4/graph/MyLogger.java index ec20850b..7eeb30ca 100644 --- a/vavi-nio-file-onedrive4/src/main/java/vavi/nio/file/onedrive4/graph/MyLogger.java +++ b/vavi-nio-file-onedrive4/src/main/java/vavi/nio/file/onedrive4/graph/MyLogger.java @@ -26,7 +26,7 @@ public class MyLogger implements ILogger { /** * Sets the logging level of this logger - * + * * @param level the level to log at */ public void setLoggingLevel(final LoggerLevel level) { @@ -36,7 +36,7 @@ public void setLoggingLevel(final LoggerLevel level) { /** * Gets the logging level of this logger - * + * * @return the level the logger is set to */ public LoggerLevel getLoggingLevel() { @@ -45,7 +45,7 @@ public LoggerLevel getLoggingLevel() { /** * Creates the tag automatically - * + * * @return the tag for the current method Sourced from * https://gist.github.com/eefret/a9c7ac052854a10a8936 */ @@ -68,7 +68,7 @@ private String getTag() { /** * Logs a debug message - * + * * @param message the message */ @Override @@ -81,7 +81,7 @@ public void logDebug(final String message) { /** * Logs an error message with throwable - * + * * @param message the message * @param throwable the throwable */ diff --git a/vavi-nio-file-onedrive4/src/main/java/vavi/nio/file/onedrive4/graph/ThumbnailUploadProvider.java b/vavi-nio-file-onedrive4/src/main/java/vavi/nio/file/onedrive4/graph/ThumbnailUploadProvider.java new file mode 100644 index 00000000..e2b6943c --- /dev/null +++ b/vavi-nio-file-onedrive4/src/main/java/vavi/nio/file/onedrive4/graph/ThumbnailUploadProvider.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2020 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package vavi.nio.file.onedrive4.graph; + + +import java.io.IOException; +import java.security.InvalidParameterException; + +import com.microsoft.graph.http.BaseRequest; +import com.microsoft.graph.http.HttpMethod; +import com.microsoft.graph.http.HttpResponseCode; +import com.microsoft.graph.http.IHttpRequest; +import com.microsoft.graph.models.extensions.DriveItem; +import com.microsoft.graph.models.extensions.IGraphServiceClient; + +import vavi.util.Debug; + + +/** + * ThumbnailUpload service provider + * + * @param the upload item type + */ +public class ThumbnailUploadProvider { + + /** + * The client + */ + private final IGraphServiceClient client; + + /** + * The upload URL + */ + private final String uploadUrl; + + /** + * Creates the ChunkedUploadProvider + * + * @param uploadSession the initial upload session + * @param client the Graph client + * @param streamSize the stream size + * @param uploadTypeClass the upload type class + */ + public ThumbnailUploadProvider(final DriveItem item, + final IGraphServiceClient client) { + if (item == null) { + throw new InvalidParameterException("item is null."); + } + + if (client == null) { + throw new InvalidParameterException("OneDrive client is null."); + } + + this.client = client; + this.uploadUrl = client.getServiceRoot() + "/drive/items/" + item.id + "/thumbnails/0/source/content"; +Debug.println("url: " + uploadUrl); + } + + /** + * Uploads a thumbnail. + * + * @param image the thumbnail bytes + * @throws IOException the IO exception that occurred during upload + */ + public void upload(byte[] image) + throws IOException { + + BaseRequest request = new BaseRequest(uploadUrl, client, null, Integer.class) {{ + setHttpMethod(HttpMethod.PUT); + }}; + int result = client.getHttpProvider().send( + (IHttpRequest) request, + Integer.class, + image, + new ThumbnailUploadResponseHandler()); + + if (result != HttpResponseCode.HTTP_OK) { + throw new IOException(String.valueOf(result)); + } + } +} diff --git a/vavi-nio-file-onedrive4/src/main/java/vavi/nio/file/onedrive4/graph/ThumbnailUploadResponseHandler.java b/vavi-nio-file-onedrive4/src/main/java/vavi/nio/file/onedrive4/graph/ThumbnailUploadResponseHandler.java new file mode 100644 index 00000000..0bbd3daf --- /dev/null +++ b/vavi-nio-file-onedrive4/src/main/java/vavi/nio/file/onedrive4/graph/ThumbnailUploadResponseHandler.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2019 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package vavi.nio.file.onedrive4.graph; + +import com.microsoft.graph.http.IConnection; +import com.microsoft.graph.http.IHttpRequest; +import com.microsoft.graph.http.IStatefulResponseHandler; +import com.microsoft.graph.logger.ILogger; +import com.microsoft.graph.serializer.ISerializer; + +import okhttp3.Response; // TODO WTF??? why not be encapsulated in IConnection??? who did this??? crazy! + + +/** + * Handles the response from the ThumbnailUpload + */ +public class ThumbnailUploadResponseHandler + implements IStatefulResponseHandler { + + /** + * Do nothing before getting the response + * + * @param connection the connection + */ + @Override + public void configConnection(final IConnection connection) { + } + + @Override + public void configConnection(Response response) { + } + + @Override + public Integer generateResult(IHttpRequest request, + Response response, + ISerializer serializer, + ILogger logger) throws Exception { + // why i need to write twice... + return response.code(); + } + + /** + * Generate the LRA monitor response result + * + * @param request the HTTP request + * @param connection the HTTP connection + * @param serializer the serializer + * @param logger the system logger + * @return the LRA monitor result, which could be either an LRA monitor or error + * @throws Exception an exception occurs if the request was unable to complete for any reason + */ + @Override + public Integer generateResult( + final IHttpRequest request, + final IConnection connection, + final ISerializer serializer, + final ILogger logger) throws Exception { + + return connection.getResponseCode(); + } +} diff --git a/vavi-nio-file-onedrive4/src/main/java/vavi/nio/file/onedrive4/webhook/websocket/MicrosoftWebSocketNotification.java b/vavi-nio-file-onedrive4/src/main/java/vavi/nio/file/onedrive4/webhook/websocket/MicrosoftWebSocketNotification.java new file mode 100644 index 00000000..d12f3bde --- /dev/null +++ b/vavi-nio-file-onedrive4/src/main/java/vavi/nio/file/onedrive4/webhook/websocket/MicrosoftWebSocketNotification.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2020 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package vavi.nio.file.onedrive4.webhook.websocket; + +import java.io.IOException; +import java.net.URI; +import java.util.function.Consumer; + +import javax.websocket.ClientEndpoint; +import javax.websocket.Session; + +import vavi.nio.file.watch.webhook.websocket.BasicAuthorizationConfigurator; +import vavi.nio.file.watch.webhook.websocket.StringWebSocketNotification; + + +/** + * MicrosoftWebSocketNotification. + *

+ * environment variables + *

    + *
  • VAVI_APPS_WEBHOOK_WEBSOCKET_BASE_URL + *
  • VAVI_APPS_WEBHOOK_WEBSOCKET_MICROSOFT_PATH + *
+ * + * @author Naohide Sano (umjammer) + * @version 0.00 2020/07/23 umjammer initial version
+ */ +@ClientEndpoint(configurator = BasicAuthorizationConfigurator.class) +public class MicrosoftWebSocketNotification extends StringWebSocketNotification { + + private static final String websocketBaseUrl = System.getenv("VAVI_APPS_WEBHOOK_WEBSOCKET_BASE_URL"); + private static final String websocketPath = System.getenv("VAVI_APPS_WEBHOOK_WEBSOCKET_MICROSOFT_PATH"); + + private static final URI uri = URI.create(websocketBaseUrl + websocketPath); + + private Consumer callback; + + /** + * @param args + */ + public MicrosoftWebSocketNotification(Consumer callback, Object... args) throws IOException { + super(uri, args); + this.callback = callback; + } + + @Override + public void onOpenImpl(Session session) throws IOException { + } + + @Override + protected void onNotifyMessageImpl(String notification) throws IOException { + callback.accept(notification); + } + + @Override + protected void onCloseImpl(Session session) throws IOException { + } +} + +/* */ diff --git a/vavi-nio-file-onedrive4/src/main/java/vavi/nio/file/onedrive4/webhook/websocket/MicrosoftWebSocketNotificationProvider.java b/vavi-nio-file-onedrive4/src/main/java/vavi/nio/file/onedrive4/webhook/websocket/MicrosoftWebSocketNotificationProvider.java new file mode 100644 index 00000000..ba77c469 --- /dev/null +++ b/vavi-nio-file-onedrive4/src/main/java/vavi/nio/file/onedrive4/webhook/websocket/MicrosoftWebSocketNotificationProvider.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2020 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package vavi.nio.file.onedrive4.webhook.websocket; + +import java.io.IOException; +import java.util.function.Consumer; + +import vavi.nio.file.watch.webhook.Notification; +import vavi.nio.file.watch.webhook.NotificationProvider; + + +/** + * MicrosoftWebSocketNotificationProvider. + * + * @author Naohide Sano (umjammer) + * @version 0.00 2020/07/23 umjammer initial version
+ */ +public class MicrosoftWebSocketNotificationProvider implements NotificationProvider { + + @Override + public Notification getNotification(Consumer callback, Object... args) throws IOException { + return Notification.class.cast(new MicrosoftWebSocketNotification(Consumer.class.cast(callback), args)); + } +} + +/* */ diff --git a/vavi-nio-file-onedrive4/src/main/resources/META-INF/services/vavi.nio.file.watch.webhook.NotificationProvider b/vavi-nio-file-onedrive4/src/main/resources/META-INF/services/vavi.nio.file.watch.webhook.NotificationProvider new file mode 100644 index 00000000..19b2e9d9 --- /dev/null +++ b/vavi-nio-file-onedrive4/src/main/resources/META-INF/services/vavi.nio.file.watch.webhook.NotificationProvider @@ -0,0 +1 @@ +vavi.nio.file.onedrive4.webhook.websocket.MicrosoftWebSocketNotificationProvider diff --git a/vavi-nio-file-onedrive4/src/test/java/vavi/nio/file/onedrive4/Main.java b/vavi-nio-file-onedrive4/src/test/java/vavi/nio/file/onedrive4/Main.java index 0bb19eec..5bf7c384 100644 --- a/vavi-nio-file-onedrive4/src/test/java/vavi/nio/file/onedrive4/Main.java +++ b/vavi-nio-file-onedrive4/src/test/java/vavi/nio/file/onedrive4/Main.java @@ -7,6 +7,8 @@ package vavi.nio.file.onedrive4; import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.Files; import java.util.Collections; import org.junit.jupiter.api.Test; @@ -28,10 +30,20 @@ public class Main { @Test void test01() throws Exception { - String email = System.getenv("MICROSOFT4_TEST_ACCOUNT"); + String email = System.getenv("MICROSOFT_TEST_ACCOUNT"); URI uri = URI.create("onedrive:///?id=" + email); - testAll(new OneDriveFileSystemProvider().newFileSystem(uri, Collections.EMPTY_MAP)); + testAll(new OneDriveFileSystemProvider().newFileSystem(uri, Collections.emptyMap())); + } + + /** */ + public static void main(String[] args) throws Exception { + String email = System.getenv("MICROSOFT_TEST_ACCOUNT"); + + URI uri = URI.create("onedrive:///?id=" + email); + + FileSystem fs = new OneDriveFileSystemProvider().newFileSystem(uri, Collections.emptyMap()); + Files.list(fs.getPath("/")).forEach(System.out::println); } } \ No newline at end of file diff --git a/vavi-nio-file-onedrive4/src/test/java/vavi/nio/file/onedrive4/Main2.java b/vavi-nio-file-onedrive4/src/test/java/vavi/nio/file/onedrive4/Main2.java index ad48f75b..5c9a24b7 100644 --- a/vavi-nio-file-onedrive4/src/test/java/vavi/nio/file/onedrive4/Main2.java +++ b/vavi-nio-file-onedrive4/src/test/java/vavi/nio/file/onedrive4/Main2.java @@ -25,10 +25,10 @@ public class Main2 { @Test void test01() throws Exception { - String email = System.getenv("MICROSOFT4_TEST_ACCOUNT"); + String email = System.getenv("MICROSOFT_TEST_ACCOUNT"); URI uri = URI.create("onedrive:///?id=" + email); - FileSystem fs = new OneDriveFileSystemProvider().newFileSystem(uri, Collections.EMPTY_MAP); + FileSystem fs = new OneDriveFileSystemProvider().newFileSystem(uri, Collections.emptyMap()); testLargeFile(fs, OneDriveUploadOption.class); } diff --git a/vavi-nio-file-onedrive4/src/test/java/vavi/nio/file/onedrive4/Main3.java b/vavi-nio-file-onedrive4/src/test/java/vavi/nio/file/onedrive4/Main3.java index db5bebd7..40206e39 100644 --- a/vavi-nio-file-onedrive4/src/test/java/vavi/nio/file/onedrive4/Main3.java +++ b/vavi-nio-file-onedrive4/src/test/java/vavi/nio/file/onedrive4/Main3.java @@ -28,7 +28,7 @@ void test01() throws Exception { String email = System.getenv("MICROSOFT4_TEST_ACCOUNT"); URI uri = URI.create("onedrive:///?id=" + email); - FileSystem fs = new OneDriveFileSystemProvider().newFileSystem(uri, Collections.EMPTY_MAP); + FileSystem fs = new OneDriveFileSystemProvider().newFileSystem(uri, Collections.emptyMap()); testMoveFolder(fs); } diff --git a/vavi-nio-file-onedrive4/src/test/java/vavi/nio/file/onedrive4/Main4.java b/vavi-nio-file-onedrive4/src/test/java/vavi/nio/file/onedrive4/Main4.java index 8f6dcbd9..e645c5d4 100644 --- a/vavi-nio-file-onedrive4/src/test/java/vavi/nio/file/onedrive4/Main4.java +++ b/vavi-nio-file-onedrive4/src/test/java/vavi/nio/file/onedrive4/Main4.java @@ -73,7 +73,7 @@ public void test01(String providerClassName) throws Exception { // - public static void main(final String... args) throws IOException { + public static void main(String[] args) throws IOException { String email = args[1]; Map env = new HashMap<>(); @@ -83,13 +83,13 @@ public static void main(final String... args) throws IOException { FileSystem fs = new OneDriveFileSystemProvider().newFileSystem(uri, env); -// System.setProperty("vavi.net.fuse.FuseProvider.class", "vavi.net.fuse.javafs.JavaFSFuseProvider"); + System.setProperty("vavi.net.fuse.FuseProvider.class", "vavi.net.fuse.javafs.JavaFSFuseProvider"); // System.setProperty("vavi.net.fuse.FuseProvider.class", "vavi.net.fuse.jnrfuse.JnrFuseFuseProvider"); Map options = new HashMap<>(); options.put("fsname", "onedrive_fs" + "@" + System.currentTimeMillis()); options.put("noappledouble", null); - options.put(vavi.net.fuse.javafs.JavaFSFuse.ENV_DEBUG, true); + options.put(vavi.net.fuse.javafs.JavaFSFuse.ENV_DEBUG, false); options.put(vavi.net.fuse.javafs.JavaFSFuse.ENV_READ_ONLY, false); Fuse.getFuse().mount(fs, args[0], options); diff --git a/vavi-nio-file-onedrive4/src/test/java/vavi/nio/file/onedrive4/Main5.java b/vavi-nio-file-onedrive4/src/test/java/vavi/nio/file/onedrive4/Main5.java index d2446466..db3e20b9 100644 --- a/vavi-nio-file-onedrive4/src/test/java/vavi/nio/file/onedrive4/Main5.java +++ b/vavi-nio-file-onedrive4/src/test/java/vavi/nio/file/onedrive4/Main5.java @@ -41,7 +41,7 @@ void test01() throws Exception { Path dstDir; Path dst; String a, b; - try (FileSystem onedrivefs = new OneDriveFileSystemProvider().newFileSystem(uri, Collections.EMPTY_MAP)) { + try (FileSystem onedrivefs = new OneDriveFileSystemProvider().newFileSystem(uri, Collections.emptyMap())) { src = Paths.get("src/test/resources/Hello.java"); dstDir = onedrivefs.getPath("/").resolve("TEST_FUSE_5"); @@ -60,7 +60,7 @@ void test01() throws Exception { a = Util.toFilenameString(Files.list(dstDir).findFirst().get()); } - try (FileSystem onedrivefs = new OneDriveFileSystemProvider().newFileSystem(uri, Collections.EMPTY_MAP)) { + try (FileSystem onedrivefs = new OneDriveFileSystemProvider().newFileSystem(uri, Collections.emptyMap())) { dstDir = onedrivefs.getPath("/").resolve("TEST_FUSE_5"); dst = dstDir.resolve("テ゚ト 001"); diff --git a/vavi-nio-file-onedrive4/src/test/java/vavi/nio/file/onedrive4/Main6.java b/vavi-nio-file-onedrive4/src/test/java/vavi/nio/file/onedrive4/Main6.java new file mode 100644 index 00000000..72e22434 --- /dev/null +++ b/vavi-nio-file-onedrive4/src/test/java/vavi/nio/file/onedrive4/Main6.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2020 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package vavi.nio.file.onedrive4; + +import java.io.IOException; +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.util.Collections; + +import org.junit.jupiter.api.Test; + +import static vavi.nio.file.Base.testDescription; + + +/** + * OneDrive. (v2.0 graph api, msgraph engine) + * + * @author Naohide Sano (umjammer) + * @version 0.00 2020/05/31 umjammer initial version
+ */ +public class Main6 { + + @Test + void test01() throws Exception { + String email = System.getenv("MICROSOFT4_TEST_ACCOUNT"); + + URI uri = URI.create("onedrive:///?id=" + email); + FileSystem fs = new OneDriveFileSystemProvider().newFileSystem(uri, Collections.emptyMap()); + + testDescription(fs); + } + + // + + public static void main(String[] args) throws IOException { + String email = System.getenv("MICROSOFT4_TEST_ACCOUNT"); + + URI uri = URI.create("onedrive:///?id=" + email); + FileSystem fs = new OneDriveFileSystemProvider().newFileSystem(uri, Collections.emptyMap()); + + Files.setAttribute(fs.getPath("tmp/Cyberduck.jpg"), "user:description", ("čĒŦ明テ゚ト " + System.currentTimeMillis()).getBytes()); + + fs.close(); + } +} diff --git a/vavi-nio-file-onedrive4/src/test/java/vavi/nio/file/onedrive4/TestGraph.java b/vavi-nio-file-onedrive4/src/test/java/vavi/nio/file/onedrive4/TestGraph.java index 42f1febb..3b4ad60c 100644 --- a/vavi-nio-file-onedrive4/src/test/java/vavi/nio/file/onedrive4/TestGraph.java +++ b/vavi-nio-file-onedrive4/src/test/java/vavi/nio/file/onedrive4/TestGraph.java @@ -9,9 +9,13 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.net.URL; import java.net.URLEncoder; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.Arrays; import com.microsoft.graph.authentication.IAuthenticationProvider; import com.microsoft.graph.concurrency.ChunkedUploadProvider; @@ -27,9 +31,12 @@ import com.microsoft.graph.models.extensions.DriveItemUploadableProperties; import com.microsoft.graph.models.extensions.IGraphServiceClient; import com.microsoft.graph.models.extensions.ItemReference; +import com.microsoft.graph.models.extensions.ThumbnailSet; import com.microsoft.graph.models.extensions.UploadSession; +import com.microsoft.graph.options.QueryOption; import com.microsoft.graph.requests.extensions.GraphServiceClient; import com.microsoft.graph.requests.extensions.IDriveItemCopyRequest; +import com.microsoft.graph.requests.extensions.IThumbnailSetCollectionPage; import vavi.net.auth.WithTotpUserCredential; import vavi.net.auth.oauth2.OAuth2AppCredential; @@ -40,6 +47,9 @@ import vavi.nio.file.onedrive4.graph.LraMonitorResponseHandler; import vavi.nio.file.onedrive4.graph.LraMonitorResult; import vavi.nio.file.onedrive4.graph.LraSession; +import vavi.nio.file.onedrive4.graph.ThumbnailUploadProvider; +import vavi.util.Debug; +import vavi.util.StringUtil; import vavi.util.properties.annotation.PropsEntity; @@ -51,27 +61,42 @@ */ public class TestGraph { + static { + System.setProperty("vavi.util.logging.VaviFormatter.extraClassMethod", "(" + + "sun\\.util\\.logging\\.\\w*Log\\w*#\\w+" + "|" + + "jdk\\.internal\\.event\\.EventHelper#log\\w+" + + ")"); + } + /** * @param args 0: email */ public static void main(String[] args) throws Exception { - String email = args[0]; +// String email = args[0]; + String email = "snaohide@hotmail.com"; TestGraph app = new TestGraph(); app.auth(email); - app.testCopy(); +// app.testCopy(); + app.testThumbnail(); + app.close(); } - /** */ + void close() { + auth2.close(); + } + + MicrosoftGraphOAuth2 auth2; + + /** @after {@link #client} */ void auth(String email) throws IOException { OAuth2AppCredential appCredential = new MicrosoftGraphLocalAppCredential(); PropsEntity.Util.bind(appCredential); - PropsEntity.Util.bind(this); - WithTotpUserCredential userCredential = new MicrosoftLocalUserCredential(email); - String accesssToken = new MicrosoftGraphOAuth2(appCredential, true).authorize(userCredential); + auth2 = new MicrosoftGraphOAuth2(appCredential, true); + String accesssToken = auth2.authorize(userCredential); client = GraphServiceClient.builder() .authenticationProvider(new IAuthenticationProvider() { @@ -167,6 +192,33 @@ public void failure(final ClientException ex) { } }); } + + /** */ + void testThumbnail() throws Exception { + Path p = Paths.get(TestGraph.class.getResource("/duke.jpg").toURI()); + byte[] b = Files.readAllBytes(p); + + DriveItem sourceEntry = client.drive().root().itemWithPath("tmp/test.zip").buildRequest().get(); + + ThumbnailUploadProvider provider = new ThumbnailUploadProvider(sourceEntry, client); + provider.upload(b); +Debug.println("upload done"); + + Thread.sleep(3000); + + IThumbnailSetCollectionPage page = client.drive().items(sourceEntry.id) + .thumbnails() + .buildRequest(Arrays.asList(new QueryOption("select", "source"))) + .get(); + ThumbnailSet set = page.getCurrentPage().get(0); +Debug.println("set: " + StringUtil.paramString(set)); +Debug.println("thumbnail url: " + set.source.url); + + Path dir = Paths.get("tmp"); + Path out = dir.resolve("thumbnail.jpg"); + + Files.copy(new URL(set.source.url).openStream(), out, StandardCopyOption.REPLACE_EXISTING); + } } /* */ diff --git a/vavi-nio-file-onedrive4/src/test/java/vavi/nio/file/onedrive4/WebHookApiTest.java b/vavi-nio-file-onedrive4/src/test/java/vavi/nio/file/onedrive4/WebHookApiTest.java new file mode 100644 index 00000000..9f90f5db --- /dev/null +++ b/vavi-nio-file-onedrive4/src/test/java/vavi/nio/file/onedrive4/WebHookApiTest.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2020 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package vavi.nio.file.onedrive4; + +import java.net.URI; +import java.util.Calendar; + +import com.microsoft.graph.authentication.IAuthenticationProvider; +import com.microsoft.graph.http.IHttpRequest; +import com.microsoft.graph.models.extensions.DriveItem; +import com.microsoft.graph.models.extensions.Folder; +import com.microsoft.graph.models.extensions.IGraphServiceClient; +import com.microsoft.graph.models.extensions.Subscription; +import com.microsoft.graph.requests.extensions.GraphServiceClient; +import com.microsoft.graph.requests.extensions.IDriveItemCollectionPage; +import com.microsoft.graph.requests.extensions.ISubscriptionCollectionPage; + +import vavi.net.auth.oauth2.microsoft.MicrosoftGraphLocalAppCredential; +import vavi.net.auth.oauth2.microsoft.MicrosoftGraphOAuth2; +import vavi.net.auth.web.microsoft.MicrosoftLocalUserCredential; +import vavi.nio.file.onedrive4.graph.MyLogger; +import vavi.util.Debug; + + +/** + * WebHookApiTest. + * + * @author Naohide Sano (umjammer) + * @version 0.00 2020/07/03 umjammer initial version
+ * @see "https://docs.microsoft.com/graph/api/subscription-post-subscriptions?view=graph-rest-1.0&tabs=http" + */ +public class WebHookApiTest { + + static final String VAVI_APPS_WEBHOOK_SECRET = System.getenv("VAVI_APPS_WEBHOOK_SECRET"); + static String websocketBaseUrl = System.getenv("VAVI_APPS_WEBHOOK_WEBSOCKET_BASE_URL"); + static String websocketPath = System.getenv("VAVI_APPS_WEBHOOK_WEBSOCKET_MICROSOFT_PATH"); + static String email = System.getenv("MICROSOFT4_TEST_ACCOUNT"); + + /** + * @param args + */ + public static void main(String[] args) throws Exception { + + MicrosoftLocalUserCredential userCredential = new MicrosoftLocalUserCredential(email); + MicrosoftGraphLocalAppCredential appCredential = new MicrosoftGraphLocalAppCredential(); + + @SuppressWarnings("resource") + String accessToken = new MicrosoftGraphOAuth2(appCredential, true).authorize(userCredential); +//Debug.println("accessToken: " + accessToken); + + IAuthenticationProvider authenticationProvider = new IAuthenticationProvider() { + @Override + public void authenticateRequest(IHttpRequest request) { + request.addHeader("Authorization", "Bearer " + accessToken); + } + }; + IGraphServiceClient graphClient = GraphServiceClient.builder() + .authenticationProvider(authenticationProvider) + .logger(new MyLogger()) + .buildClient(); + graphClient.getHttpProvider().getConnectionConfig().setReadTimeout(30 * 1000); + + // create + URI uri = URI.create(websocketBaseUrl + websocketPath); + // Listen for file upload events in the specified folder + DriveItem rootFolder = graphClient.drive().root().buildRequest().get(); + IDriveItemCollectionPage pages = graphClient.drive().items(rootFolder.id).children().buildRequest().get(); + for (DriveItem i : pages.getCurrentPage()) { + if (i.name.equals("TEST_WEBHOOK")) { +System.out.println("rmdir " + i.name); + graphClient.drive().items(i.id).buildRequest().delete(); + } + } +System.out.println("mkdir " + "TEST_WEBHOOK"); + DriveItem preEntry = new DriveItem(); + preEntry.name = "TEST_WEBHOOK"; + preEntry.folder = new Folder(); + DriveItem newFolder = graphClient.drive().items(rootFolder.id).children().buildRequest().post(preEntry); + +System.out.println("[create] webhook"); + Subscription preSubscription = new Subscription(); +// preSubscription.id = UUID.randomUUID().toString(); // doesn't work (spec.) +// preSubscription.changeType = "created,updated,deleted"; // root is only supported 'update' + preSubscription.changeType = "updated"; + preSubscription.notificationUrl = uri.toString(); + preSubscription.resource = "me/drive/root"; + Calendar calendar = Calendar.getInstance(); + calendar.roll(Calendar.DATE, true); + preSubscription.expirationDateTime = calendar; + preSubscription.clientState = VAVI_APPS_WEBHOOK_SECRET; +// Subscription subscription = graphClient.subscriptions().buildRequest().post(preSubscription); + Subscription subscription = graphClient.subscriptions().buildRequest().post(preSubscription); +Debug.println(subscription.id); + +System.out.println("[ls] webhook"); + ISubscriptionCollectionPage subscPages = graphClient.subscriptions().buildRequest().get(); + for (Subscription s : subscPages.getCurrentPage()) { +System.out.println(s); + } + +System.out.println("mkdir " + "TEST_WEBHOOK/" + "NEW FOLDER"); + preEntry = new DriveItem(); + preEntry.name = "NEW FOLDER"; + preEntry.folder = new Folder(); + DriveItem subFolder = graphClient.drive().items(newFolder.id).children().buildRequest().post(preEntry); + + Thread.sleep(5000); + + // update +System.out.println("[update] webhook"); + calendar.roll(Calendar.DATE, true); + subscription.expirationDateTime = calendar; + subscription = graphClient.subscriptions(subscription.id).buildRequest().patch(subscription); + + // delete +System.out.println("[delete] webhook"); + graphClient.subscriptions(subscription.id).buildRequest().delete(); + +System.out.println("rmdir " + subFolder.name); + graphClient.drive().items(subFolder.id).buildRequest().delete(); +System.out.println("rmdir " + newFolder.name); + graphClient.drive().items(newFolder.id).buildRequest().delete(); + } +} + +/* */ diff --git a/vavi-nio-file-onedrive4/src/test/resources/duke.jpg b/vavi-nio-file-onedrive4/src/test/resources/duke.jpg new file mode 100644 index 00000000..3402f12e Binary files /dev/null and b/vavi-nio-file-onedrive4/src/test/resources/duke.jpg differ diff --git a/vavi-nio-file-onedrive4/src/test/resources/logging.properties b/vavi-nio-file-onedrive4/src/test/resources/logging.properties index c2e69d4c..d613ede0 100644 --- a/vavi-nio-file-onedrive4/src/test/resources/logging.properties +++ b/vavi-nio-file-onedrive4/src/test/resources/logging.properties @@ -11,7 +11,13 @@ handlers=java.util.logging.ConsoleHandler # HttpUrlConnection java.util.logging.ConsoleHandler.level=INFO #sun.net.www.protocol.http.HttpURLConnection.level=OFF -vavi.nio.file.onedrive4.graph.MyLogger.level=OFF +vavi.nio.file.onedrive4.graph.MyLogger.level=ALL +sun.util.logging.level=WARNING +com.sun.jersey.level=WARNING +sun.security.level=WARNING +vavi.util.properties.level=WARNING +vavi.beans.level=WARNING +sun.net.level=WARNING java.util.logging.ConsoleHandler.formatter=vavi.util.logging.VaviFormatter #java.util.logging.ConsoleHandler.formatter=vavi.util.logging.BetterFormatter diff --git a/vavi-nio-file-onedrive4/src/test/resources/onedrive.properties b/vavi-nio-file-onedrive4/src/test/resources/onedrive.properties index 298c42c4..3f300320 100644 --- a/vavi-nio-file-onedrive4/src/test/resources/onedrive.properties +++ b/vavi-nio-file-onedrive4/src/test/resources/onedrive.properties @@ -1 +1 @@ -authenticatorClassName=vavi.net.auth.oauth2.microsoft.MicrosoftLocalAuthenticator +authenticatorClassName=vavi.net.auth.oauth2.microsoft.MicrosoftBasicAuthenticator diff --git a/vavi-nio-file-sandbox/pom.xml b/vavi-nio-file-sandbox/pom.xml index a056db1c..50a2d3e3 100644 --- a/vavi-nio-file-sandbox/pom.xml +++ b/vavi-nio-file-sandbox/pom.xml @@ -7,7 +7,7 @@ vavi vavi-apps-fuse - 0.1.6 + 0.1.7 vavi-nio-file-sandbox @@ -21,19 +21,50 @@ https://github.com/umjammer/vavi-nio-file-sandbox/issues + + + + maven-dependency-plugin + 2.10 + + + copy + generate-resources + + copy + + + + + vavi + vavi-image-ffmpeg + 1.0.9 + dylib + false + ${project.build.testOutputDirectory} + libFfmpegResampleOpWrapper.dylib + + + + + + + + + - central - bintray - https://jcenter.bintray.com + jitpack.io + https://jitpack.io + + + github + GitHub umjammer Apache Maven Packages + https://maven.pkg.github.com/umjammer/* umjammer-mvn-repo https://raw.github.com/umjammer/umjammer/mvn-repo/ - - true - always - seasar @@ -51,7 +82,32 @@ com.github.umjammer vavi-commons - 1.1.0 + 1.1.6 + + + + org.jsoup + jsoup + 1.14.3 + test + + + org.jdom + jdom + 2.0.2 + test + + + jaxen + jaxen + 1.2.0 + test + + + net.sf.saxon + Saxon-HE + 11.2 + test @@ -70,29 +126,42 @@ test + + com.rainerhahnekamp + sneakythrow + test + + vavi vavi-nio-file-onedrive - 0.1.6 + 0.1.7 test vavi vavi-nio-file-onedrive4 - 0.1.6 + 0.1.7 test vavi vavi-nio-file-googledrive - 0.1.6 + 0.1.7 test com.github.umjammer vavi-util-archive - 1.0.6 + 1.1.0 + test + + + + com.github.umjammer + vavi-image + 1.0.9 test @@ -113,22 +182,34 @@ com.github.umjammer klab-commons-csv - 2.0.2 + 2.0.3 test - com.github.Querz - NBT - 4.0 + org.cryptomator + fuse-nio-adapter + 1.2.3 test - org.cryptomator - fuse-nio-adapter + com.github.umjammer + vavi-util-screenscraping + 1.0.9 + test + + + ch.qos.logback + logback-classic 1.2.3 test + + ${vavi-nio-file.groupId} + vavi-nio-file + test-jar + test + diff --git a/vavi-nio-file-sandbox/readme.md b/vavi-nio-file-sandbox/readme.md new file mode 100644 index 00000000..aeec751b --- /dev/null +++ b/vavi-nio-file-sandbox/readme.md @@ -0,0 +1,9 @@ +# vavi-nio-file-sandbox + +sandbox + +## samples + + * google drive thumbnail using `user:attribute` for *googledrive* file system + + ![](https://lh3.googleusercontent.com/pw/AM-JKLUGXSshFc0wm7QRsYZ6tvvEV94Fv7nutU-fcNV8naSbTiSXawGAQfPwIlDtTSuDYpV7pYi_GXwMLJ2rEr0FjvgBD0fGD00R-yGczAnik8fXYTM8rFAf1apvFfP14KHZ1peruG8_RV_--jH4xJbOTNOR=w1024-h724-no?authuser=0) \ No newline at end of file diff --git a/vavi-nio-file-sandbox/src/test/java/Classification.java b/vavi-nio-file-sandbox/src/test/java/Classification.java index d831e1e0..c199c06e 100644 --- a/vavi-nio-file-sandbox/src/test/java/Classification.java +++ b/vavi-nio-file-sandbox/src/test/java/Classification.java @@ -14,112 +14,129 @@ import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.stream.Collectors; - -import vavi.net.auth.oauth2.OAuth2AppCredential; -import vavi.net.auth.oauth2.microsoft.MicrosoftLocalAppCredential; -import vavi.nio.file.onedrive.OneDriveFileSystemProvider; -import vavi.util.properties.annotation.PropsEntity; import static java.nio.file.FileVisitResult.CONTINUE; /** * onedrive classification - * + *

+ * if an author has three more novels, create author folder and move those into there. + *

* @author Naohide Sano (umjammer) * @version 0.00 2017/03/14 umjammer initial version
*/ public final class Classification { + static { + System.setProperty("vavi.util.logging.VaviFormatter.extraClassMethod", + "com\\.microsoft\\.graph\\.logger\\.DefaultLogger#logDebug"); + } + /** * @param args 0: email, 1: dir */ - public static void main(final String... args) throws IOException { - String email = args[0]; - String cwd = args[1]; - boolean dryRun = true; + public static void main(String[] args) throws Exception { + Classification app = new Classification(args[0]); + app.exec(args[1]); + } - URI uri = URI.create("onedrive:///?id=" + email); + boolean dryRun = true; - OAuth2AppCredential appCredential = new MicrosoftLocalAppCredential(); - PropsEntity.Util.bind(appCredential); - - Map env = new HashMap<>(); - env.put(OneDriveFileSystemProvider.ENV_APP_CREDENTIAL, appCredential); - - FileSystem onedrivefs = FileSystems.newFileSystem(uri, env); - - Path root = onedrivefs.getPath(cwd); - FileSearcher fileSearcher = new FileSearcher(); - Files.walkFileTree(root, fileSearcher); - Pattern pattern = Pattern.compile("\\[(.+?)\\]"); - Map authors = fileSearcher.result().stream() - .map(path -> pattern.matcher(path.getFileName().toString())) - .filter(matcher -> matcher.find()) - .collect(Collectors.groupingBy(matcher -> matcher.group(1), Collectors.counting())); - authors.entrySet().stream() - .filter(e -> e.getValue() >= 3) - .forEach(e -> { - fileSearcher.result().stream() - .filter(path -> path.getFileName().toString().indexOf("[" + e.getKey() + "]") > 0) - .forEach(path -> { - try { - Path dir = path.getParent().resolve(e.getKey()); - if (!Files.exists(dir)) { - System.err.println("mkdir " + dir); - if (!dryRun) { - Files.createDirectory(dir); - } - } - - System.err.println("mv " + path + " " + dir); - if (!dryRun) { - Files.move(path, dir.resolve(path.getFileName())); - } - } catch (IOException f) { - throw new IllegalStateException(f); - } - }); - }); + FileSystem fs; - System.exit(0); - } + Classification(String email) throws IOException { - static class FileSearcher extends SimpleFileVisitor { + URI uri = URI.create("onedrive:///?id=" + email); - private List list = new ArrayList<>(); + fs = FileSystems.newFileSystem(uri, Collections.emptyMap()); + } + + void exec(String cwd) throws IOException { + Path root = fs.getPath(cwd); + Files.walkFileTree(root, new MyFileVisitor1()); +System.err.println("\ndone counting"); + exec2(); +System.err.println("done"); + } - Pattern pattern = Pattern.compile("[あかさたãĒはぞやらわ]"); + class MyFileVisitor1 extends SimpleFileVisitor { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attr) { if (attr.isRegularFile()) { - if (pattern.matcher(file.getParent().getFileName().toString()).matches()) { - System.err.println(file); - list.add(file); +System.err.print("."); + if (filter1(file)) { + func1(file); + targets.add(file); } } return CONTINUE; } + } - @Override - public FileVisitResult postVisitDirectory(Path dir, IOException exc) { - return CONTINUE; - } + static final Pattern pattern1 = Pattern.compile("[あかさたãĒはぞやらわ]"); - @Override - public FileVisitResult visitFileFailed(Path file, IOException exc) { - System.err.println(exc); - return CONTINUE; + // novels parent is one of ka sa ta na ... only + boolean filter1(Path file) { + return pattern1.matcher(file.getParent().getFileName().toString()).matches(); + } + + static final Pattern pattern2 = Pattern.compile("\\[(.+?)\\]"); + + Map counter = new HashMap<>(); + + List targets = new ArrayList<>(); + + /** counter */ + void func1(Path file) { + Matcher matcher = pattern2.matcher(file.getFileName().toString()); + if (matcher.find()) { + String author = matcher.group(1); + if (counter.get(author) == null) { + counter.put(author, 1); + } else { + counter.put(author, counter.get(author) + 1); + } } + } - List result() { - return list; + void exec2() { + targets.stream() + .forEach(file -> { + Matcher matcher = pattern2.matcher(file.getFileName().toString()); + if (matcher.find()) { + func2(file, matcher.group(1)); + } + }); + } + + /** */ + void func2(Path file, String author) { + try { + if (counter.get(author) >= 3) { +System.err.println("author " + author); + Path dir = file.getParent().resolve(author); + if (!Files.exists(dir)) { +System.err.println("mkdir " + dir); + if (!dryRun) { + Files.createDirectory(dir); + } + } + +System.err.println("mv " + file + " " + dir); + if (!dryRun) { + Files.move(file, dir.resolve(file.getFileName())); + } + } + } catch (IOException e) { + e.printStackTrace(); } } } diff --git a/vavi-nio-file-sandbox/src/test/java/Classification2.java b/vavi-nio-file-sandbox/src/test/java/Classification2.java index 0444c41b..8cf9b3d2 100644 --- a/vavi-nio-file-sandbox/src/test/java/Classification2.java +++ b/vavi-nio-file-sandbox/src/test/java/Classification2.java @@ -50,12 +50,12 @@ public final class Classification2 { /** * @param args 0: dir */ - public static void main(final String... args) throws IOException { + public static void main(String[] args) throws IOException { String cwd = args[0]; boolean dryRun = true; Path root = Paths.get(cwd); - FileSearcher fileSearcher = new FileSearcher(root); + MyFileVisitor fileSearcher = new MyFileVisitor(root); Files.walkFileTree(root, fileSearcher); Pattern pattern = Pattern.compile("\\[(.+?)\\]"); fileSearcher.result().forEach(path -> { @@ -124,7 +124,7 @@ private static String toKana(String text) throws IOException{ return sb.length() == 0 ? text : sb.toString(); } - static class FileSearcher extends SimpleFileVisitor { + static class MyFileVisitor extends SimpleFileVisitor { private List list = new ArrayList<>(); @@ -133,7 +133,7 @@ static class FileSearcher extends SimpleFileVisitor { Pattern patternF = Pattern.compile("\\[(.+?)\\]"); Pattern patternD = Pattern.compile("[あかさたãĒはぞやらわ]"); - FileSearcher(Path root) { + MyFileVisitor(Path root) { this.root = root; } @@ -161,12 +161,6 @@ public FileVisitResult postVisitDirectory(Path dir, IOException exc) { return CONTINUE; } - @Override - public FileVisitResult visitFileFailed(Path file, IOException exc) { - System.err.println(exc); - return CONTINUE; - } - List result() { return list; } diff --git a/vavi-nio-file-sandbox/src/test/java/Classification3.java b/vavi-nio-file-sandbox/src/test/java/Classification3.java index 84d1b055..a5e82a46 100644 --- a/vavi-nio-file-sandbox/src/test/java/Classification3.java +++ b/vavi-nio-file-sandbox/src/test/java/Classification3.java @@ -14,17 +14,12 @@ import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; -import java.util.HashMap; +import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; -import vavi.net.auth.oauth2.OAuth2AppCredential; -import vavi.net.auth.oauth2.microsoft.MicrosoftLocalAppCredential; import vavi.nio.file.Util; -import vavi.nio.file.onedrive.OneDriveFileSystemProvider; -import vavi.util.properties.annotation.PropsEntity; import static java.nio.file.FileVisitResult.CONTINUE; @@ -47,16 +42,10 @@ public static void main(final String... args) throws IOException { URI uri = URI.create("onedrive:///?id=" + email); - OAuth2AppCredential appCredential = new MicrosoftLocalAppCredential(); - PropsEntity.Util.bind(appCredential); - - Map env = new HashMap<>(); - env.put(OneDriveFileSystemProvider.ENV_APP_CREDENTIAL, appCredential); - - FileSystem onedrivefs = FileSystems.newFileSystem(uri, env); + FileSystem onedrivefs = FileSystems.newFileSystem(uri, Collections.emptyMap()); Path root = onedrivefs.getPath(cwd); - FileSearcher fileSearcher = new FileSearcher(); + MyFileVisitor fileSearcher = new MyFileVisitor(); Files.walkFileTree(root, fileSearcher); Pattern pattern = Pattern.compile("\\[(.+?)\\]"); fileSearcher.result().stream() @@ -82,11 +71,9 @@ public static void main(final String... args) throws IOException { System.err.println(f); } }); - - System.exit(0); } - static class FileSearcher extends SimpleFileVisitor { + static class MyFileVisitor extends SimpleFileVisitor { private List list = new ArrayList<>(); @@ -103,17 +90,6 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attr) { return CONTINUE; } - @Override - public FileVisitResult postVisitDirectory(Path dir, IOException exc) { - return CONTINUE; - } - - @Override - public FileVisitResult visitFileFailed(Path file, IOException exc) { - System.err.println(exc); - return CONTINUE; - } - public List result() { return list; } diff --git a/vavi-nio-file-sandbox/src/test/java/CloserFinder.java b/vavi-nio-file-sandbox/src/test/java/CloserFinder.java index d37b7e7b..c4d34540 100644 --- a/vavi-nio-file-sandbox/src/test/java/CloserFinder.java +++ b/vavi-nio-file-sandbox/src/test/java/CloserFinder.java @@ -14,16 +14,11 @@ import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; -import java.util.HashMap; +import java.util.Collections; import java.util.List; -import java.util.Map; -import vavi.net.auth.oauth2.OAuth2AppCredential; -import vavi.net.auth.oauth2.microsoft.MicrosoftLocalAppCredential; import vavi.nio.file.Util; -import vavi.nio.file.onedrive.OneDriveFileSystemProvider; import vavi.util.LevenshteinDistance; -import vavi.util.properties.annotation.PropsEntity; import static java.nio.file.FileVisitResult.CONTINUE; @@ -39,22 +34,16 @@ public final class CloserFinder { /** * @param args 0: email, 1: dir */ - public static void main(final String... args) throws IOException { + public static void main(String[] args) throws IOException { String email = args[0]; String cwd = args[1]; URI uri = URI.create("onedrive:///?id=" + email); - OAuth2AppCredential appCredential = new MicrosoftLocalAppCredential(); - PropsEntity.Util.bind(appCredential); - - Map env = new HashMap<>(); - env.put(OneDriveFileSystemProvider.ENV_APP_CREDENTIAL, appCredential); - - FileSystem onedrivefs = FileSystems.newFileSystem(uri, env); + FileSystem onedrivefs = FileSystems.newFileSystem(uri, Collections.emptyMap()); Path root = onedrivefs.getPath(cwd); - FileSearcher fileSearcher = new FileSearcher(); + MyFileVisitor fileSearcher = new MyFileVisitor(); Files.walkFileTree(root, fileSearcher); fileSearcher.result().parallelStream() .forEach(path1 -> { @@ -74,11 +63,9 @@ public static void main(final String... args) throws IOException { } }); }); - - System.exit(0); } - static class FileSearcher extends SimpleFileVisitor { + static class MyFileVisitor extends SimpleFileVisitor { private List list = new ArrayList<>(); @@ -90,17 +77,6 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attr) { return CONTINUE; } - @Override - public FileVisitResult postVisitDirectory(Path dir, IOException exc) { - return CONTINUE; - } - - @Override - public FileVisitResult visitFileFailed(Path file, IOException exc) { - System.err.println(exc); - return CONTINUE; - } - public List result() { return list; } diff --git a/vavi-nio-file-sandbox/src/test/java/Del.java b/vavi-nio-file-sandbox/src/test/java/Del.java new file mode 100644 index 00000000..1840e68b --- /dev/null +++ b/vavi-nio-file-sandbox/src/test/java/Del.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2020 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + + +/** + * Del. + * + * @author Naohide Sano (umjammer) + * @version 0.00 2020/07/01 umjammer initial version
+ */ +public class Del { + + public static void main(String[] args) throws Exception { + + Path trash = Paths.get("./Trash"); +// Files.list(trash).forEach(p -> Sneaky.sneaked(Files::delete)); + Files.list(trash).forEach(System.out::println); + } +} \ No newline at end of file diff --git a/vavi-nio-file-sandbox/src/test/java/Descriptor.java b/vavi-nio-file-sandbox/src/test/java/Descriptor.java new file mode 100644 index 00000000..721ac474 --- /dev/null +++ b/vavi-nio-file-sandbox/src/test/java/Descriptor.java @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2020 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +import java.io.IOException; +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.Collections; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import vavix.util.screenscrape.annotation.JsonPathParser; +import vavix.util.screenscrape.annotation.PlainInputHandler; +import vavix.util.screenscrape.annotation.Target; +import vavix.util.screenscrape.annotation.WebScraper; + +import static java.nio.file.FileVisitResult.CONTINUE; + + +/** + * cloud drive description adder + * + * @author Naohide Sano (umjammer) + * @version 0.00 2020/09/08 umjammer initial version
+ * @see "https://manablog.org/google-books-apis/" + */ +public final class Descriptor { + + /** + * @param args 0: email, 1: dir + */ + public static void main(String[] args) throws IOException { + String email = args[0]; + String cwd = args[1]; + + URI uri = URI.create("onedrive:///?id=" + email); + + try (FileSystem fs = FileSystems.newFileSystem(uri, Collections.emptyMap())) { + + Path root = fs.getPath(cwd); + MyFileVisitor fileSearcher = new MyFileVisitor(); + Files.walkFileTree(root, fileSearcher); + +// String query = args[2]; +// Result result = query(query); +//System.err.println(result); + + } +//Thread.getAllStackTraces().keySet().forEach(System.err::println); + } + + @WebScraper(url = "https://www.googleapis.com/books/v1/volumes?q={0}", + parser = JsonPathParser.class, + input = PlainInputHandler.class, + value = "$..items", + isDebug = false, + isCollection = false) + public static class Result { + @Target(value = "$.volumeInfo.title") + String title; + @Target(value = "$.volumeInfo.authors", optional = true) + List authors; + @Target(value = "$.volumeInfo.publishedDate", optional = true) + String publishedDate; + @Target(value = "$.volumeInfo.description", optional = true) + String description; + @Target(value = "$.volumeInfo.industryIdentifiers[0].identifier", optional = true) + String isbn10; + @Target(value = "$.volumeInfo.industryIdentifiers[1].identifier", optional = true) + String isbn13; + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Result [title="); + builder.append(title); + builder.append(", authors="); + builder.append(authors); + builder.append(", publishedDate="); + builder.append(publishedDate); + builder.append(", description="); + builder.append(description); + builder.append(", isbn10="); + builder.append(isbn10); + builder.append(", isbn13="); + builder.append(isbn13); + builder.append("]"); + return builder.toString(); + } + public String toFormatedString() { + StringBuilder builder = new StringBuilder(); + builder.append(authors == null ? "[]" : authors); + builder.append(" "); + builder.append(title); + builder.append("\n"); + builder.append(publishedDate == null ? "" : publishedDate); + builder.append("\n"); + if (isbn10 != null) { + builder.append(isbn10); + builder.append("\n"); + } + if (isbn13 != null) { + builder.append(isbn13); + builder.append("\n"); + } + builder.append("\n"); + builder.append(description == null ? "" : description); + return builder.toString(); + } + } + + static Result query(String query) throws IOException { + return WebScraper.Util.scrape(Result.class, query).get(0); + } + + static class MyFileVisitor extends SimpleFileVisitor { + + static final Pattern pattern = Pattern.compile("^\\(一čˆŦ小čĒŦ\\)\\s\\[(.+?)\\]\\s(.+?)(\\(.+?\\)){0,1}\\..+$"); + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attr) { + if (attr.isRegularFile()) { + String filename = file.getFileName().toString(); +//System.err.println(filename); + Matcher matcher = pattern.matcher(filename); + if (matcher.find()) { +//System.out.println(matcher.group(1) + " - " + matcher.group(2)); + try { + byte[] bytes = (byte[]) Files.getAttribute(file, "user:description"); + if (bytes == null || bytes.length == 0) { + + Result result = query(matcher.group(1) + " " + matcher.group(2)); + String description = result.toFormatedString(); +System.out.println(description + "\n\n"); + Files.setAttribute(file, "user:description", description.getBytes()); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + } + return CONTINUE; + } + } +} diff --git a/vavi-nio-file-sandbox/src/test/java/EpubManipulator.java b/vavi-nio-file-sandbox/src/test/java/EpubManipulator.java new file mode 100644 index 00000000..4f7b4ac4 --- /dev/null +++ b/vavi-nio-file-sandbox/src/test/java/EpubManipulator.java @@ -0,0 +1,233 @@ +/* + * Copyright (c) 2022 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +import java.io.IOException; +import java.io.OutputStream; +import java.net.URI; +import java.net.URL; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.nio.file.StandardOpenOption; +import java.util.Collections; +import java.util.List; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathFactory; + +import org.jdom2.JDOMException; +import org.jdom2.filter.Filters; +import org.jdom2.input.SAXBuilder; +import org.jdom2.output.XMLOutputter; +import org.jsoup.Jsoup; +import org.jsoup.select.Elements; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.xml.sax.InputSource; + +import vavi.util.Debug; +import vavi.xml.util.XPathDebugger; + +import net.sf.saxon.dom.DOMNodeList; +import net.sf.saxon.dom.NodeOverNodeInfo; +import net.sf.saxon.om.NodeInfo; + + +/** + * EpubManipulator. + * + * @author Naohide Sano (umjammer) + * @version 0.00 2022/02/28 umjammer initial version
+ */ +public class EpubManipulator { + + /** + * @param args dir + */ + public static void main(String[] args) throws Exception { + EpubManipulator app = new EpubManipulator(); + app.exec(); + } + + void exec() throws Exception { +// Path src = Paths.get(Test1.class.getResource("/test.zip").toURI()); + + Path dir = Paths.get("tmp"); +// Files.createDirectories(dir); + + Path target = dir.resolve("(一čˆŦ小čĒŦ) [éĻŗ星周] マãƒŗゴãƒŧãƒģãƒŦイãƒŗ (č§’åˇæ–‡åēĢ).epub"); +// Files.copy(src, target, StandardCopyOption.REPLACE_EXISTING); + URI uri = URI.create("jar:" + target.toUri()); +Debug.println("uri: " + uri); + + FileSystem fs = FileSystems.newFileSystem(uri, Collections.emptyMap()); +Debug.println("fs: " + fs.getClass().getName()); + +// jsoup(fs); +// jdom2(fs); + saxon(fs); + + fs.close(); + } + + /** + * @see "https://www.ipentec.com/document/internet-get-amazon-product-image" + */ + static String amazon(String asin) { + int countryCode = 9; + String imageType = "LZZZZZZZ"; + return String.format("http://images-jp.amazon.com/images/P/%s.%02d.%s.jpg", asin, countryCode, imageType); + } + + /** + * TODO jsoup doesn't handle attribute namespace + * @see "https://github.com/jhy/jsoup/pull/1682" + */ + static void jsoup(FileSystem fs) throws IOException { + + Path conatiner = fs.getPath("META-INF/container.xml"); +Debug.println(conatiner + ": " + Files.exists(conatiner)); + + org.jsoup.nodes.Document document = Jsoup.parse(Files.newInputStream(conatiner), "utf8", conatiner.toUri().toString()); +Debug.println("conatiner: " + document); + Elements elements = document.select("container > rootfiles > rootfile[full-path]"); + org.jsoup.nodes.Element element = elements.first(); + String fullPath = element.attr("full-path"); +Debug.println("full-path: " + fullPath); + + Path content = fs.getPath(fullPath); + document = Jsoup.parse(Files.newInputStream(content), "utf8", conatiner.toUri().toString()); +Debug.println("content: " + document); + + elements = document.select("dc|identifier[opfU00003Ascheme]"); +Debug.println("elements: " + elements); + element = elements.first(); + String asin = element.ownText(); +Debug.println("asin: " + asin); + String url = amazon(asin); +Debug.println("url: " + url); + + Path out = fs.getPath("vavi", asin + ".jpg"); + Files.copy(new URL(url).openStream(), out, StandardCopyOption.REPLACE_EXISTING); +Debug.println("out: " + Files.size(out)); + } + + /** */ + static void jdom2(FileSystem fs) throws IOException, JDOMException { + + Path conatiner = fs.getPath("META-INF/container.xml"); +Debug.println(conatiner + ": " + Files.exists(conatiner)); + + SAXBuilder builder = new SAXBuilder(); + org.jdom2.xpath.XPathFactory factory = org.jdom2.xpath.XPathFactory.instance(); + +XPathDebugger.getEntryList(new InputSource(Files.newInputStream(conatiner))).forEach(System.err::println); + + org.jdom2.Document document = builder.build(Files.newInputStream(conatiner)); +Debug.println("conatiner: " + new XMLOutputter().outputString(document)); + + org.jdom2.xpath.XPathExpression expression = + factory.compile("//*[local-name() = 'rootfile']", Filters.element()); + org.jdom2.Element element = expression.evaluateFirst(document); +Debug.println("element: " + element); + String fullPath = element.getAttributeValue("full-path"); +Debug.println("full-path: " + fullPath); + + Path content = fs.getPath(fullPath); + document = builder.build(Files.newInputStream(content)); +Debug.println("content: " + document); + + expression = factory.compile("//*[local-name() = 'identifier'][@*[local-name() = 'scheme']]", Filters.element()); + element = expression.evaluateFirst(document); +Debug.println("element: " + element); + String asin = element.getText(); +Debug.println("asin: " + asin); + String url = amazon(asin); +Debug.println("url: " + url); + + Path out = fs.getPath("vavi", asin + ".jpg"); + Files.copy(new URL(url).openStream(), out, StandardCopyOption.REPLACE_EXISTING); +Debug.println("out: " + Files.size(out)); + } + + /** */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + static void saxon(FileSystem fs) throws Exception { + + Path conatiner = fs.getPath("META-INF/container.xml"); +Debug.println(conatiner + ": " + Files.exists(conatiner)); + + System.setProperty(XPathFactory.DEFAULT_PROPERTY_NAME + ":" + XPathFactory.DEFAULT_OBJECT_MODEL_URI, + "net.sf.saxon.xpath.XPathFactoryImpl"); + XPath xpath = XPathFactory.newInstance().newXPath(); + + InputSource is = new InputSource(Files.newInputStream(conatiner)); +//XPathDebugger.getEntryList(is).forEach(System.err::println); + + List elements = (List) xpath.evaluate("//*[local-name() = 'rootfile']", is, XPathConstants.NODESET); + Element element = (Element) NodeOverNodeInfo.wrap(elements.get(0)); +Debug.println("element: " + element.getTagName()); + String fullPath = element.getAttribute("full-path"); +Debug.println("full-path: " + fullPath); + + Path content = fs.getPath(fullPath); + is = new InputSource(Files.newInputStream(content)); +//Debug.println("content: " + content); + +// System.setProperty("javax.xml.parsers.DocumentBuilderFactory", "net.sf.saxon.dom.DocumentBuilderFactoryImpl"); + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + DocumentBuilder db = dbf.newDocumentBuilder(); + Document document = db.parse(new InputSource(Files.newInputStream(content))); + + DOMNodeList elements2 = (DOMNodeList) xpath.evaluate("//*[local-name() = 'identifier'][@*[local-name() = 'scheme']]", document, XPathConstants.NODESET); + element = (Element) elements2.item(0); +Debug.println("element: " + element.getTagName()); + String asin = element.getTextContent(); +Debug.println("asin: " + asin); + String url = amazon(asin); +Debug.println("url: " + url); + + Path outDir = fs.getPath("vavi"); + if (!Files.exists(outDir)) { + Files.createDirectory(outDir); + } + Path out = outDir.resolve(asin + ".jpg"); + Files.copy(new URL(url).openStream(), out, StandardCopyOption.REPLACE_EXISTING); +Debug.println("out: " + Files.size(out)); + + elements2 = (DOMNodeList) xpath.evaluate("//*[local-name() = 'manifest']/*[local-name() = 'item'][@*[local-name() = 'id']='cover']", document, XPathConstants.NODESET); + element = (Element) elements2.item(0); +Debug.println("href:B: " + element.getAttribute("href")); + element.setAttribute("href", out.toString()); +Debug.println("href:A: " + element.getAttribute("href")); + + TransformerFactory tf = TransformerFactory.newInstance(); + Transformer transformer = tf.newTransformer(); + + DOMSource source = new DOMSource(document); + OutputStream os = Files.newOutputStream(content, StandardOpenOption.TRUNCATE_EXISTING); + StreamResult result = new StreamResult(os); + transformer.transform(source, result); + os.flush(); // needed + os.close(); // needed +StreamResult result2 = new StreamResult(System.out); +transformer.transform(source, result2); + +Debug.println("content: " + Files.size(content)); + } +} + +/* */ diff --git a/vavi-nio-file-sandbox/src/test/java/GoogleDriveThumbnail.java b/vavi-nio-file-sandbox/src/test/java/GoogleDriveThumbnail.java new file mode 100644 index 00000000..a105706c --- /dev/null +++ b/vavi-nio-file-sandbox/src/test/java/GoogleDriveThumbnail.java @@ -0,0 +1,313 @@ +/* + * Copyright (c) 2022 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +import java.awt.image.BufferedImage; +import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URL; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.logging.Level; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +import javax.imageio.ImageIO; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathFactory; + +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; + +import vavi.awt.image.resample.FfmpegResampleOp; +import vavi.util.Debug; + +import static java.nio.file.FileVisitResult.CONTINUE; + + +/** + * GoogleDriveThumbnail. + * + * @author Naohide Sano (umjammer) + * @version 0.00 2022/01/26 umjammer initial version
+ */ +public class GoogleDriveThumbnail { + + static byte[] duke; + + static String string; + static String asin; + + /** + * @param args dir + */ + public static void main(String[] args) throws Exception { + + String email = System.getenv("GOOGLE_TEST_ACCOUNT"); + + URI uri = URI.create("googledrive:///?id=" + email); + FileSystem fs = FileSystems.newFileSystem(uri, Collections.emptyMap()); + +// String start = args[0]; + String start = "/Books/Novels/あさぎあつこ"; + string = "09"; + asin = "B00M98X8SM"; + +// Files.createDirectories(Paths.get("tmp")); + duke = Files.readAllBytes(Paths.get(GoogleDriveThumbnail.class.getResource("/duke.jpg").toURI())); + + Path dir = fs.getPath(start); + Files.walkFileTree(dir, new MyFileVisitor()); + + fs.close(); + } + + static class MyFileVisitor extends SimpleFileVisitor { + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attr) { + if (attr.isRegularFile()) { + try { + if (filter1(file)) { + func4(file); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + return CONTINUE; + } + } + + // filters + + static final String ext = ".epub"; + static final Pattern pattern = Pattern.compile("^\\(一čˆŦ小čĒŦ\\)\\s\\[(.+?)\\]\\s(.+?)\\" + ext + "$"); + + /** comic zip */ + static boolean filter1(Path file) { + String filename = file.getFileName().toString(); +//System.err.println(filename); + Matcher matcher = pattern.matcher(filename); + if (matcher.find() && filename.contains(string)) { +System.out.println(matcher.group(1) + " - " + matcher.group(2)); + return true; + } else { + return false; + } + } + + // functions + + /** get thumbnail and save it to local */ + static void func1(Path file) throws IOException { + byte[] bytes = (byte[]) Files.getAttribute(file, "user:thumbnail"); + if (bytes != null && bytes.length != 0) { + String name = file.getFileName().toString().replace(".zip", ".jpg"); +System.err.println("output: " + name); + Path path = Paths.get("tmp", name); + Files.write(path, bytes); + } else { +System.err.println("skip: " + file); + } + } + + + static final boolean DRY_RUN = false; + static final boolean OVERWRITE = true; + +// static final Pattern jpg = Pattern.compile("^.+?\\.jpg$"); + static final Pattern jpg = Pattern.compile("^[\\w+\\/]+\\.jpe?g$"); + + /** extract self and set first jpg as a thumbnail */ + static void func2(Path file) throws IOException { + // check existence + byte[] bytes = (byte[]) Files.getAttribute(file, "user:thumbnail"); + if (!OVERWRITE && bytes != null && bytes.length != 0) { +System.err.println("skip: " + file); + return; + } + + // exec + ZipInputStream zis = new ZipInputStream(new BufferedInputStream(Files.newInputStream(file))); + ZipEntry entry; + List names = new ArrayList<>(); + while ((entry = zis.getNextEntry()) != null) { + Matcher m = jpg.matcher(entry.getName()); + if (m.matches()) { + names.add(entry.getName()); + } + } + + // determine cover + Collections.sort(names, (a, b) -> { + if (a.contains("cover") && !b.contains("cover")) { + return -1; + } else if (!a.contains("cover") && b.contains("cover")) { + return 1; + } else { + return a.compareTo(b); + } + }); + if (names.size() == 0) { +zis = new ZipInputStream(new BufferedInputStream(Files.newInputStream(file))); +while ((entry = zis.getNextEntry()) != null) { + System.err.println(entry.getName()); +} +Debug.print(Level.WARNING, "no images in: " + file); + return; + } + String name = names.get(0); + + // extract image + zis = new ZipInputStream(new BufferedInputStream(Files.newInputStream(file))); + + BufferedImage image = null; + while ((entry = zis.getNextEntry()) != null) { + if (name.equals(entry.getName())) { + image = ImageIO.read(zis); + break; + } + } + + // resize image + double sx = 600d / image.getWidth(); + BufferedImage thumbnail = new FfmpegResampleOp(sx, sx).filter(image, null); +System.err.println(entry.getName() + ": " + thumbnail.getWidth() + "x" + thumbnail.getHeight()); + + // set image + if (DRY_RUN) { + Path path = Paths.get("tmp", file.getFileName().toString().replace(ext, ".jpg")); + ImageIO.write(thumbnail, "JPG", Files.newOutputStream(path)); + } else { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ImageIO.write(thumbnail, "JPG", baos); + bytes = baos.toByteArray(); + Files.setAttribute(file, "user:thumbnail", bytes); + } + } + + /** set amazon thumbnail asin from self meta data */ + static void func3(Path file) throws Exception { + // check existence + byte[] bytes = (byte[]) Files.getAttribute(file, "user:thumbnail"); + if (!OVERWRITE && bytes != null && bytes.length != 0) { +System.err.println("skip: " + file); + return; + } + + // exec + Path gd = Paths.get("/Volumes/GoogleDrive/My Drive", file.toString()); + URI uri = URI.create("jar:" + gd.toUri().toString()); +Debug.println("uri: " + uri); + + FileSystem fs = FileSystems.newFileSystem(uri, Collections.emptyMap()); +Debug.println("fs: " + fs.getClass().getName()); + + + Path conatiner = fs.getPath("META-INF/container.xml"); +Debug.println(conatiner + ": " + Files.exists(conatiner)); + + XPath xpath = XPathFactory.newInstance().newXPath(); + + InputSource is = new InputSource(Files.newInputStream(conatiner)); + + NodeList elements = (NodeList) xpath.evaluate("//*[local-name() = 'rootfile']", is, XPathConstants.NODESET); + Element element = (Element) elements.item(0); +Debug.println("element: " + element.getTagName()); + String fullPath = element.getAttribute("full-path"); +Debug.println("full-path: " + fullPath); + + Path content = fs.getPath(fullPath); + is = new InputSource(Files.newInputStream(content)); + + is = new InputSource(Files.newInputStream(content)); + + elements = (NodeList) xpath.evaluate("//*[local-name() = 'identifier'][@*[local-name() = 'scheme']]", is, XPathConstants.NODESET); + element = (Element) elements.item(0); +Debug.println("element: " + element.getTagName()); + String asin = element.getTextContent(); +Debug.println("asin: " + asin); + String url = EpubManipulator.amazon(asin); +Debug.println("url: " + url); + + + InputStream in = new BufferedInputStream(new URL(url).openStream()); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + byte[] buffer = new byte[8024]; + int l = 0; + while ((l = in.read(buffer)) != -1) { + baos.write(buffer, 0, l); + } + bytes = baos.toByteArray(); +Debug.println("image: " + bytes.length); + if (bytes.length < 100) { + throw new IllegalStateException("no image in amazon? for: " + asin); + } + + + if (DRY_RUN) { + Path path = Paths.get("tmp", file.getFileName().toString().replace(ext, ".jpg")); + Files.write(path, bytes); + } else { + Files.setAttribute(file, "user:thumbnail", bytes); + } + } + + /** set amazon thumbnail specify asin directly */ + static void func4(Path file) throws Exception { + // check existence + byte[] bytes = (byte[]) Files.getAttribute(file, "user:thumbnail"); + if (!OVERWRITE && bytes != null && bytes.length != 0) { +System.err.println("skip: " + file); + return; + } + + // exec +Debug.println("asin: " + asin); + String url = EpubManipulator.amazon(asin); +Debug.println("url: " + url); + + + InputStream in = new BufferedInputStream(new URL(url).openStream()); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + byte[] buffer = new byte[8024]; + int l = 0; + while ((l = in.read(buffer)) != -1) { + baos.write(buffer, 0, l); + } + bytes = baos.toByteArray(); +Debug.println("image: " + bytes.length); + if (bytes.length < 100) { + throw new IllegalStateException("no image in amazon? for: " + asin); + } + + + if (DRY_RUN) { + Path path = Paths.get("tmp", file.getFileName().toString().replace(ext, ".jpg")); + Files.write(path, bytes); + } else { + Files.setAttribute(file, "user:thumbnail", bytes); + } + } +} + +/* */ diff --git a/vavi-nio-file-sandbox/src/test/java/GoogleOCR.java b/vavi-nio-file-sandbox/src/test/java/GoogleOCR.java index c845ff25..db7af70b 100644 --- a/vavi-nio-file-sandbox/src/test/java/GoogleOCR.java +++ b/vavi-nio-file-sandbox/src/test/java/GoogleOCR.java @@ -35,7 +35,7 @@ public final class GoogleOCR { /** * @param args 0: email, 1: zip file, 2: extract to dir, 3: extracted dir, 4: output ocr dir */ - public static void main(final String... args) { + public static void main(String[] args) { int exitCode = 0; try { GoogleOCR app = new GoogleOCR(); @@ -61,7 +61,7 @@ FileSystem prepare(final String... args) throws IOException { URI uri = URI.create("googledrive:///?id=" + email); - return FileSystems.newFileSystem(uri, Collections.EMPTY_MAP); + return FileSystems.newFileSystem(uri, Collections.emptyMap()); } /** diff --git a/vavi-nio-file-sandbox/src/test/java/Main8.java b/vavi-nio-file-sandbox/src/test/java/Main8.java new file mode 100644 index 00000000..a67bfbe4 --- /dev/null +++ b/vavi-nio-file-sandbox/src/test/java/Main8.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2020 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +import java.nio.file.FileSystems; +import java.nio.file.Paths; +import java.nio.file.WatchEvent; +import java.nio.file.WatchEvent.Kind; +import java.nio.file.WatchKey; +import java.nio.file.WatchService; +import java.nio.file.Watchable; + +import vavi.util.Debug; + +import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE; +import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE; +import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY; + + +/** + * WatchService. + * + * @author Naohide Sano (umjammer) + * @version 0.00 2020/07/01 umjammer initial version
+ */ +public class Main8 { + + public static void main(String[] args) throws Exception { + WatchService watcher = FileSystems.getDefault().newWatchService(); + + Watchable path = Paths.get("./tmp"); + path.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY); + + while (true) { + WatchKey watchKey = watcher.take(); + + for (WatchEvent event : watchKey.pollEvents()) { + Kind kind = event.kind(); + Object context = event.context(); +Debug.println("kind: " + kind + ", context: " + context); + } + + if (!watchKey.reset()) { + System.out.println("WatchKey has reset"); + return; + } + } + } +} \ No newline at end of file diff --git a/vavi-nio-file-sandbox/src/test/java/MavenRepoCleaner.java b/vavi-nio-file-sandbox/src/test/java/MavenRepoCleaner.java new file mode 100644 index 00000000..3cdc6f71 --- /dev/null +++ b/vavi-nio-file-sandbox/src/test/java/MavenRepoCleaner.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2021 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +import java.io.IOException; +import java.nio.file.DirectoryStream; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.util.stream.StreamSupport; + +import vavi.util.Debug; + +import static com.rainerhahnekamp.sneakythrow.Sneaky.sneaked; +import static java.nio.file.FileVisitResult.CONTINUE; + + +/** + * maven repo cleaner. + *

+ * fu*king eclipse content assist leaves half way named junk files and folders + * in the repository. + *

+ * + * @author Naohide Sano (umjammer) + * @version 0.00 2021/11/12 umjammer initial version
+ */ +public class MavenRepoCleaner { + + /** + * @param args + */ + public static void main(String[] args) throws IOException { + Files.walkFileTree(Paths.get(System.getProperty("user.home"), ".m2/repository"), new MyFileVisitor()); +Debug.println("Done"); + } + + static class MyFileVisitor extends SimpleFileVisitor { + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) { + try { + long c1 = Files.list(dir).count(); + + DirectoryStream iterable = Files.newDirectoryStream(dir, "*.lastUpdated"); + long c2 = StreamSupport.stream(iterable.spliterator(), false).count(); + iterable.close(); + + iterable = Files.newDirectoryStream(dir, ".DS_Store"); + long c3 = StreamSupport.stream(iterable.spliterator(), false).count(); + iterable.close(); + + iterable = Files.newDirectoryStream(dir, "resolver-status.properties"); + long c4 = StreamSupport.stream(iterable.spliterator(), false).count(); + iterable.close(); + + if (c1 == 0) { + // no files + System.out.println("DIR0: " + dir); + + Files.delete(dir); + } else if (c1 == c2) { + // only eclipse files + System.out.println("DIR1: " + dir); + + Files.list(dir).forEach(sneaked(Files::delete)); + Files.delete(dir); + } else if (c1 == c2 + c4) { + // only eclipse files 2 + System.out.println("DIR2: " + dir); + + Files.list(dir).forEach(sneaked(Files::delete)); + Files.delete(dir); + } else if (c1 == c3) { + // only mac files + System.out.println("DIRX " + dir); + + Files.list(dir).forEach(sneaked(Files::delete)); + Files.delete(dir); + } + } catch (IOException e) { + System.err.println("ERROR: " + dir + ", " + e.getMessage()); + } + return CONTINUE; + } + } +} \ No newline at end of file diff --git a/vavi-nio-file-sandbox/src/test/java/Renamer.java b/vavi-nio-file-sandbox/src/test/java/Renamer.java index e27e7427..a829a2f7 100644 --- a/vavi-nio-file-sandbox/src/test/java/Renamer.java +++ b/vavi-nio-file-sandbox/src/test/java/Renamer.java @@ -29,7 +29,7 @@ public final class Renamer { /** * @param args 0: dir, 1: regex, 2: replacement */ - public static void main(final String... args) throws IOException { + public static void main(String[] args) throws IOException { String cwd = args[0]; String regex = args[1]; String replacement = args[2]; diff --git a/vavi-nio-file-sandbox/src/test/java/ShowEmptyFolder.java b/vavi-nio-file-sandbox/src/test/java/ShowEmptyFolder.java new file mode 100644 index 00000000..05769b87 --- /dev/null +++ b/vavi-nio-file-sandbox/src/test/java/ShowEmptyFolder.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2021 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + + + +import java.io.IOException; +import java.net.URI; +import java.nio.file.DirectoryStream; +import java.nio.file.FileSystem; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.Collections; +import java.util.stream.StreamSupport; + +import vavi.nio.file.googledrive.GoogleDriveFileSystemProvider; +import vavi.util.Debug; + +import static com.rainerhahnekamp.sneakythrow.Sneaky.sneaked; +import static java.nio.file.FileVisitResult.CONTINUE; + + +/** + * apply something to filtered folders + * + * @author Naohide Sano (umjammer) + * @version 0.00 2021/10/30 umjammer initial version
+ */ +public class ShowEmptyFolder { + + public static void main(String[] args) throws IOException { + ShowEmptyFolder app = new ShowEmptyFolder(); + app.exec(args); + } + + void exec(String[] args) throws IOException { + String email = System.getenv("GOOGLE_TEST_ACCOUNT"); + + URI uri = URI.create("googledrive:///?id=" + email); + FileSystem fs = new GoogleDriveFileSystemProvider().newFileSystem(uri, Collections.emptyMap()); + + Files.walkFileTree(fs.getPath("/Music/midi"), new MyFileVisitor()); + + fs.close(); +Debug.println("Done"); + } + + class MyFileVisitor extends SimpleFileVisitor { + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attr) { + return CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) { + try { + if (filter2(dir)) { + func2(dir); + } + } catch (IOException e) { + System.err.println("ERROR: " + dir + ", " + e.getMessage()); + } + return CONTINUE; + } + + @Override + public FileVisitResult visitFileFailed(Path file, IOException exc) { + System.err.println(exc); + return CONTINUE; + } + } + + boolean filter1(Path path) throws IOException { + return Files.list(path).count() == 0; + } + + boolean filter2(Path path) throws IOException { + long c1 = Files.list(path).count(); + + DirectoryStream iterable = Files.newDirectoryStream(path, ".DS_Store"); + long c3 = StreamSupport.stream(iterable.spliterator(), false).count(); + iterable.close(); + + if (c1 == 0) { + System.out.println("DIR0: " + path); + return true; + } else if (c1 == c3) { + System.out.println("DIR1X " + path); + return true; + } else { + return false; + } + } + + void func1(Path path) throws IOException { + System.out.println("DIR0: " + path); + } + + void func2(Path path) throws IOException { + Files.list(path).forEach(sneaked(Files::delete)); + Files.delete(path); + } +} \ No newline at end of file diff --git a/vavi-nio-file-sandbox/src/test/java/Synchronizer.java b/vavi-nio-file-sandbox/src/test/java/Synchronizer.java index 4125a2cf..39d9a9a5 100644 --- a/vavi-nio-file-sandbox/src/test/java/Synchronizer.java +++ b/vavi-nio-file-sandbox/src/test/java/Synchronizer.java @@ -15,15 +15,10 @@ import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; -import java.util.HashMap; +import java.util.Collections; import java.util.List; -import java.util.Map; -import vavi.net.auth.oauth2.OAuth2AppCredential; -import vavi.net.auth.oauth2.microsoft.MicrosoftLocalAppCredential; import vavi.nio.file.Util; -import vavi.nio.file.onedrive.OneDriveFileSystemProvider; -import vavi.util.properties.annotation.PropsEntity; import static java.nio.file.FileVisitResult.CONTINUE; @@ -39,7 +34,7 @@ public final class Synchronizer { /** * @param args 0: dir, 1: dir1, 2: dir2 */ - public static void main(final String... args) throws IOException { + public static void main(String[] args) throws IOException { String email = args[0]; String cwd1 = args[1]; String cwd2 = args[2]; @@ -47,22 +42,16 @@ public static void main(final String... args) throws IOException { URI uri = URI.create("onedrive:///?id=" + email); - OAuth2AppCredential appCredential = new MicrosoftLocalAppCredential(); - PropsEntity.Util.bind(appCredential); - - Map env = new HashMap<>(); - env.put(OneDriveFileSystemProvider.ENV_APP_CREDENTIAL, appCredential); - - FileSystem onedrivefs = FileSystems.newFileSystem(uri, env); + FileSystem onedrivefs = FileSystems.newFileSystem(uri, Collections.emptyMap()); // onedrive Path root1 = onedrivefs.getPath(cwd1); - FileSearcher fileSearcher1 = new FileSearcher(); + MyFileVisitor fileSearcher1 = new MyFileVisitor(); Files.walkFileTree(root1, fileSearcher1); // local Path root2 = Paths.get(cwd2); - FileSearcher fileSearcher2 = new FileSearcher(); + MyFileVisitor fileSearcher2 = new MyFileVisitor(); Files.walkFileTree(root2, fileSearcher2); fileSearcher2.result().parallelStream() @@ -83,11 +72,9 @@ public static void main(final String... args) throws IOException { throw new IllegalStateException(e); } }); - - System.exit(0); } - static class FileSearcher extends SimpleFileVisitor { + static class MyFileVisitor extends SimpleFileVisitor { private List list = new ArrayList<>(); diff --git a/vavi-nio-file-sandbox/src/test/java/Test1.java b/vavi-nio-file-sandbox/src/test/java/Test1.java index 44798ae5..5f485b0c 100644 --- a/vavi-nio-file-sandbox/src/test/java/Test1.java +++ b/vavi-nio-file-sandbox/src/test/java/Test1.java @@ -4,15 +4,36 @@ * Programmed by Naohide Sano */ +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.FileSystemNotFoundException; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.UserDefinedFileAttributeView; +import java.util.Collections; +import java.util.List; + +import org.jdom2.filter.Filters; +import org.jdom2.input.SAXBuilder; +import org.jdom2.xpath.XPathExpression; +import org.jdom2.xpath.XPathFactory; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable; + +import vavi.util.Debug; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; /** - * Test. + * Test1. * * @author Naohide Sano (umjammer) * @version 0.00 2016/02/27 umjammer initial version
@@ -34,7 +55,7 @@ public void test() { public void test2() { SecurityManager security = System.getSecurityManager(); if (security == null) { - System.err.println("no security manager"); +Debug.println("no security manager"); return; } try { @@ -46,7 +67,7 @@ public void test2() { @Test void test01() { - System.out.printf("file: %1$s, %2$tF %2$tT.%2$tL, %3$d\n", "a", System.currentTimeMillis(), 1); +Debug.printf("file: %1$s, %2$tF %2$tT.%2$tL, %3$d\n", "a", System.currentTimeMillis(), 1); } @Test @@ -55,6 +76,76 @@ void test04() { long o = Long.parseLong(m.substring(9, m.length() - 1)); assertEquals(123456L, o); } + + @Test + void test05() throws Exception { + Path path = Paths.get("/"); + boolean r = Files.getFileStore(path).supportsFileAttributeView(UserDefinedFileAttributeView.class); +Debug.println("support user attr view: " + r); + } + + @Test + @DisabledIfEnvironmentVariable(named = "GITHUB_WORKFLOW", matches = ".*") + void test06() throws Exception { + Path path = Paths.get("/Volumes/GoogleDrive/Books/Comics"); + UserDefinedFileAttributeView userDefinedFAView = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class); + if (userDefinedFAView != null) { + List attributes = userDefinedFAView.list(); +attributes.forEach(Debug::println); + } + } + + @SuppressWarnings("restriction") + @Test + void test07() throws Exception { + Path src = Paths.get(Test1.class.getResource("/test.zip").toURI()); + Path dir = Paths.get("tmp"); + Files.createDirectories(dir); + Path target = dir.resolve(src.getFileName()); + Files.copy(src, target, StandardCopyOption.REPLACE_EXISTING); + URI uri = URI.create("jar:" + target.toUri()); +Debug.println("uri: " + uri); + assertThrows(FileSystemNotFoundException.class, () -> { + // first time uri is not instance of ZipPath + // cause not found + // @see "https://stackoverflow.com/a/25033217" + FileSystems.getFileSystem(uri); + }); + + FileSystem fs = FileSystems.newFileSystem(uri, Collections.emptyMap()); +Debug.println("fs: " + fs.getClass().getName()); + assertInstanceOf(com.sun.nio.zipfs.ZipFileSystem.class, fs); + + // why second time pass test? + fs = FileSystems.getFileSystem(uri); +Debug.println("fs: " + fs.getClass().getName()); + assertInstanceOf(com.sun.nio.zipfs.ZipFileSystem.class, fs); + } + + @Test + void test08() throws Exception { + SAXBuilder builder = new SAXBuilder(); + XPathFactory factory = XPathFactory.instance(); + Path p = Paths.get(Test1.class.getResource("/log4j2.xml").toURI()); + org.jdom2.Document document = builder.build(Files.newInputStream(p)); +Debug.println("conatiner: " + document); + + XPathExpression expression = + factory.compile("/Configuration", Filters.element()); + org.jdom2.Element element = expression.evaluateFirst(document); +Debug.println("element: " + element); + assertEquals("Configuration", element.getName()); + + expression = factory.compile("/Configuration/Loggers/Root/AppenderRef", Filters.element()); + element = expression.evaluateFirst(document); +Debug.println("element: " + element); + assertEquals("AppenderRef", element.getName()); + + expression = factory.compile("*", Filters.element()); + element = expression.evaluateFirst(document); +Debug.println("element: " + element); + assertEquals("Configuration", element.getName()); + } } /* */ diff --git a/vavi-nio-file-sandbox/src/test/java/Test5.java b/vavi-nio-file-sandbox/src/test/java/Test5.java index f40238f1..3bba4c79 100644 --- a/vavi-nio-file-sandbox/src/test/java/Test5.java +++ b/vavi-nio-file-sandbox/src/test/java/Test5.java @@ -23,9 +23,8 @@ */ public class Test5 { - // TODO doesn't work @Test - @Disabled + @Disabled("doesn't work") public void test01() throws Exception { // String email = System.getenv("TEST5_GOOGLE_ACCOUNT"); String mp = System.getenv("TEST5_GOOGLE_MOUNT_POINT"); diff --git a/vavi-nio-file-sandbox/src/test/java/TestAozora.java b/vavi-nio-file-sandbox/src/test/java/TestAozora.java index 2d625cb4..921ea63e 100644 --- a/vavi-nio-file-sandbox/src/test/java/TestAozora.java +++ b/vavi-nio-file-sandbox/src/test/java/TestAozora.java @@ -41,7 +41,7 @@ public static void main(String[] args) throws Exception { //result.forEach(System.err::println); Path root = Paths.get(cwd); - FileSearcher fileSearcher = new FileSearcher(); + MyFileVisitor fileSearcher = new MyFileVisitor(); Files.walkFileTree(root, fileSearcher); fileSearcher.result().stream().filter(path -> { final String name = path.getFileName().toString(); @@ -75,7 +75,7 @@ public String getAuthor() { } } - static class FileSearcher extends SimpleFileVisitor { + static class MyFileVisitor extends SimpleFileVisitor { private List list = new ArrayList<>(); diff --git a/vavi-nio-file-sandbox/src/test/java/TestNbt.java b/vavi-nio-file-sandbox/src/test/java/TestNbt.java deleted file mode 100644 index c777636e..00000000 --- a/vavi-nio-file-sandbox/src/test/java/TestNbt.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) 2019 by Naohide Sano, All rights reserved. - * - * Programmed by Naohide Sano - */ - -import java.io.IOException; - -import com.google.gson.GsonBuilder; -import com.google.gson.JsonParser; - -import net.querz.nbt.NBTUtil; -import net.querz.nbt.Tag; - - -/** - * TestNbt. - * - * @author Naohide Sano (umjammer) - * @version 0.00 2019/07/08 umjammer initial version
- */ -public class TestNbt { - - /** - * @param args - */ - public static void main(String[] args) throws IOException { - Tag tag = NBTUtil.readTag(args[0]); - System.out.println(new GsonBuilder().setPrettyPrinting().create().toJson(new JsonParser().parse(tag.toString()))); - } -} - -/* */ diff --git a/vavi-nio-file-sandbox/src/test/java/vavi/nio/file/watch/WatchServiceTest.java b/vavi-nio-file-sandbox/src/test/java/vavi/nio/file/watch/WatchServiceTest.java new file mode 100644 index 00000000..39db84d9 --- /dev/null +++ b/vavi-nio-file-sandbox/src/test/java/vavi/nio/file/watch/WatchServiceTest.java @@ -0,0 +1,546 @@ +package vavi.nio.file.watch; +/* + * Copyright (c) 2008, 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* @test + * @bug 4313887 6838333 7017446 8011537 8042470 + * @summary Unit test for java.nio.file.WatchService + * @library .. + * @run main Basic + */ + + + +import java.io.IOException; +import java.io.OutputStream; +import java.net.URI; +import java.nio.file.ClosedWatchServiceException; +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.nio.file.StandardWatchEventKinds; +import java.nio.file.WatchEvent; +import java.nio.file.WatchKey; +import java.nio.file.WatchService; +import java.util.Collections; +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import vavi.nio.file.googledrive.GoogleDriveFileSystemProvider; + +import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE; +import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE; +import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY; +import static java.nio.file.StandardWatchEventKinds.OVERFLOW; +import static vavi.nio.file.Base.removeTree; + + +/** + * Unit test for WatchService that exercises all methods in various scenarios. + */ +@Disabled +public class WatchServiceTest { + + void checkKey(WatchKey key, Path dir) { + if (!key.isValid()) + throw new RuntimeException("Key is not valid"); + if (key.watchable() != dir) + throw new RuntimeException("Unexpected watchable"); + } + + void takeExpectedKey(WatchService watcher, WatchKey expected) { + System.out.println("take events..."); + WatchKey key; + try { + key = watcher.take(); + } catch (InterruptedException x) { + // not expected + throw new RuntimeException(x); + } + if (key != expected) + throw new RuntimeException("removed unexpected key"); + } + + void checkExpectedEvent(Iterable> events, + WatchEvent.Kind expectedKind, + Object expectedContext) + { + WatchEvent event = events.iterator().next(); + System.out.format("got event: type=%s, count=%d, context=%s\n", + event.kind(), event.count(), event.context()); + if (event.kind() != expectedKind) + throw new RuntimeException("unexpected event"); + if (!expectedContext.equals(event.context())) + throw new RuntimeException("unexpected context"); + } + + static FileSystem fs; + + /** + * Simple test of each of the standard events + */ + @Test + void testEvents() throws IOException { + System.out.println("-- Standard Events --"); + + Path name = fs.getPath("foo"); + + try (WatchService watcher = fs.newWatchService()) { + // --- ENTRY_CREATE --- + + // register for event + System.out.format("register %s for ENTRY_CREATE\n", dir); + WatchKey myKey = dir.register(watcher, + new WatchEvent.Kind[]{ ENTRY_CREATE }); + checkKey(myKey, dir); + + // create file + Path file = dir.resolve("foo"); + System.out.format("create %s\n", file); + Files.createFile(file); + + // remove key and check that we got the ENTRY_CREATE event + takeExpectedKey(watcher, myKey); + checkExpectedEvent(myKey.pollEvents(), + StandardWatchEventKinds.ENTRY_CREATE, name); + + System.out.println("reset key"); + if (!myKey.reset()) + throw new RuntimeException("key has been cancalled"); + + System.out.println("OKAY"); + + // --- ENTRY_DELETE --- + + System.out.format("register %s for ENTRY_DELETE\n", dir); + WatchKey deleteKey = dir.register(watcher, + new WatchEvent.Kind[]{ ENTRY_DELETE }); + if (deleteKey != myKey) + throw new RuntimeException("register did not return existing key"); + checkKey(deleteKey, dir); + + System.out.format("delete %s\n", file); + Files.delete(file); + takeExpectedKey(watcher, myKey); + checkExpectedEvent(myKey.pollEvents(), + StandardWatchEventKinds.ENTRY_DELETE, name); + + System.out.println("reset key"); + if (!myKey.reset()) + throw new RuntimeException("key has been cancalled"); + + System.out.println("OKAY"); + + // create the file for the next test + Files.createFile(file); + + // --- ENTRY_MODIFY --- + + System.out.format("register %s for ENTRY_MODIFY\n", dir); + WatchKey newKey = dir.register(watcher, + new WatchEvent.Kind[]{ ENTRY_MODIFY }); + if (newKey != myKey) + throw new RuntimeException("register did not return existing key"); + checkKey(newKey, dir); + + System.out.format("update: %s\n", file); + try (OutputStream out = Files.newOutputStream(file, StandardOpenOption.APPEND)) { + out.write("I am a small file".getBytes("UTF-8")); + } + + // remove key and check that we got the ENTRY_MODIFY event + takeExpectedKey(watcher, myKey); + checkExpectedEvent(myKey.pollEvents(), + StandardWatchEventKinds.ENTRY_MODIFY, name); + System.out.println("OKAY"); + + // done + Files.delete(file); + } + } + + /** + * Check that a cancelled key will never be queued + */ + @Test + void testCancel() throws IOException { + System.out.println("-- Cancel --"); + + try (WatchService watcher = fs.newWatchService()) { + + System.out.format("register %s for events\n", dir); + WatchKey myKey = dir.register(watcher, + new WatchEvent.Kind[]{ ENTRY_CREATE }); + checkKey(myKey, dir); + + System.out.println("cancel key"); + myKey.cancel(); + + // create a file in the directory + Path file = dir.resolve("mars"); + System.out.format("create: %s\n", file); + Files.createFile(file); + + // poll for keys - there will be none + System.out.println("poll..."); + try { + WatchKey key = watcher.poll(3000, TimeUnit.MILLISECONDS); + if (key != null) + throw new RuntimeException("key should not be queued"); + } catch (InterruptedException x) { + throw new RuntimeException(x); + } + + // done + Files.delete(file); + + System.out.println("OKAY"); + } + } + + /** + * Check that deleting a registered directory causes the key to be + * cancelled and queued. + */ + @Test + void testAutomaticCancel() throws IOException { + System.out.println("-- Automatic Cancel --"); + + Path subdir = Files.createDirectory(dir.resolve("bar")); + + try (WatchService watcher = fs.newWatchService()) { + + System.out.format("register %s for events\n", subdir); + WatchKey myKey = subdir.register(watcher, + new WatchEvent.Kind[]{ ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY }); + + System.out.format("delete: %s\n", subdir); + Files.delete(subdir); + takeExpectedKey(watcher, myKey); + + System.out.println("reset key"); + if (myKey.reset()) + throw new RuntimeException("Key was not cancelled"); + if (myKey.isValid()) + throw new RuntimeException("Key is still valid"); + + System.out.println("OKAY"); + + } + } + + /** + * Asynchronous close of watcher causes blocked threads to wakeup + */ + @Test + void testWakeup() throws IOException { + System.out.println("-- Wakeup Tests --"); + final WatchService watcher = fs.newWatchService(); + Runnable r = new Runnable() { + public void run() { + try { + Thread.sleep(5000); + System.out.println("close WatchService..."); + watcher.close(); + } catch (InterruptedException x) { + x.printStackTrace(); + } catch (IOException x) { + x.printStackTrace(); + } + } + }; + + // start thread to close watch service after delay + new Thread(r).start(); + + try { + System.out.println("take..."); + watcher.take(); + throw new RuntimeException("ClosedWatchServiceException not thrown"); + } catch (InterruptedException x) { + throw new RuntimeException(x); + } catch (ClosedWatchServiceException x) { + System.out.println("ClosedWatchServiceException thrown"); + } + + System.out.println("OKAY"); + } + + /** + * Simple test to check exceptions and other cases + */ + @Test + void testExceptions() throws IOException { + System.out.println("-- Exceptions and other simple tests --"); + + WatchService watcher = fs.newWatchService(); + try { + + // Poll tests + + WatchKey key; + System.out.println("poll..."); + key = watcher.poll(); + if (key != null) + throw new RuntimeException("no keys registered"); + + System.out.println("poll with timeout..."); + try { + long start = System.nanoTime(); + key = watcher.poll(3000, TimeUnit.MILLISECONDS); + if (key != null) + throw new RuntimeException("no keys registered"); + long waited = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start); + if (waited < 2900) + throw new RuntimeException("poll was too short"); + } catch (InterruptedException x) { + throw new RuntimeException(x); + } + + // IllegalArgumentException + System.out.println("IllegalArgumentException tests..."); + try { + dir.register(watcher /*empty event list*/); + throw new RuntimeException("IllegalArgumentException not thrown"); + } catch (IllegalArgumentException x) { + } + try { + // OVERFLOW is ignored so this is equivalent to the empty set + dir.register(watcher, OVERFLOW); + throw new RuntimeException("IllegalArgumentException not thrown"); + } catch (IllegalArgumentException x) { + } + try { + // OVERFLOW is ignored even if specified multiple times + dir.register(watcher, OVERFLOW, OVERFLOW); + throw new RuntimeException("IllegalArgumentException not thrown"); + } catch (IllegalArgumentException x) { + } + + // UnsupportedOperationException + try { + dir.register(watcher, + new WatchEvent.Kind() { + @Override public String name() { return "custom"; } + @Override public Class type() { return Object.class; } + }); + throw new RuntimeException("UnsupportedOperationException not thrown"); + } catch (UnsupportedOperationException x) { + } + try { + dir.register(watcher, + new WatchEvent.Kind[]{ ENTRY_CREATE }, + new WatchEvent.Modifier() { + @Override public String name() { return "custom"; } + }); + throw new RuntimeException("UnsupportedOperationException not thrown"); + } catch (UnsupportedOperationException x) { + } + + // NullPointerException + System.out.println("NullPointerException tests..."); + try { + dir.register(null, ENTRY_CREATE); + throw new RuntimeException("NullPointerException not thrown"); + } catch (NullPointerException x) { + } + try { + dir.register(watcher, new WatchEvent.Kind[]{ null }); + throw new RuntimeException("NullPointerException not thrown"); + } catch (NullPointerException x) { + } + try { + dir.register(watcher, new WatchEvent.Kind[]{ ENTRY_CREATE }, + (WatchEvent.Modifier)null); + throw new RuntimeException("NullPointerException not thrown"); + } catch (NullPointerException x) { + } + } finally { + watcher.close(); + } + + // -- ClosedWatchServiceException -- + + System.out.println("ClosedWatchServiceException tests..."); + + try { + watcher.poll(); + throw new RuntimeException("ClosedWatchServiceException not thrown"); + } catch (ClosedWatchServiceException x) { + } + + // assume that poll throws exception immediately + long start = System.nanoTime(); + try { + watcher.poll(10000, TimeUnit.MILLISECONDS); + throw new RuntimeException("ClosedWatchServiceException not thrown"); + } catch (InterruptedException x) { + throw new RuntimeException(x); + } catch (ClosedWatchServiceException x) { + long waited = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start); + if (waited > 5000) + throw new RuntimeException("poll was too long"); + } + + try { + watcher.take(); + throw new RuntimeException("ClosedWatchServiceException not thrown"); + } catch (InterruptedException x) { + throw new RuntimeException(x); + } catch (ClosedWatchServiceException x) { + } + + try { + dir.register(watcher, new WatchEvent.Kind[]{ ENTRY_CREATE }); + throw new RuntimeException("ClosedWatchServiceException not thrown"); + } catch (ClosedWatchServiceException x) { + } + + System.out.println("OKAY"); + } + + /** + * Test that directory can be registered with more than one watch service + * and that events don't interfere with each other + */ + @Test + void testTwoWatchers() throws IOException { + System.out.println("-- Two watchers test --"); + + WatchService watcher1 = fs.newWatchService(); + WatchService watcher2 = fs.newWatchService(); + try { + Path name1 = fs.getPath("gus1"); + Path name2 = fs.getPath("gus2"); + + // create gus1 + Path file1 = dir.resolve(name1); + System.out.format("create %s\n", file1); + Files.createFile(file1); + + // register with both watch services (different events) + System.out.println("register for different events"); + WatchKey key1 = dir.register(watcher1, + new WatchEvent.Kind[]{ ENTRY_CREATE }); + WatchKey key2 = dir.register(watcher2, + new WatchEvent.Kind[]{ ENTRY_DELETE }); + + if (key1 == key2) + throw new RuntimeException("keys should be different"); + + // create gus2 + Path file2 = dir.resolve(name2); + System.out.format("create %s\n", file2); + Files.createFile(file2); + + // check that key1 got ENTRY_CREATE + takeExpectedKey(watcher1, key1); + checkExpectedEvent(key1.pollEvents(), + StandardWatchEventKinds.ENTRY_CREATE, name2); + + // check that key2 got zero events + WatchKey key = watcher2.poll(); + if (key != null) + throw new RuntimeException("key not expected"); + + // delete gus1 + Files.delete(file1); + + // check that key2 got ENTRY_DELETE + takeExpectedKey(watcher2, key2); + checkExpectedEvent(key2.pollEvents(), + StandardWatchEventKinds.ENTRY_DELETE, name1); + + // check that key1 got zero events + key = watcher1.poll(); + if (key != null) + throw new RuntimeException("key not expected"); + + // reset for next test + key1.reset(); + key2.reset(); + + // change registration with watcher2 so that they are both + // registered for the same event + System.out.println("register for same event"); + key2 = dir.register(watcher2, new WatchEvent.Kind[]{ ENTRY_CREATE }); + + // create file and key2 should be queued + System.out.format("create %s\n", file1); + Files.createFile(file1); + takeExpectedKey(watcher2, key2); + checkExpectedEvent(key2.pollEvents(), + StandardWatchEventKinds.ENTRY_CREATE, name1); + + System.out.println("OKAY"); + + } finally { + watcher2.close(); + watcher1.close(); + } + } + + /** + * Test that thread interruped status is preserved upon a call + * to register() + */ + @Test + void testThreadInterrupt() throws IOException { + System.out.println("-- Thread interrupted status test --"); + + Thread curr = Thread.currentThread(); + try (WatchService watcher = fs.newWatchService()) { + System.out.println("interrupting current thread"); + curr.interrupt(); + dir.register(watcher, ENTRY_CREATE); + if (!curr.isInterrupted()) + throw new RuntimeException("thread should remain interrupted"); + System.out.println("current thread is still interrupted"); + System.out.println("OKAY"); + } finally { + Thread.interrupted(); + } + } + + static Path dir; + + @BeforeAll + static void before() throws IOException { + String email = System.getenv("GOOGLE_TEST_ACCOUNT"); + + URI uri = URI.create("googledrive:///?id=" + email); + fs = new GoogleDriveFileSystemProvider().newFileSystem(uri, Collections.EMPTY_MAP); + + dir = Files.createTempDirectory(fs.getRootDirectories().iterator().next(), "VAVIFUSE-TEST-WATCHSERVICE"); + } + + @AfterAll + static void after() throws Exception { + removeTree(dir); + } +} diff --git a/vavi-nio-file-sandbox/src/test/resources/duke.jpg b/vavi-nio-file-sandbox/src/test/resources/duke.jpg new file mode 100644 index 00000000..3402f12e Binary files /dev/null and b/vavi-nio-file-sandbox/src/test/resources/duke.jpg differ diff --git a/vavi-nio-file-sandbox/src/test/resources/log4j.properties b/vavi-nio-file-sandbox/src/test/resources/log4j.properties index b46df5a7..44b4ae61 100644 --- a/vavi-nio-file-sandbox/src/test/resources/log4j.properties +++ b/vavi-nio-file-sandbox/src/test/resources/log4j.properties @@ -1,9 +1,3 @@ -# -# Copyright (c) 2004 by Naohide Sano, All rights reserved. -# -# Written by Naohide Sano -# - # # ALL, DEBUG, INFO, WARN, ERROR, FATAL and OFF # diff --git a/vavi-nio-file-sandbox/src/test/resources/log4j2.xml b/vavi-nio-file-sandbox/src/test/resources/log4j2.xml index daa4f7f6..22f2742d 100644 --- a/vavi-nio-file-sandbox/src/test/resources/log4j2.xml +++ b/vavi-nio-file-sandbox/src/test/resources/log4j2.xml @@ -7,7 +7,7 @@ - + diff --git a/vavi-nio-file-sandbox/src/test/resources/logging.properties b/vavi-nio-file-sandbox/src/test/resources/logging.properties index 0a62dc8f..32c1270b 100644 --- a/vavi-nio-file-sandbox/src/test/resources/logging.properties +++ b/vavi-nio-file-sandbox/src/test/resources/logging.properties @@ -12,5 +12,7 @@ java.util.logging.ConsoleHandler.level=INFO java.util.logging.ConsoleHandler.formatter=vavi.util.logging.VaviFormatter #java.util.logging.ConsoleHandler.formatter=vavi.util.logging.BetterFormatter +vavi.nio.file.onedrive4.graph.MyLogger.level=OFF #com.box.sdk.level=ALL +#vavi.level=OFF diff --git a/vavi-nio-file-sandbox/src/test/resources/test.zip b/vavi-nio-file-sandbox/src/test/resources/test.zip new file mode 100644 index 00000000..8fcb7600 Binary files /dev/null and b/vavi-nio-file-sandbox/src/test/resources/test.zip differ diff --git a/vavi-nio-file-vfs/pom.xml b/vavi-nio-file-vfs/pom.xml index 6c67b6a2..dd12d64c 100644 --- a/vavi-nio-file-vfs/pom.xml +++ b/vavi-nio-file-vfs/pom.xml @@ -4,7 +4,7 @@ vavi-apps-fuse vavi - 0.1.6 + 0.1.7 vavi-nio-file-vfs @@ -42,7 +42,7 @@ - com.github.umjammer.vavi-net-auth + ${vavi-net-auth.groupId} vavi-net-auth-common ${vavi-net-auth.version} @@ -51,31 +51,33 @@ org.apache.commons commons-vfs2 - 2.6.0 + 2.9.0 + org.apache.commons commons-vfs2-jackrabbit2 - 2.6.0 + 2.9.0 - jcifs - jcifs - 1.3.17 - - com.jcraft jsch 0.1.55 + - commons-httpclient - commons-httpclient - 3.1 + com.github.vbauer + commons-vfs2-cifs + 1.2.0 - + com.github.mikhasd + commons-vfs2-smb + 1.0.0 + + + org.junit.jupiter junit-jupiter-api test @@ -93,7 +95,6 @@ org.junit.jupiter junit-jupiter-params - 5.3.0 test @@ -123,8 +124,8 @@ org.slf4j - slf4j-log4j12 - 1.7.2 + slf4j-jdk14 + 1.7.5 test diff --git a/vavi-nio-file-vfs/src/main/java/vavi/net/auth/proprietary/vfs/SftpVfsAuthenticator.java b/vavi-nio-file-vfs/src/main/java/vavi/net/auth/proprietary/vfs/SftpVfsAuthenticator.java index ba8bc1ff..aae32272 100644 --- a/vavi-nio-file-vfs/src/main/java/vavi/net/auth/proprietary/vfs/SftpVfsAuthenticator.java +++ b/vavi-nio-file-vfs/src/main/java/vavi/net/auth/proprietary/vfs/SftpVfsAuthenticator.java @@ -9,6 +9,7 @@ import java.io.File; import java.io.IOException; import java.net.URI; +import java.time.Duration; import java.util.Map; import java.util.NoSuchElementException; @@ -145,8 +146,8 @@ public FileSystemOptions authorize(VfsCredential credential) throws IOException FileSystemOptions options = new FileSystemOptions(); SftpFileSystemConfigBuilder builder = SftpFileSystemConfigBuilder.getInstance(); builder.setUserDirIsRoot(options, false); - builder.setConnectTimeoutMillis(options, 30000); - builder.setSessionTimeoutMillis(options, 30000); + builder.setConnectTimeout(options, Duration.ofMillis(30000)); + builder.setSessionTimeout(options, Duration.ofMillis(30000)); builder.setUserInfo(options, new SftpUserInfo(pkc ? c.passphrase : c.password, pkc)); if (pkc) { builder.setIdentityProvider(options, new IdentityInfo(new File(c.keyPath))); diff --git a/vavi-nio-file-vfs/src/main/java/vavi/nio/file/vfs/VfsFileAttributesFactory.java b/vavi-nio-file-vfs/src/main/java/vavi/nio/file/vfs/VfsFileAttributesFactory.java index eec4bfa7..688455d0 100644 --- a/vavi-nio-file-vfs/src/main/java/vavi/nio/file/vfs/VfsFileAttributesFactory.java +++ b/vavi-nio-file-vfs/src/main/java/vavi/nio/file/vfs/VfsFileAttributesFactory.java @@ -8,7 +8,7 @@ import org.apache.commons.vfs2.FileObject; -import com.github.fge.filesystem.attributes.FileAttributesFactory; +import com.github.fge.filesystem.driver.ExtendedFileSystemDriverBase.ExtendsdFileAttributesFactory; /** @@ -17,7 +17,7 @@ * @author Naohide Sano (umjammer) * @version 0.00 2016/04/06 umjammer initial version
*/ -public final class VfsFileAttributesFactory extends FileAttributesFactory { +public final class VfsFileAttributesFactory extends ExtendsdFileAttributesFactory { public VfsFileAttributesFactory() { setMetadataClass(FileObject.class); diff --git a/vavi-nio-file-vfs/src/main/java/vavi/nio/file/vfs/VfsFileStore.java b/vavi-nio-file-vfs/src/main/java/vavi/nio/file/vfs/VfsFileStore.java index 7018ed72..e530239c 100644 --- a/vavi-nio-file-vfs/src/main/java/vavi/nio/file/vfs/VfsFileStore.java +++ b/vavi-nio-file-vfs/src/main/java/vavi/nio/file/vfs/VfsFileStore.java @@ -26,7 +26,7 @@ */ public final class VfsFileStore extends FileStoreBase { - private final FileObject root; // TODO + private final FileObject root; // TODO how to get quota /** * Constructor diff --git a/vavi-nio-file-vfs/src/main/java/vavi/nio/file/vfs/VfsFileSystemDriver.java b/vavi-nio-file-vfs/src/main/java/vavi/nio/file/vfs/VfsFileSystemDriver.java index a289dbf6..35e800e2 100644 --- a/vavi-nio-file-vfs/src/main/java/vavi/nio/file/vfs/VfsFileSystemDriver.java +++ b/vavi-nio-file-vfs/src/main/java/vavi/nio/file/vfs/VfsFileSystemDriver.java @@ -9,26 +9,17 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.nio.file.AccessDeniedException; -import java.nio.file.AccessMode; import java.nio.file.CopyOption; -import java.nio.file.DirectoryNotEmptyException; -import java.nio.file.DirectoryStream; -import java.nio.file.FileAlreadyExistsException; import java.nio.file.FileStore; import java.nio.file.NoSuchFileException; -import java.nio.file.NotDirectoryException; import java.nio.file.OpenOption; import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.nio.file.attribute.FileAttribute; -import java.nio.file.spi.FileSystemProvider; -import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; -import javax.annotation.Nonnull; import javax.annotation.ParametersAreNonnullByDefault; import org.apache.commons.vfs2.FileObject; @@ -36,8 +27,7 @@ import org.apache.commons.vfs2.FileSystemOptions; import org.apache.commons.vfs2.Selectors; -import com.github.fge.filesystem.driver.ExtendedFileSystemDriverBase; -import com.github.fge.filesystem.exceptions.IsDirectoryException; +import com.github.fge.filesystem.driver.ExtendedFileSystemDriver; import com.github.fge.filesystem.provider.FileSystemFactoryProvider; import vavi.nio.file.Util; @@ -54,40 +44,53 @@ * @version 0.00 2016/03/30 umjammer initial version
*/ @ParametersAreNonnullByDefault -public final class VfsFileSystemDriver extends ExtendedFileSystemDriverBase { +public final class VfsFileSystemDriver extends ExtendedFileSystemDriver { private final FileSystemManager manager; private final FileSystemOptions opts; - private boolean ignoreAppleDouble = false; - private final String baseUrl; /** * @param env { "baseUrl": "smb://10.3.1.1/Temporary Share/", "ignoreAppleDouble": boolean } */ - @SuppressWarnings("unchecked") - public VfsFileSystemDriver(final FileStore fileStore, - final FileSystemFactoryProvider provider, - final FileSystemManager manager, - final FileSystemOptions options, + public VfsFileSystemDriver(FileStore fileStore, + FileSystemFactoryProvider provider, + FileSystemManager manager, + FileSystemOptions options, String baseUrl, - final Map env) throws IOException { + Map env) throws IOException { + super(fileStore, provider); + + setEnv(env); + this.manager = manager; this.opts = options; - ignoreAppleDouble = (Boolean) ((Map) env).getOrDefault("ignoreAppleDouble", Boolean.FALSE); + //System.err.println("ignoreAppleDouble: " + ignoreAppleDouble); this.baseUrl = baseUrl; } - /** - * VFS might have cache? - * @see #ignoreAppleDouble - * @throws NoSuchFileException apple double file - */ - private FileObject getEntry(Path path) throws IOException { + @Override + protected String getFilenameString(FileObject entry) { + return entry.getName().getBaseName(); + } + + @Override + protected boolean isFolder(FileObject entry) throws IOException { + return entry.isFolder(); + } + + @Override + protected boolean exists(FileObject entry) throws IOException { + return entry.exists(); + } + + // VFS might have cache? + @Override + protected FileObject getEntry(Path path) throws IOException { return getEntry(path, true); } @@ -95,13 +98,13 @@ private FileObject getEntry(Path path) throws IOException { * @param check check existence of the path */ private FileObject getEntry(Path path, boolean check) throws IOException { -//System.err.println("path: " + path); +//Debug.println(Level.FINE, "path: " + path); if (ignoreAppleDouble && path.getFileName() != null && isAppleDouble(path)) { throw new NoSuchFileException("ignore apple double file: " + path); } FileObject entry = manager.resolveFile(baseUrl + toPathString(path), opts); -//System.err.println("entry: " + entry + ", " + entry.exists()); +//Debug.println(Level.FINE, "entry: " + entry + ", " + entry.exists()); if (check) { if (entry.exists()) { return entry; @@ -113,226 +116,68 @@ private FileObject getEntry(Path path, boolean check) throws IOException { } } - @Nonnull @Override - public InputStream newInputStream(final Path path, final Set options) throws IOException { - final FileObject entry = getEntry(path); - - if (entry.isFolder()) { - throw new IsDirectoryException(path.toString()); - } - + protected InputStream downloadEntry(FileObject entry, Path path, Set options) throws IOException { return entry.getContent().getInputStream(Util.BUFFER_SIZE); } - @Nonnull @Override - public OutputStream newOutputStream(final Path path, final Set options) throws IOException { - final FileObject entry = getEntry(path, false); - - if (entry.exists()) { - if (entry.isFolder()) { - throw new IsDirectoryException(path.toString()); - } else { - throw new FileAlreadyExistsException(path.toString()); - } - } else { - entry.createFile(); - } - - return entry.getContent().getOutputStream(Util.BUFFER_SIZE); + protected OutputStream uploadEntry(FileObject parentEntry, Path path, Set options) throws IOException { + FileObject targetEntry = getEntry(path, false); + targetEntry.createFile(); + return targetEntry.getContent().getOutputStream(Util.BUFFER_SIZE); } - @Nonnull - @Override - public DirectoryStream newDirectoryStream(final Path dir, - final DirectoryStream.Filter filter) throws IOException { - return Util.newDirectoryStream(getDirectoryEntries(dir), filter); + /** */ + protected List getDirectoryEntries(FileObject dirEntry, Path dir) throws IOException { +//System.err.println("path: " + dir); +//Arrays.stream(dirEntry.getChildren()).forEach(System.err::println); + return Arrays.stream(dirEntry.getChildren()).collect(Collectors.toList()); } @Override - public void createDirectory(final Path dir, final FileAttribute... attrs) throws IOException { + protected FileObject createDirectoryEntry(FileObject parentEntry, Path dir) throws IOException { FileObject dirEntry = getEntry(dir, false); - if (dirEntry.exists()) { - throw new FileAlreadyExistsException(dir.toString()); - } - dirEntry.createFolder(); + return dirEntry; } @Override - public void delete(final Path path) throws IOException { - removeEntry(path); + protected boolean hasChildren(FileObject dirEntry, Path dir) throws IOException { + return dirEntry.getChildren().length > 0; } @Override - public void copy(final Path source, final Path target, final Set options) throws IOException { - FileObject targetEntry = getEntry(target, false); - if (targetEntry.exists()) { - if (options != null && options.stream().anyMatch(o -> o.equals(StandardCopyOption.REPLACE_EXISTING))) { - removeEntry(target); - } else { - throw new FileAlreadyExistsException(target.toString()); - } + protected void removeEntry(FileObject entry, Path path) throws IOException { + if (!entry.delete()) { + throw new IOException("delete failed: " + path); } - copyEntry(source, target); } @Override - public void move(final Path source, final Path target, final Set options) throws IOException { + protected FileObject copyEntry(FileObject sourceEntry, FileObject targetParentEntry, Path source, Path target, Set options) throws IOException { FileObject targetEntry = getEntry(target, false); - if (targetEntry.exists()) { - if (targetEntry.isFolder()) { - if (options != null && options.stream().anyMatch(o -> o.equals(StandardCopyOption.REPLACE_EXISTING))) { - // replace the target - if (targetEntry.getChildren().length > 0) { - throw new DirectoryNotEmptyException(target.toString()); - } else { - removeEntry(target); - moveEntry(source, target, false); - } - } else { - // move into the target - // TODO SPEC is FileAlreadyExistsException ? - moveEntry(source, target, true); - } - } else { - if (options != null && options.stream().anyMatch(o -> o.equals(StandardCopyOption.REPLACE_EXISTING))) { - removeEntry(target); - moveEntry(source, target, false); - } else { - throw new FileAlreadyExistsException(target.toString()); - } - } - } else { - if (source.getParent().equals(target.getParent())) { - // rename - renameEntry(source, target); - } else { - moveEntry(source, target, false); - } - } + targetEntry.copyFrom(sourceEntry, Selectors.SELECT_ALL); + return targetEntry; } - /** - * Check access modes for a path on this filesystem - *

- * If no modes are provided to check for, this simply checks for the - * existence of the path. - *

- * - * @param path the path to check - * @param modes the modes to check for, if any - * @throws IOException filesystem level error, or a plain I/O error if you - * use this with javafs (jnr-fuse), you should throw - * {@link NoSuchFileException} when the file not found. - * @see FileSystemProvider#checkAccess(Path, AccessMode...) - */ @Override - protected void checkAccessImpl(final Path path, final AccessMode... modes) throws IOException { - final FileObject entry = getEntry(path); - - if (entry.isFolder()) { - return; - } - - // TODO: assumed; not a file == directory - for (final AccessMode mode : modes) { - if (mode == AccessMode.EXECUTE) { - throw new AccessDeniedException(path.toString()); - } - } + protected FileObject moveEntry(FileObject sourceEntry, FileObject targetParentEntry, Path source, Path target, boolean targetIsParent) throws IOException { + FileObject targetEntry = getEntry(targetIsParent ? target.resolve(toFilenameString(source)) : target, false); + sourceEntry.moveTo(targetEntry); + return targetEntry; } @Override - public void close() throws IOException { - // don't close the manager here, it will shutdown whole resources. - // https://issues.apache.org/jira/browse/VFS-454 + protected FileObject moveFolderEntry(FileObject sourceEntry, FileObject targetParentEntry, Path source, Path target, boolean targetIsParent) throws IOException { + return moveEntry(sourceEntry, targetParentEntry, source, target, targetIsParent); } - /** - * @throws IOException if you use this with javafs (jnr-fuse), you should - * throw {@link NoSuchFileException} when the file not found. - */ - @Nonnull @Override - protected Object getPathMetadataImpl(final Path path) throws IOException { - return getEntry(path); - } - - /** */ - private List getDirectoryEntries(final Path dir) throws IOException { - final FileObject entry = getEntry(dir); - - if (!entry.isFolder()) { - throw new NotDirectoryException(dir.toString()); - } - - List list = null; - final FileObject[] children = entry.getChildren(); - list = new ArrayList<>(children.length); - - for (final FileObject child : children) { - Path childPath = dir.resolve(child.getName().getBaseName()); - list.add(childPath); - } - - return list; + protected FileObject renameEntry(FileObject sourceEntry, FileObject targetParentEntry, Path source, Path target) throws IOException { + return moveEntry(sourceEntry, targetParentEntry, source, target, false); } - /** */ - private void removeEntry(Path path) throws IOException { - final FileObject entry = getEntry(path); - - if (entry.isFolder()) { - final FileObject[] list = entry.getChildren(); - - if (list.length > 0) { - throw new DirectoryNotEmptyException(path.toString()); - } - } - - if (!entry.delete()) { - throw new IOException("delete: " + path); - } - } - - /** */ - private void copyEntry(final Path source, final Path target) throws IOException { - FileObject targetEntry = getEntry(target, false); - FileObject sourceEntry = getEntry(source); - - if (sourceEntry.isFile()) { - targetEntry.copyFrom(sourceEntry, Selectors.SELECT_ALL); - } else if (sourceEntry.isFolder()) { - // TODO java spec. allows empty folder - throw new IsDirectoryException(source.toString()); - } - } - - /** - * @param targetIsParent if the target is folder - */ - private void moveEntry(final Path source, final Path target, boolean targetIsParent) throws IOException { - FileObject sourceEntry = getEntry(source); - FileObject targetEntry = getEntry(targetIsParent ? target.resolve(toFilenameString(source)) : target, false); - - if (sourceEntry.isFile()) { - sourceEntry.moveTo(targetEntry); - } else if (sourceEntry.isFolder()) { - sourceEntry.moveTo(targetEntry); - } - } - - /** */ - private void renameEntry(final Path source, final Path target) throws IOException { - FileObject sourceEntry = getEntry(source); - FileObject targetEntry = getEntry(target, false); - - if (sourceEntry.isFile()) { - sourceEntry.moveTo(targetEntry); - } else if (sourceEntry.isFolder()) { - throw new IsDirectoryException(source.toString()); - } - } + // don't close the manager here, it will shutdown whole resources. + // https://issues.apache.org/jira/browse/VFS-454 } diff --git a/vavi-nio-file-vfs/src/main/java/vavi/nio/file/vfs/VfsFileSystemRepository.java b/vavi-nio-file-vfs/src/main/java/vavi/nio/file/vfs/VfsFileSystemRepository.java index 13dc1b37..3b3504b5 100644 --- a/vavi-nio-file-vfs/src/main/java/vavi/nio/file/vfs/VfsFileSystemRepository.java +++ b/vavi-nio-file-vfs/src/main/java/vavi/nio/file/vfs/VfsFileSystemRepository.java @@ -67,10 +67,10 @@ public FileSystemDriver createDriver(final URI uri, final Map env) th Debug.println("baseUrl: " + baseUrl); FileSystemManager manager = VFS.getManager(); -//for (String scheme : manager.getSchemes()) { -// System.err.println("scheme: " + scheme); -//} if (!manager.hasProvider(protocol)) { +for (String scheme : manager.getSchemes()) { + System.err.println("scheme: " + scheme); +} throw new IllegalStateException("missing provider: " + protocol); } diff --git a/vavi-nio-file-vfs/src/test/java/vavi/nio/file/vfs/Main2.java b/vavi-nio-file-vfs/src/test/java/vavi/nio/file/vfs/Main2.java index 1187d2b4..0ae6b02e 100644 --- a/vavi-nio-file-vfs/src/test/java/vavi/nio/file/vfs/Main2.java +++ b/vavi-nio-file-vfs/src/test/java/vavi/nio/file/vfs/Main2.java @@ -44,7 +44,7 @@ void test01() throws Exception { URI uri = URI.create(String.format("vfs:sftp://%s@%s%s?keyPath=%s&passphrase=%s", username, host, path, keyPath, passPhrase)); - FileSystem fs = new VfsFileSystemProvider().newFileSystem(uri, Collections.EMPTY_MAP); + FileSystem fs = new VfsFileSystemProvider().newFileSystem(uri, Collections.emptyMap()); testLargeFile(fs, null); } diff --git a/vavi-nio-file-vfs/src/test/java/vavi/nio/file/vfs/Main3.java b/vavi-nio-file-vfs/src/test/java/vavi/nio/file/vfs/Main3.java index 524690c6..63840ba5 100644 --- a/vavi-nio-file-vfs/src/test/java/vavi/nio/file/vfs/Main3.java +++ b/vavi-nio-file-vfs/src/test/java/vavi/nio/file/vfs/Main3.java @@ -44,7 +44,7 @@ void test01() throws Exception { URI uri = URI.create(String.format("vfs:sftp://%s@%s%s?keyPath=%s&passphrase=%s", username, host, path, keyPath, passPhrase)); - FileSystem fs = new VfsFileSystemProvider().newFileSystem(uri, Collections.EMPTY_MAP); + FileSystem fs = new VfsFileSystemProvider().newFileSystem(uri, Collections.emptyMap()); testMoveFolder(fs); } diff --git a/vavi-nio-file-vfs/src/test/java/vavi/nio/file/vfs/TestVfs.java b/vavi-nio-file-vfs/src/test/java/vavi/nio/file/vfs/TestVfs.java index 67241317..48271043 100644 --- a/vavi-nio-file-vfs/src/test/java/vavi/nio/file/vfs/TestVfs.java +++ b/vavi-nio-file-vfs/src/test/java/vavi/nio/file/vfs/TestVfs.java @@ -1,10 +1,11 @@ -package vavi.nio.file.vfs; /* * Copyright (c) 2016 by Naohide Sano, All rights reserved. * * Programmed by Naohide Sano */ +package vavi.nio.file.vfs; + import java.io.IOException; import java.net.URI; diff --git a/vavi-nio-file-vfs/src/test/java/vavi/nio/file/vfs/TestVfsSftp.java b/vavi-nio-file-vfs/src/test/java/vavi/nio/file/vfs/TestVfsSftp.java index f48a84a7..234f5cda 100644 --- a/vavi-nio-file-vfs/src/test/java/vavi/nio/file/vfs/TestVfsSftp.java +++ b/vavi-nio-file-vfs/src/test/java/vavi/nio/file/vfs/TestVfsSftp.java @@ -7,6 +7,7 @@ import java.io.File; import java.io.IOException; +import java.time.Duration; import org.apache.commons.vfs2.FileObject; import org.apache.commons.vfs2.FileSystemManager; @@ -82,7 +83,7 @@ void proceed() throws IOException { FileSystemOptions options = new FileSystemOptions(); // SftpFileSystemConfigBuilder.getInstance().setStrictHostKeyChecking(options, "no"); SftpFileSystemConfigBuilder.getInstance().setUserDirIsRoot(options, false); - SftpFileSystemConfigBuilder.getInstance().setSessionTimeoutMillis(options, 10000); + SftpFileSystemConfigBuilder.getInstance().setSessionTimeout(options, Duration.ofMillis(10000)); SftpFileSystemConfigBuilder.getInstance().setUserInfo(options, new SftpPassphraseUserInfo(passphrase)); SftpFileSystemConfigBuilder.getInstance().setIdentityProvider(options, new IdentityInfo(new File(keyPath))); FileSystemManager fs = VFS.getManager(); diff --git a/vavi-nio-file-vfs/src/test/java/vavi/nio/file/vfs/TestVfsSftp2.java b/vavi-nio-file-vfs/src/test/java/vavi/nio/file/vfs/TestVfsSftp2.java index f81173ed..4078781a 100644 --- a/vavi-nio-file-vfs/src/test/java/vavi/nio/file/vfs/TestVfsSftp2.java +++ b/vavi-nio-file-vfs/src/test/java/vavi/nio/file/vfs/TestVfsSftp2.java @@ -6,6 +6,7 @@ */ import java.io.IOException; +import java.time.Duration; import org.apache.commons.vfs2.FileObject; import org.apache.commons.vfs2.FileSystemManager; @@ -78,7 +79,7 @@ void proceed() throws IOException { FileSystemOptions options = new FileSystemOptions(); // SftpFileSystemConfigBuilder.getInstance().setStrictHostKeyChecking(options, "no"); SftpFileSystemConfigBuilder.getInstance().setUserDirIsRoot(options, false); - SftpFileSystemConfigBuilder.getInstance().setSessionTimeoutMillis(options, 10000); + SftpFileSystemConfigBuilder.getInstance().setSessionTimeout(options, Duration.ofMillis(10000)); SftpFileSystemConfigBuilder.getInstance().setUserInfo(options, new SftpPasswordUserInfo(passphrase)); FileSystemManager fs = VFS.getManager(); if (!fs.hasProvider("sftp")) diff --git a/vavi-nio-file-vfs/src/test/java/vavi/nio/file/vfs/TestVfsWebdav.java b/vavi-nio-file-vfs/src/test/java/vavi/nio/file/vfs/TestVfsWebdav.java index 6f76c946..76cce34f 100644 --- a/vavi-nio-file-vfs/src/test/java/vavi/nio/file/vfs/TestVfsWebdav.java +++ b/vavi-nio-file-vfs/src/test/java/vavi/nio/file/vfs/TestVfsWebdav.java @@ -1,10 +1,11 @@ -package vavi.nio.file.vfs; /* * Copyright (c) 2016 by Naohide Sano, All rights reserved. * * Programmed by Naohide Sano */ +package vavi.nio.file.vfs; + import java.io.IOException; import java.net.URLEncoder; @@ -66,7 +67,7 @@ void proceed() throws IOException { //System.err.println(smbFile.exists() + " " + smbFile.getContent().getLastModifiedTime()); if (davFile.isFolder()) { for (FileObject fo : davFile.getChildren()) { -System.err.println(fo.getName()); // TODO 文字化け +System.err.println(fo.getName()); // TODO garbled text } } }