diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 36cb45e..a7a9b82 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,8 +7,8 @@ jobs: runs-on: ubuntu-latest if: "!contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]')" steps: - - uses: actions/checkout@v2 - - uses: actions/setup-java@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-java@v3 with: distribution: 'temurin' java-version: 17 @@ -19,12 +19,12 @@ jobs: - name: Build and Test id: buildAndTest run: mvn -B clean install - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 with: name: artifacts path: target/*.jar - name: Create Release - uses: actions/create-release@v1 + uses: actions/create-release@v1 # NOTE: action is unmaintained and repo archived if: startsWith(github.ref, 'refs/tags/') env: GITHUB_TOKEN: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }} # release as "cryptobot" diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index c95b54d..7eedd35 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -15,19 +15,19 @@ jobs: runs-on: ubuntu-latest if: "!contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]')" steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: fetch-depth: 2 - - uses: actions/setup-java@v2 + - uses: actions/setup-java@v3 with: distribution: 'temurin' java-version: 17 cache: 'maven' - name: Initialize CodeQL - uses: github/codeql-action/init@v1 + uses: github/codeql-action/init@v2 with: languages: java - name: Build run: mvn -B compile - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 \ No newline at end of file + uses: github/codeql-action/analyze@v2 \ No newline at end of file diff --git a/.github/workflows/publish-central.yml b/.github/workflows/publish-central.yml index f3897af..6cecb12 100644 --- a/.github/workflows/publish-central.yml +++ b/.github/workflows/publish-central.yml @@ -10,10 +10,10 @@ jobs: publish: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: ref: "refs/tags/${{ github.event.inputs.tag }}" - - uses: actions/setup-java@v2 + - uses: actions/setup-java@v3 with: distribution: 'temurin' java-version: 17 diff --git a/.github/workflows/publish-github.yml b/.github/workflows/publish-github.yml index 885c659..309a8ac 100644 --- a/.github/workflows/publish-github.yml +++ b/.github/workflows/publish-github.yml @@ -7,8 +7,8 @@ jobs: runs-on: ubuntu-latest if: startsWith(github.ref, 'refs/tags/') # only allow publishing tagged versions steps: - - uses: actions/checkout@v2 - - uses: actions/setup-java@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-java@v3 with: distribution: 'temurin' java-version: 17 diff --git a/pom.xml b/pom.xml index 922d445..3e18fb0 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 org.cryptomator integrations-api - 1.1.0 + 1.2.0 Cryptomator Integrations API Defines optional service interfaces that may be used by Cryptomator @@ -59,13 +59,13 @@ org.junit.jupiter junit-jupiter - 5.8.2 + 5.9.0 test org.mockito mockito-core - 4.3.1 + 4.8.0 test @@ -92,6 +92,11 @@ + + org.apache.maven.plugins + maven-surefire-plugin + 3.0.0-M7 + maven-javadoc-plugin 3.2.0 diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 12d8bd4..7108775 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -1,3 +1,5 @@ +import org.cryptomator.integrations.mount.MountService; +import org.cryptomator.integrations.revealpath.RevealPathService; import org.cryptomator.integrations.tray.TrayMenuController; import org.cryptomator.integrations.autostart.AutoStartProvider; import org.cryptomator.integrations.keychain.KeychainAccessProvider; @@ -12,11 +14,15 @@ exports org.cryptomator.integrations.autostart; exports org.cryptomator.integrations.common; exports org.cryptomator.integrations.keychain; + exports org.cryptomator.integrations.mount; + exports org.cryptomator.integrations.revealpath; exports org.cryptomator.integrations.tray; exports org.cryptomator.integrations.uiappearance; uses AutoStartProvider; uses KeychainAccessProvider; + uses MountService; + uses RevealPathService; uses TrayIntegrationProvider; uses TrayMenuController; uses UiAppearanceProvider; diff --git a/src/main/java/org/cryptomator/integrations/mount/Mount.java b/src/main/java/org/cryptomator/integrations/mount/Mount.java new file mode 100644 index 0000000..24754d9 --- /dev/null +++ b/src/main/java/org/cryptomator/integrations/mount/Mount.java @@ -0,0 +1,50 @@ +package org.cryptomator.integrations.mount; + +import java.io.IOException; + +/** + * Handle to control the lifecycle of a mounted file system. + *

+ * Created by {@link MountBuilder} + */ +public interface Mount extends AutoCloseable { + + /** + * Returns the absolute OS path, where this mount can be accessed. + * + * @return Absolute path to the mountpoint. + */ + Mountpoint getMountpoint(); + + /** + * Unmounts the mounted Volume. + *

+ * If possible, attempt a graceful unmount. + * + * @throws UnmountFailedException If the unmount was not successful. + * @see #unmountForced() + */ + void unmount() throws UnmountFailedException; + + /** + * If supported, force-unmount the volume. + * + * @throws UnmountFailedException If the unmount was not successful. + * @throws UnsupportedOperationException If {@link MountCapability#UNMOUNT_FORCED} is not supported + */ + default void unmountForced() throws UnmountFailedException { + throw new UnsupportedOperationException(); + } + + /** + * Unmounts (if required) and releases any resources. + * + * @throws UnmountFailedException Thrown if unmounting failed + * @throws IOException Thrown if cleaning up resources failed + */ + default void close() throws UnmountFailedException, IOException { + unmount(); + } + + +} diff --git a/src/main/java/org/cryptomator/integrations/mount/MountBuilder.java b/src/main/java/org/cryptomator/integrations/mount/MountBuilder.java new file mode 100644 index 0000000..7c9f903 --- /dev/null +++ b/src/main/java/org/cryptomator/integrations/mount/MountBuilder.java @@ -0,0 +1,128 @@ +package org.cryptomator.integrations.mount; + +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.Range; + +import java.nio.file.Path; + +/** + * Builder to mount a filesystem. + *

+ * The setter may attempt to validate the input, but {@link #mount()} may still fail due to missing or invalid (combination of) options. + * This holds especially for {@link MountBuilder#setMountFlags(String)}; + */ +public interface MountBuilder { + + /** + * Sets the file system name. + * + * @param fileSystemName file system name + * @return this + * @throws UnsupportedOperationException If {@link MountCapability#FILE_SYSTEM_NAME} is not supported + */ + @Contract("_ -> this") + default MountBuilder setFileSystemName(String fileSystemName) { + throw new UnsupportedOperationException(); + } + + /** + * Use the given host name as the loopback address. + * + * @param hostName string conforming with the uri host part + * @return this + * @throws UnsupportedOperationException If {@link MountCapability#LOOPBACK_HOST_NAME} is not supported + */ + @Contract("_ -> this") + default MountBuilder setLoopbackHostName(String hostName) { + throw new UnsupportedOperationException(); + } + + /** + * Use the given TCP port of the loopback address. + * + * @param port Fixed TCP port or 0 to use a system-assigned port + * @return this + * @throws UnsupportedOperationException If {@link MountCapability#LOOPBACK_PORT} is not supported + */ + @Contract("_ -> this") + default MountBuilder setLoopbackPort(@Range(from = 0, to = Short.MAX_VALUE) int port) { + throw new UnsupportedOperationException(); + } + + /** + * Sets the mount point. + *

+ * Unless the mount service provider supports {@link MountCapability#MOUNT_TO_SYSTEM_CHOSEN_PATH}, setting a mount point is required. + * + * @param mountPoint Where to mount the volume + * @return this + */ + @Contract("_ -> this") + default MountBuilder setMountpoint(Path mountPoint) { + throw new UnsupportedOperationException(); + } + + /** + * Sets mount flags. + * + * @param mountFlags Mount flags + * @return this + * @throws UnsupportedOperationException If {@link MountCapability#MOUNT_FLAGS} is not supported + * @see MountService#getDefaultMountFlags() + */ + @Contract("_ -> this") + default MountBuilder setMountFlags(String mountFlags) { + throw new UnsupportedOperationException(); + } + + + /** + * Instructs the mount to be read-only. + * + * @param mountReadOnly Whether to mount read-only. + * @return this + * @throws UnsupportedOperationException If {@link MountCapability#READ_ONLY} is not supported + */ + @Contract("_ -> this") + default MountBuilder setReadOnly(boolean mountReadOnly) { + throw new UnsupportedOperationException(); + } + + /** + * Sets a unique volume id. + *

+ * The volume id is used as a path component, thus must conform with the os-dependent path component restrictions. + * + * @param volumeId String conforming with the os-dependent path component restrictions + * @return this + * @throws UnsupportedOperationException If {@link MountCapability#VOLUME_ID} is not supported + */ + @Contract("_ -> this") + default MountBuilder setVolumeId(String volumeId) { + throw new UnsupportedOperationException(); + } + + /** + * Sets a volume name. + *

+ * The volume name is intended to be human-readable. The input string might be altered to replace non-conforming characters and thus is not suited to identify the volume. + * + * @param volumeName String conforming with the os-dependent naming restrictions + * @return this + * @throws UnsupportedOperationException If {@link MountCapability#VOLUME_NAME} is not supported + */ + @Contract("_ -> this") + default MountBuilder setVolumeName(String volumeName) { + throw new UnsupportedOperationException(); + } + + /** + * Mounts the file system. + * + * @return A mount handle + * @throws MountFailedException If mounting failed + */ + @Contract(" -> new") + Mount mount() throws MountFailedException; + +} diff --git a/src/main/java/org/cryptomator/integrations/mount/MountCapability.java b/src/main/java/org/cryptomator/integrations/mount/MountCapability.java new file mode 100644 index 0000000..73ba871 --- /dev/null +++ b/src/main/java/org/cryptomator/integrations/mount/MountCapability.java @@ -0,0 +1,85 @@ +package org.cryptomator.integrations.mount; + +import java.nio.file.Path; + +/** + * Describes what aspects of the mount implementation can or should be used. + *

+ * This may be used to show or hide different configuration options depending on the chosen mount provider. + */ +public enum MountCapability { + /** + * The builder supports {@link MountBuilder#setFileSystemName(String)}. + */ + FILE_SYSTEM_NAME, + + /** + * The builder supports {@link MountBuilder#setLoopbackHostName(String)}. + */ + LOOPBACK_HOST_NAME, + + /** + * The service provider supports {@link MountService#getDefaultLoopbackPort()} + * and the builder requires {@link MountBuilder#setLoopbackPort(int)}. + */ + LOOPBACK_PORT, + + /** + * The service provider supports {@link MountService#getDefaultMountFlags()} + * and the builder requires {@link MountBuilder#setMountFlags(String)}. + */ + MOUNT_FLAGS, + + /** + * With the exception of a provider-supplied default mount point, the mount point must be an existing dir. + *

+ * This option is mutually exclusive with {@link #MOUNT_WITHIN_EXISTING_PARENT}. + * + * @see #MOUNT_TO_SYSTEM_CHOSEN_PATH + */ + MOUNT_TO_EXISTING_DIR, + + /** + * With the exception of a provider-supplied default mount point, the mount point must be a non-existing + * child within an existing parent. + *

+ * This option is mutually exclusive with {@link #MOUNT_TO_EXISTING_DIR}. + * + * @see #MOUNT_TO_SYSTEM_CHOSEN_PATH + */ + MOUNT_WITHIN_EXISTING_PARENT, + + /** + * The mount point may be a drive letter. + * + * @see #MOUNT_TO_EXISTING_DIR + * @see #MOUNT_WITHIN_EXISTING_PARENT + * @see #MOUNT_TO_SYSTEM_CHOSEN_PATH + */ + MOUNT_AS_DRIVE_LETTER, + + /** + * The service provider supports suggesting a default mount point, if no mount point is set via {@link MountBuilder#setMountpoint(Path)}. + */ + MOUNT_TO_SYSTEM_CHOSEN_PATH, + + /** + * The builder supports {@link MountBuilder#setReadOnly(boolean)}. + */ + READ_ONLY, + + /** + * The mount supports {@link Mount#unmountForced()}. + */ + UNMOUNT_FORCED, + + /** + * The builder requires {@link MountBuilder#setVolumeId(String)}. + */ + VOLUME_ID, + + /** + * The builder supports {@link MountBuilder#setVolumeName(String)}. + */ + VOLUME_NAME +} diff --git a/src/main/java/org/cryptomator/integrations/mount/MountFailedException.java b/src/main/java/org/cryptomator/integrations/mount/MountFailedException.java new file mode 100644 index 0000000..c22556f --- /dev/null +++ b/src/main/java/org/cryptomator/integrations/mount/MountFailedException.java @@ -0,0 +1,16 @@ +package org.cryptomator.integrations.mount; + +public class MountFailedException extends Exception { + + public MountFailedException(String msg) { + super(msg); + } + + public MountFailedException(Exception cause) { + super(cause); + } + + public MountFailedException(String msg, Exception cause) { + super(msg, cause); + } +} diff --git a/src/main/java/org/cryptomator/integrations/mount/MountService.java b/src/main/java/org/cryptomator/integrations/mount/MountService.java new file mode 100644 index 0000000..207f30e --- /dev/null +++ b/src/main/java/org/cryptomator/integrations/mount/MountService.java @@ -0,0 +1,90 @@ +package org.cryptomator.integrations.mount; + +import org.cryptomator.integrations.common.IntegrationsLoader; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.Range; + +import java.nio.file.Path; +import java.util.Set; +import java.util.stream.Stream; + +/** + * A mechanism to mount a file system. + * + * @since 1.2.0 + */ +public interface MountService { + + /** + * Loads all supported mount providers. + * + * @return Stream of supported MountProviders (may be empty) + */ + static Stream get() { + return IntegrationsLoader.loadAll(MountService.class).filter(MountService::isSupported); + } + + /** + * Name of this provider. + * + * @return A human readable name of this provider + */ + String displayName(); + + /** + * Indicates, if this provider can be used. + * + * @return true, if this provider is supported in the current OS environment + * @implSpec This check needs to return fast and in constant time + */ + boolean isSupported(); + + /** + * Default mount flags. May be empty. + * + * @return Concatenated String of valid mount flags + * @throws UnsupportedOperationException If {@link MountCapability#MOUNT_FLAGS} is not supported + */ + default String getDefaultMountFlags() { + throw new UnsupportedOperationException(); + } + + /** + * The default TCP port of the loopback address used by this provider. + * + * @return fixed TCP port or 0 to use a system-assigned port + * @throws UnsupportedOperationException If {@link MountCapability#LOOPBACK_PORT} is not supported + */ + @Range(from = 0, to = Short.MAX_VALUE) + default int getDefaultLoopbackPort() { + throw new UnsupportedOperationException(); + } + + /** + * Mount capabilites supported by this provider. + * + * @return Set of supported {@link MountCapability}s + */ + Set capabilities(); + + /** + * Tests whether this provider supports the given capability. + * + * @param capability The capability + * @return {@code true} if supported + */ + default boolean hasCapability(MountCapability capability) { + return capabilities().contains(capability); + } + + + /** + * Creates a new mount builder. + * + * @param fileSystemRoot The root of the VFS to be mounted + * @return New mount builder + */ + @Contract("_ -> new") + MountBuilder forFileSystem(Path fileSystemRoot); + +} diff --git a/src/main/java/org/cryptomator/integrations/mount/Mountpoint.java b/src/main/java/org/cryptomator/integrations/mount/Mountpoint.java new file mode 100644 index 0000000..91f61ea --- /dev/null +++ b/src/main/java/org/cryptomator/integrations/mount/Mountpoint.java @@ -0,0 +1,36 @@ +package org.cryptomator.integrations.mount; + +import java.net.URI; +import java.nio.file.Path; + +/** + * A {@link Mount}'s mount point. There are two types of mount points: Path-based and URI-based. + */ +public sealed interface Mountpoint permits Mountpoint.WithPath, Mountpoint.WithUri { + + /** + * Gets an URI representation of this mount point. + * + * @return an URI pointing to this mount point + */ + URI uri(); + + static Mountpoint forUri(URI uri) { + return new WithUri(uri); + } + + static Mountpoint forPath(Path path) { + return new WithPath(path); + } + + record WithUri(URI uri) implements Mountpoint { + } + + record WithPath(Path path) implements Mountpoint { + + public URI uri() { + return path.toUri(); + } + + } +} diff --git a/src/main/java/org/cryptomator/integrations/mount/UnmountFailedException.java b/src/main/java/org/cryptomator/integrations/mount/UnmountFailedException.java new file mode 100644 index 0000000..2babcc4 --- /dev/null +++ b/src/main/java/org/cryptomator/integrations/mount/UnmountFailedException.java @@ -0,0 +1,16 @@ +package org.cryptomator.integrations.mount; + +public class UnmountFailedException extends Exception { + + public UnmountFailedException(String msg) { + super(msg); + } + + public UnmountFailedException(Exception cause) { + super(cause); + } + + public UnmountFailedException(String msg, Exception cause) { + super(msg, cause); + } +} diff --git a/src/main/java/org/cryptomator/integrations/mount/package-info.java b/src/main/java/org/cryptomator/integrations/mount/package-info.java new file mode 100644 index 0000000..d6535b2 --- /dev/null +++ b/src/main/java/org/cryptomator/integrations/mount/package-info.java @@ -0,0 +1,7 @@ +/** + * Mount service package + */ +@ApiStatus.Experimental +package org.cryptomator.integrations.mount; + +import org.jetbrains.annotations.ApiStatus; diff --git a/src/main/java/org/cryptomator/integrations/revealpath/RevealFailedException.java b/src/main/java/org/cryptomator/integrations/revealpath/RevealFailedException.java new file mode 100644 index 0000000..c56b6f9 --- /dev/null +++ b/src/main/java/org/cryptomator/integrations/revealpath/RevealFailedException.java @@ -0,0 +1,17 @@ +package org.cryptomator.integrations.revealpath; + +public class RevealFailedException extends Exception { + + public RevealFailedException(String msg) { + super(msg); + } + + public RevealFailedException(Exception cause) { + super(cause); + } + + public RevealFailedException(String msg, Exception cause) { + super(msg, cause); + } + +} diff --git a/src/main/java/org/cryptomator/integrations/revealpath/RevealPathService.java b/src/main/java/org/cryptomator/integrations/revealpath/RevealPathService.java new file mode 100644 index 0000000..db7442e --- /dev/null +++ b/src/main/java/org/cryptomator/integrations/revealpath/RevealPathService.java @@ -0,0 +1,38 @@ +package org.cryptomator.integrations.revealpath; + +import org.cryptomator.integrations.common.IntegrationsLoader; + +import java.nio.file.Path; +import java.util.stream.Stream; + +public interface RevealPathService { + + /** + * Loads all supported service providers. + * + * @return Stream of supported RevealPathService implementations (may be empty) + */ + static Stream get() { + return IntegrationsLoader.loadAll(RevealPathService.class).filter(RevealPathService::isSupported); + } + + /** + * Reveal the path in the system default file manager. + *

+ * If the path points to a file, the parent of the file is opened and the file is selected in the file manager window. + * If the path points to a directory, the directory is opened and its content shown in the file manager window. + * + * @param p Path to reveal + * @throws RevealFailedException if revealing the path failed + */ + void reveal(Path p) throws RevealFailedException; + + /** + * Indicates, if this provider can be used. + * + * @return true, if this provider is supported in the current OS environment + * @implSpec This check needs to return fast and in constant time + */ + boolean isSupported(); + +} diff --git a/src/main/java/org/cryptomator/integrations/tray/TrayMenuController.java b/src/main/java/org/cryptomator/integrations/tray/TrayMenuController.java index 2d8b864..85f8c63 100644 --- a/src/main/java/org/cryptomator/integrations/tray/TrayMenuController.java +++ b/src/main/java/org/cryptomator/integrations/tray/TrayMenuController.java @@ -30,6 +30,14 @@ static Optional get() { */ void showTrayIcon(byte[] imageData, Runnable defaultAction, String tooltip) throws TrayMenuException; + /** + * Updates the icon on the system tray. + * + * @param imageData What image to show + * @throws IllegalStateException thrown when called before an icon has been added + */ + void updateTrayIcon(byte[] imageData); + /** * Show the given options in the tray menu. *

@@ -40,4 +48,16 @@ static Optional get() { */ void updateTrayMenu(List items) throws TrayMenuException; + /** + * Action to run before the tray menu opens. + *

+ * This method is used to set up an event listener for when the menu is opened, + * e.g. so that the vault list can be updated to reflect volume mount state changes + * which occur while Cryptomator is in the system tray (and not open). + * + * @param listener + * @throws IllegalStateException thrown when adding listeners fails (i.e. there's no tray menu) + */ + void onBeforeOpenMenu(Runnable listener); + } diff --git a/src/test/java/org/cryptomator/integrations/common/ClassLoaderFactoryTest.java b/src/test/java/org/cryptomator/integrations/common/ClassLoaderFactoryTest.java index a5a8db5..2463966 100644 --- a/src/test/java/org/cryptomator/integrations/common/ClassLoaderFactoryTest.java +++ b/src/test/java/org/cryptomator/integrations/common/ClassLoaderFactoryTest.java @@ -47,12 +47,15 @@ public void setup(@TempDir Path tmpDir) throws IOException { @Test @DisplayName("can load resources from both jars") public void testForPluginDirWithPath() throws IOException { - var cl = ClassLoaderFactory.forPluginDirWithPath(pluginDir); - var fooContents = cl.getResourceAsStream("foo.properties").readAllBytes(); - var barContents = cl.getResourceAsStream("bar.properties").readAllBytes(); - - Assertions.assertArrayEquals(FOO_CONTENTS, fooContents); - Assertions.assertArrayEquals(BAR_CONTENTS, barContents); + try (var cl = ClassLoaderFactory.forPluginDirWithPath(pluginDir); + var fooIn = cl.getResourceAsStream("foo.properties"); + var barIn = cl.getResourceAsStream("bar.properties")) { + var fooContents = fooIn.readAllBytes(); + var barContents = barIn.readAllBytes(); + + Assertions.assertArrayEquals(FOO_CONTENTS, fooContents); + Assertions.assertArrayEquals(BAR_CONTENTS, barContents); + } } @Test @@ -60,12 +63,15 @@ public void testForPluginDirWithPath() throws IOException { public void testForPluginDirFromSysProp() throws IOException { System.setProperty("cryptomator.pluginDir", pluginDir.toString()); - var cl = ClassLoaderFactory.forPluginDir(); - var fooContents = cl.getResourceAsStream("foo.properties").readAllBytes(); - var barContents = cl.getResourceAsStream("bar.properties").readAllBytes(); + try (var cl = ClassLoaderFactory.forPluginDir(); + var fooIn = cl.getResourceAsStream("foo.properties"); + var barIn = cl.getResourceAsStream("bar.properties")) { + var fooContents = fooIn.readAllBytes(); + var barContents = barIn.readAllBytes(); - Assertions.assertArrayEquals(FOO_CONTENTS, fooContents); - Assertions.assertArrayEquals(BAR_CONTENTS, barContents); + Assertions.assertArrayEquals(FOO_CONTENTS, fooContents); + Assertions.assertArrayEquals(BAR_CONTENTS, barContents); + } } } @@ -126,7 +132,7 @@ public void testFindJars2(@TempDir Path tmpDir) throws IOException { var urls = ClassLoaderFactory.findJars(tmpDir); Arrays.sort(urls, Comparator.comparing(URL::toString)); - Assertions.assertArrayEquals(new URL[]{ + Assertions.assertArrayEquals(new URL[] { new URL(tmpDir.toUri() + "a.jar"), new URL(tmpDir.toUri() + "dir2/b.jar") }, urls); diff --git a/src/test/java/org/cryptomator/integrations/mount/MountpointTest.java b/src/test/java/org/cryptomator/integrations/mount/MountpointTest.java new file mode 100644 index 0000000..38bf6c2 --- /dev/null +++ b/src/test/java/org/cryptomator/integrations/mount/MountpointTest.java @@ -0,0 +1,55 @@ +package org.cryptomator.integrations.mount; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.EnabledOnOs; +import org.junit.jupiter.api.condition.OS; + +import java.net.URI; +import java.nio.file.Path; + +public class MountpointTest { + + @Test + @DisplayName("MountPoint.forPath()") + @DisabledOnOs(OS.WINDOWS) + public void testForPath() { + var path = Path.of("/foo/bar"); + var mountPoint = Mountpoint.forPath(path); + + if (mountPoint instanceof Mountpoint.WithPath m) { + Assertions.assertEquals(path, m.path()); + Assertions.assertEquals(URI.create("file:///foo/bar"), mountPoint.uri()); + } else { + Assertions.fail(); + } + } + + @Test + @DisplayName("MountPoint.forPath() (Windows)") + @EnabledOnOs(OS.WINDOWS) + public void testForPathWindows() { + var path = Path.of("D:\\foo\\bar"); + var mountPoint = Mountpoint.forPath(path); + + if (mountPoint instanceof Mountpoint.WithPath m) { + Assertions.assertEquals(path, m.path()); + Assertions.assertEquals(URI.create("file:///D:/foo/bar"), mountPoint.uri()); + } else { + Assertions.fail(); + } + } + + @Test + @DisplayName("MountPoint.forUri()") + public void testForUri() { + var uri = URI.create("webdav://localhost:8080/foo/bar"); + var mountPoint = Mountpoint.forUri(uri); + + Assertions.assertTrue(mountPoint instanceof Mountpoint.WithUri); + Assertions.assertEquals(uri, mountPoint.uri()); + } + +} \ No newline at end of file