|
11 | 11 | import java.nio.file.Path;
|
12 | 12 | import java.nio.file.attribute.BasicFileAttributes;
|
13 | 13 | import java.util.Arrays;
|
| 14 | +import java.util.List; |
| 15 | +import java.util.Objects; |
14 | 16 | import java.util.concurrent.CountDownLatch;
|
15 | 17 | import java.util.concurrent.TimeUnit;
|
16 | 18 | import java.util.stream.Collectors;
|
17 | 19 |
|
18 | 20 | public class DBusFileMangerRevealPath implements RevealPathService {
|
19 | 21 |
|
| 22 | + private static final String[] FILEMANAGER_OBJECT_PATHS = {"/org/gnome/Nautilus", "/org/kde/dolphin", "/xfce/Thunar"}; |
20 | 23 | private static final String FOR_FOLDERS = "org.freedesktop.FileManager1.ShowFolders";
|
21 | 24 | private static final String FOR_FILES = "org.freedesktop.FileManager1.ShowItems";
|
22 | 25 | private static final int TIMEOUT_THRESHOLD = 5000;
|
@@ -56,24 +59,80 @@ public void reveal(Path path) throws RevealFailedException {
|
56 | 59 |
|
57 | 60 | @Override
|
58 | 61 | public boolean isSupported() {
|
59 |
| - CountDownLatch waitBarrier = new CountDownLatch(3); |
60 |
| - ProcessBuilder builderExistsDbusSend = new ProcessBuilder().command("which", "dbus-send"); |
61 |
| - ProcessBuilder builderExistsNautilus = new ProcessBuilder().command("which", "nautilus"); |
62 |
| - ProcessBuilder builderExistsDolphin = new ProcessBuilder().command("which", "dolphin"); |
| 62 | + CountDownLatch waitBarrier = new CountDownLatch(FILEMANAGER_OBJECT_PATHS.length + 1); |
| 63 | + ProcessBuilder dbusSendExistBuilder = new ProcessBuilder().command("test", " `command -v dbus-send`"); |
| 64 | + List<ProcessBuilder> fileManagerExistBuilders = Arrays.stream(FILEMANAGER_OBJECT_PATHS) |
| 65 | + .map(DBusFileMangerRevealPath::createDbusObjectCheck).toList(); |
| 66 | + |
63 | 67 | try {
|
64 |
| - var existsDbusSend = builderExistsDbusSend.start(); |
| 68 | + var existsDbusSend = dbusSendExistBuilder.start(); |
65 | 69 | existsDbusSend.onExit().thenRun(waitBarrier::countDown);
|
66 |
| - var existsNautilus = builderExistsNautilus.start(); |
67 |
| - existsNautilus.onExit().thenRun(waitBarrier::countDown); |
68 |
| - var existsDolphin = builderExistsDolphin.start(); |
69 |
| - existsDolphin.onExit().thenRun(waitBarrier::countDown); |
| 70 | + //TODO: process process-output in paralell |
| 71 | + List<Process> fileManagerChecks = fileManagerExistBuilders.stream().map(builder -> { |
| 72 | + try { |
| 73 | + return builder.start(); |
| 74 | + } catch (IOException e) { |
| 75 | + waitBarrier.countDown(); //to prevent blocking |
| 76 | + return null; |
| 77 | + } |
| 78 | + }).filter(Objects::nonNull).toList(); |
| 79 | + |
| 80 | + fileManagerChecks.forEach(process -> process.onExit().thenRun(waitBarrier::countDown)); |
70 | 81 | if (waitBarrier.await(TIMEOUT_THRESHOLD, TimeUnit.MILLISECONDS)) {
|
71 |
| - return existsDbusSend.exitValue() == 0 && (existsNautilus.exitValue() == 0 | existsDolphin.exitValue() == 0); |
| 82 | + var zeroExitfileManagerChecks = fileManagerChecks.stream().filter(p -> p.exitValue() == 0).toList(); |
| 83 | + if (existsDbusSend.exitValue() == 0 && zeroExitfileManagerChecks.size() != 0) { |
| 84 | + return parseOutputsForActualDBusObject(zeroExitfileManagerChecks); |
| 85 | + } |
72 | 86 | }
|
73 | 87 | } catch (IOException | InterruptedException e) {
|
74 | 88 | //NO-OP
|
75 | 89 | }
|
76 | 90 | return false;
|
77 | 91 | }
|
78 | 92 |
|
| 93 | + /** |
| 94 | + * Parses process stdout to see if dbus-send answer is just an empty node. |
| 95 | + * <p> |
| 96 | + * Some dbus-send implementations return on calling methods on not-existing objects an empty node |
| 97 | + * <pre> |
| 98 | + * <node> |
| 99 | + * </node> |
| 100 | + * </pre> |
| 101 | + * instead of throwing an error. |
| 102 | + * <p> |
| 103 | + * Regarding parsing, see the dbus spec <a href="https://dbus.freedesktop.org/doc/dbus-specification.html#introspection-format">on the introsepction format</a>. |
| 104 | + * |
| 105 | + * @param dbusChecks List of dbus-send processes with zero-exitcode |
| 106 | + * @return if one dbus-send output contains actual content |
| 107 | + * @throws IOException if the Inputer reader on the process output cannot be created |
| 108 | + */ |
| 109 | + private boolean parseOutputsForActualDBusObject(List<Process> dbusChecks) throws IOException { |
| 110 | + for (var check : dbusChecks) { |
| 111 | + try (var reader = check.inputReader(StandardCharsets.UTF_8)) { |
| 112 | + boolean passedInitialNode = false; |
| 113 | + var line = reader.readLine(); |
| 114 | + while (line != null) { |
| 115 | + if (passedInitialNode) { |
| 116 | + return !line.equals("</node>"); |
| 117 | + } |
| 118 | + passedInitialNode = line.startsWith("<node"); //root node might also contain name, hence no closing bracket |
| 119 | + line = reader.readLine(); |
| 120 | + } |
| 121 | + } |
| 122 | + } |
| 123 | + return false; |
| 124 | + } |
| 125 | + |
| 126 | + private static ProcessBuilder createDbusObjectCheck(String dbusObjectPath) { |
| 127 | + return new ProcessBuilder().command( |
| 128 | + "dbus-send", |
| 129 | + "--session", |
| 130 | + "--print-reply", |
| 131 | + "--reply-timeout=" + TIMEOUT_THRESHOLD, |
| 132 | + "--dest=org.freedesktop.FileManager1", |
| 133 | + "--type=method_call", |
| 134 | + dbusObjectPath, |
| 135 | + "org.freedesktop.DBus.Introspectable.Introspect" |
| 136 | + ); |
| 137 | + } |
79 | 138 | }
|
0 commit comments