From 203f9c418ffdfa20742a0235d5e51962d504eb69 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 8 Feb 2023 19:07:14 +0100 Subject: [PATCH 1/5] for isSupported() replace "whch" usage by dbus-send: * checking known file manager object paths * compatible with dbus impls returning empty nodes * flatpak compatible --- .../revealpath/DBusFileMangerRevealPath.java | 79 ++++++++++++++++--- 1 file changed, 69 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/cryptomator/linux/revealpath/DBusFileMangerRevealPath.java b/src/main/java/org/cryptomator/linux/revealpath/DBusFileMangerRevealPath.java index d1e3647..ef6dd8a 100644 --- a/src/main/java/org/cryptomator/linux/revealpath/DBusFileMangerRevealPath.java +++ b/src/main/java/org/cryptomator/linux/revealpath/DBusFileMangerRevealPath.java @@ -11,12 +11,15 @@ import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributes; import java.util.Arrays; +import java.util.List; +import java.util.Objects; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; public class DBusFileMangerRevealPath implements RevealPathService { + private static final String[] FILEMANAGER_OBJECT_PATHS = {"/org/gnome/Nautilus", "/org/kde/dolphin", "/xfce/Thunar"}; private static final String FOR_FOLDERS = "org.freedesktop.FileManager1.ShowFolders"; private static final String FOR_FILES = "org.freedesktop.FileManager1.ShowItems"; private static final int TIMEOUT_THRESHOLD = 5000; @@ -56,19 +59,30 @@ public void reveal(Path path) throws RevealFailedException { @Override public boolean isSupported() { - CountDownLatch waitBarrier = new CountDownLatch(3); - ProcessBuilder builderExistsDbusSend = new ProcessBuilder().command("which", "dbus-send"); - ProcessBuilder builderExistsNautilus = new ProcessBuilder().command("which", "nautilus"); - ProcessBuilder builderExistsDolphin = new ProcessBuilder().command("which", "dolphin"); + CountDownLatch waitBarrier = new CountDownLatch(FILEMANAGER_OBJECT_PATHS.length + 1); + ProcessBuilder dbusSendExistBuilder = new ProcessBuilder().command("test", " `command -v dbus-send`"); + List fileManagerExistBuilders = Arrays.stream(FILEMANAGER_OBJECT_PATHS) + .map(DBusFileMangerRevealPath::createDbusObjectCheck).toList(); + try { - var existsDbusSend = builderExistsDbusSend.start(); + var existsDbusSend = dbusSendExistBuilder.start(); existsDbusSend.onExit().thenRun(waitBarrier::countDown); - var existsNautilus = builderExistsNautilus.start(); - existsNautilus.onExit().thenRun(waitBarrier::countDown); - var existsDolphin = builderExistsDolphin.start(); - existsDolphin.onExit().thenRun(waitBarrier::countDown); + //TODO: process process-output in paralell + List fileManagerChecks = fileManagerExistBuilders.stream().map(builder -> { + try { + return builder.start(); + } catch (IOException e) { + waitBarrier.countDown(); //to prevent blocking + return null; + } + }).filter(Objects::nonNull).toList(); + + fileManagerChecks.forEach(process -> process.onExit().thenRun(waitBarrier::countDown)); if (waitBarrier.await(TIMEOUT_THRESHOLD, TimeUnit.MILLISECONDS)) { - return existsDbusSend.exitValue() == 0 && (existsNautilus.exitValue() == 0 | existsDolphin.exitValue() == 0); + var zeroExitfileManagerChecks = fileManagerChecks.stream().filter(p -> p.exitValue() == 0).toList(); + if (existsDbusSend.exitValue() == 0 && zeroExitfileManagerChecks.size() != 0) { + return parseOutputsForActualDBusObject(zeroExitfileManagerChecks); + } } } catch (IOException | InterruptedException e) { //NO-OP @@ -76,4 +90,49 @@ public boolean isSupported() { return false; } + /** + * Parses process stdout to see if dbus-send answer is just an empty node. + *

+ * Some dbus-send implementations return on calling methods on not-existing objects an empty node + *

+	 *    <node>
+	 *    </node>
+	 * 
+ * instead of throwing an error. + *

+ * Regarding parsing, see the dbus spec on the introsepction format. + * + * @param dbusChecks List of dbus-send processes with zero-exitcode + * @return if one dbus-send output contains actual content + * @throws IOException if the Inputer reader on the process output cannot be created + */ + private boolean parseOutputsForActualDBusObject(List dbusChecks) throws IOException { + for (var check : dbusChecks) { + try (var reader = check.inputReader(StandardCharsets.UTF_8)) { + boolean passedInitialNode = false; + var line = reader.readLine(); + while (line != null) { + if (passedInitialNode) { + return !line.equals(""); + } + passedInitialNode = line.startsWith(" Date: Wed, 8 Feb 2023 19:16:39 +0100 Subject: [PATCH 2/5] move unit test to correct package --- .../{keychain => revealpath}/DbusFileManagerRevealPathTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/test/java/org/cryptomator/linux/{keychain => revealpath}/DbusFileManagerRevealPathTest.java (96%) diff --git a/src/test/java/org/cryptomator/linux/keychain/DbusFileManagerRevealPathTest.java b/src/test/java/org/cryptomator/linux/revealpath/DbusFileManagerRevealPathTest.java similarity index 96% rename from src/test/java/org/cryptomator/linux/keychain/DbusFileManagerRevealPathTest.java rename to src/test/java/org/cryptomator/linux/revealpath/DbusFileManagerRevealPathTest.java index 9b47263..d79b6a0 100644 --- a/src/test/java/org/cryptomator/linux/keychain/DbusFileManagerRevealPathTest.java +++ b/src/test/java/org/cryptomator/linux/revealpath/DbusFileManagerRevealPathTest.java @@ -1,4 +1,4 @@ -package org.cryptomator.linux.keychain; +package org.cryptomator.linux.revealpath; import org.cryptomator.integrations.revealpath.RevealFailedException; import org.cryptomator.linux.revealpath.DBusFileMangerRevealPath; From 6b115674de3bae618a5c6dffef4ca74d7d64ae63 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 8 Feb 2023 19:45:09 +0100 Subject: [PATCH 3/5] use correct object path for Thunar --- .../cryptomator/linux/revealpath/DBusFileMangerRevealPath.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/cryptomator/linux/revealpath/DBusFileMangerRevealPath.java b/src/main/java/org/cryptomator/linux/revealpath/DBusFileMangerRevealPath.java index ef6dd8a..2cd1e14 100644 --- a/src/main/java/org/cryptomator/linux/revealpath/DBusFileMangerRevealPath.java +++ b/src/main/java/org/cryptomator/linux/revealpath/DBusFileMangerRevealPath.java @@ -19,7 +19,7 @@ public class DBusFileMangerRevealPath implements RevealPathService { - private static final String[] FILEMANAGER_OBJECT_PATHS = {"/org/gnome/Nautilus", "/org/kde/dolphin", "/xfce/Thunar"}; + private static final String[] FILEMANAGER_OBJECT_PATHS = {"/org/gnome/Nautilus", "/org/kde/dolphin", "/org/xfce/Thunar"}; private static final String FOR_FOLDERS = "org.freedesktop.FileManager1.ShowFolders"; private static final String FOR_FILES = "org.freedesktop.FileManager1.ShowItems"; private static final int TIMEOUT_THRESHOLD = 5000; From 13449752ac6f9120032645231e0f4b27517b9434 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 8 Feb 2023 19:50:52 +0100 Subject: [PATCH 4/5] Rename reveal path impl classes --- ...gerRevealPath.java => DBusSendRevealPathService.java} | 4 ++-- ...cryptomator.integrations.revealpath.RevealPathService | 2 +- ...lPathTest.java => DBusSendRevealPathServiceTest.java} | 9 ++++----- 3 files changed, 7 insertions(+), 8 deletions(-) rename src/main/java/org/cryptomator/linux/revealpath/{DBusFileMangerRevealPath.java => DBusSendRevealPathService.java} (97%) rename src/test/java/org/cryptomator/linux/revealpath/{DbusFileManagerRevealPathTest.java => DBusSendRevealPathServiceTest.java} (74%) diff --git a/src/main/java/org/cryptomator/linux/revealpath/DBusFileMangerRevealPath.java b/src/main/java/org/cryptomator/linux/revealpath/DBusSendRevealPathService.java similarity index 97% rename from src/main/java/org/cryptomator/linux/revealpath/DBusFileMangerRevealPath.java rename to src/main/java/org/cryptomator/linux/revealpath/DBusSendRevealPathService.java index 2cd1e14..7dca454 100644 --- a/src/main/java/org/cryptomator/linux/revealpath/DBusFileMangerRevealPath.java +++ b/src/main/java/org/cryptomator/linux/revealpath/DBusSendRevealPathService.java @@ -17,7 +17,7 @@ import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; -public class DBusFileMangerRevealPath implements RevealPathService { +public class DBusSendRevealPathService implements RevealPathService { private static final String[] FILEMANAGER_OBJECT_PATHS = {"/org/gnome/Nautilus", "/org/kde/dolphin", "/org/xfce/Thunar"}; private static final String FOR_FOLDERS = "org.freedesktop.FileManager1.ShowFolders"; @@ -62,7 +62,7 @@ public boolean isSupported() { CountDownLatch waitBarrier = new CountDownLatch(FILEMANAGER_OBJECT_PATHS.length + 1); ProcessBuilder dbusSendExistBuilder = new ProcessBuilder().command("test", " `command -v dbus-send`"); List fileManagerExistBuilders = Arrays.stream(FILEMANAGER_OBJECT_PATHS) - .map(DBusFileMangerRevealPath::createDbusObjectCheck).toList(); + .map(DBusSendRevealPathService::createDbusObjectCheck).toList(); try { var existsDbusSend = dbusSendExistBuilder.start(); diff --git a/src/main/resources/META-INF/services/org.cryptomator.integrations.revealpath.RevealPathService b/src/main/resources/META-INF/services/org.cryptomator.integrations.revealpath.RevealPathService index 8fb02d5..87ee81c 100644 --- a/src/main/resources/META-INF/services/org.cryptomator.integrations.revealpath.RevealPathService +++ b/src/main/resources/META-INF/services/org.cryptomator.integrations.revealpath.RevealPathService @@ -1 +1 @@ -org.cryptomator.linux.revealpath.DBusFileMangerRevealPath \ No newline at end of file +org.cryptomator.linux.revealpath.DBusSendRevealPathService \ No newline at end of file diff --git a/src/test/java/org/cryptomator/linux/revealpath/DbusFileManagerRevealPathTest.java b/src/test/java/org/cryptomator/linux/revealpath/DBusSendRevealPathServiceTest.java similarity index 74% rename from src/test/java/org/cryptomator/linux/revealpath/DbusFileManagerRevealPathTest.java rename to src/test/java/org/cryptomator/linux/revealpath/DBusSendRevealPathServiceTest.java index d79b6a0..eeb8272 100644 --- a/src/test/java/org/cryptomator/linux/revealpath/DbusFileManagerRevealPathTest.java +++ b/src/test/java/org/cryptomator/linux/revealpath/DBusSendRevealPathServiceTest.java @@ -1,7 +1,6 @@ package org.cryptomator.linux.revealpath; import org.cryptomator.integrations.revealpath.RevealFailedException; -import org.cryptomator.linux.revealpath.DBusFileMangerRevealPath; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Disabled; @@ -14,10 +13,10 @@ @EnabledOnOs(OS.LINUX) @Disabled -public class DbusFileManagerRevealPathTest { +public class DBusSendRevealPathServiceTest { @TempDir Path tmpDir; - DBusFileMangerRevealPath inTest = new DBusFileMangerRevealPath(); + DBusSendRevealPathService inTest = new DBusSendRevealPathService(); @Test public void testIsSupported() { @@ -26,7 +25,7 @@ public void testIsSupported() { @Test public void testRevealSuccess() { - DBusFileMangerRevealPath revealPathService = new DBusFileMangerRevealPath(); + DBusSendRevealPathService revealPathService = new DBusSendRevealPathService(); Assumptions.assumeTrue(revealPathService.isSupported()); Assertions.assertDoesNotThrow(() -> revealPathService.reveal(tmpDir)); @@ -34,7 +33,7 @@ public void testRevealSuccess() { @Test public void testRevealFail() { - DBusFileMangerRevealPath revealPathService = new DBusFileMangerRevealPath(); + DBusSendRevealPathService revealPathService = new DBusSendRevealPathService(); Assumptions.assumeTrue(revealPathService.isSupported()); Assertions.assertThrows(RevealFailedException.class, () -> revealPathService.reveal(tmpDir.resolve("foobar"))); From 6c33f55a9c8f03b3c5e2360367b729506f9f92c0 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Thu, 9 Feb 2023 13:20:19 +0100 Subject: [PATCH 5/5] only check for FileManager1 interface --- .../revealpath/DBusSendRevealPathService.java | 74 ++++++------------- 1 file changed, 24 insertions(+), 50 deletions(-) diff --git a/src/main/java/org/cryptomator/linux/revealpath/DBusSendRevealPathService.java b/src/main/java/org/cryptomator/linux/revealpath/DBusSendRevealPathService.java index 7dca454..533a8e5 100644 --- a/src/main/java/org/cryptomator/linux/revealpath/DBusSendRevealPathService.java +++ b/src/main/java/org/cryptomator/linux/revealpath/DBusSendRevealPathService.java @@ -1,5 +1,6 @@ package org.cryptomator.linux.revealpath; +import com.google.common.base.Preconditions; import org.cryptomator.integrations.revealpath.RevealFailedException; import org.cryptomator.integrations.revealpath.RevealPathService; @@ -11,15 +12,16 @@ import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributes; import java.util.Arrays; -import java.util.List; -import java.util.Objects; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; +/** + * RevealPathService provider using the DBus freedesktop FileManager1 interface and dbus-send command. + */ public class DBusSendRevealPathService implements RevealPathService { - private static final String[] FILEMANAGER_OBJECT_PATHS = {"/org/gnome/Nautilus", "/org/kde/dolphin", "/org/xfce/Thunar"}; + private static final String FILEMANAGER1_XML_ELEMENT = ""; private static final String FOR_FOLDERS = "org.freedesktop.FileManager1.ShowFolders"; private static final String FOR_FILES = "org.freedesktop.FileManager1.ShowItems"; private static final int TIMEOUT_THRESHOLD = 5000; @@ -59,29 +61,19 @@ public void reveal(Path path) throws RevealFailedException { @Override public boolean isSupported() { - CountDownLatch waitBarrier = new CountDownLatch(FILEMANAGER_OBJECT_PATHS.length + 1); - ProcessBuilder dbusSendExistBuilder = new ProcessBuilder().command("test", " `command -v dbus-send`"); - List fileManagerExistBuilders = Arrays.stream(FILEMANAGER_OBJECT_PATHS) - .map(DBusSendRevealPathService::createDbusObjectCheck).toList(); + CountDownLatch waitBarrier = new CountDownLatch(2); + ProcessBuilder dbusSendExistsBuilder = new ProcessBuilder().command("test", " `command -v dbus-send`"); + ProcessBuilder fileManager1ExistsBuilder = createFileManager1Check(); try { - var existsDbusSend = dbusSendExistBuilder.start(); - existsDbusSend.onExit().thenRun(waitBarrier::countDown); - //TODO: process process-output in paralell - List fileManagerChecks = fileManagerExistBuilders.stream().map(builder -> { - try { - return builder.start(); - } catch (IOException e) { - waitBarrier.countDown(); //to prevent blocking - return null; - } - }).filter(Objects::nonNull).toList(); + var dbusSendExists = dbusSendExistsBuilder.start(); + dbusSendExists.onExit().thenRun(waitBarrier::countDown); + var fileManager1Exists = fileManager1ExistsBuilder.start(); + fileManager1Exists.onExit().thenRun(waitBarrier::countDown); - fileManagerChecks.forEach(process -> process.onExit().thenRun(waitBarrier::countDown)); if (waitBarrier.await(TIMEOUT_THRESHOLD, TimeUnit.MILLISECONDS)) { - var zeroExitfileManagerChecks = fileManagerChecks.stream().filter(p -> p.exitValue() == 0).toList(); - if (existsDbusSend.exitValue() == 0 && zeroExitfileManagerChecks.size() != 0) { - return parseOutputsForActualDBusObject(zeroExitfileManagerChecks); + if (dbusSendExists.exitValue() == 0 && fileManager1Exists.exitValue() == 0) { + return parseOutputForFileManagerInterface(fileManager1Exists); } } } catch (IOException | InterruptedException e) { @@ -91,39 +83,21 @@ public boolean isSupported() { } /** - * Parses process stdout to see if dbus-send answer is just an empty node. - *

- * Some dbus-send implementations return on calling methods on not-existing objects an empty node - *

-	 *    <node>
-	 *    </node>
-	 * 
- * instead of throwing an error. - *

- * Regarding parsing, see the dbus spec on the introsepction format. + * Parses process stdout to see if the answer contains "{@value FILEMANAGER1_XML_ELEMENT}". + * DBus introspection output is defined in the dbus spec. * - * @param dbusChecks List of dbus-send processes with zero-exitcode - * @return if one dbus-send output contains actual content + * @param fileManager1Process The already exited process for checking the FileManager1 interface + * @return {@code true} if the interface is found in the introspection output, otherwise false * @throws IOException if the Inputer reader on the process output cannot be created */ - private boolean parseOutputsForActualDBusObject(List dbusChecks) throws IOException { - for (var check : dbusChecks) { - try (var reader = check.inputReader(StandardCharsets.UTF_8)) { - boolean passedInitialNode = false; - var line = reader.readLine(); - while (line != null) { - if (passedInitialNode) { - return !line.equals(""); - } - passedInitialNode = line.startsWith("