ossrh
diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java
index 9ec0149..58e6f70 100644
--- a/src/main/java/module-info.java
+++ b/src/main/java/module-info.java
@@ -1,8 +1,13 @@
+import org.cryptomator.integrations.autostart.AutoStartProvider;
import org.cryptomator.integrations.keychain.KeychainAccessProvider;
+import org.cryptomator.integrations.quickaccess.QuickAccessService;
import org.cryptomator.integrations.revealpath.RevealPathService;
import org.cryptomator.integrations.tray.TrayMenuController;
+import org.cryptomator.linux.autostart.FreedesktopAutoStartService;
import org.cryptomator.linux.keychain.KDEWalletKeychainAccess;
import org.cryptomator.linux.keychain.SecretServiceKeychainAccess;
+import org.cryptomator.linux.quickaccess.DolphinPlaces;
+import org.cryptomator.linux.quickaccess.NautilusBookmarks;
import org.cryptomator.linux.revealpath.DBusSendRevealPathService;
import org.cryptomator.linux.tray.AppindicatorTrayMenuController;
@@ -14,9 +19,13 @@
requires org.purejava.kwallet;
requires de.swiesend.secretservice;
+ provides AutoStartProvider with FreedesktopAutoStartService;
provides KeychainAccessProvider with SecretServiceKeychainAccess, KDEWalletKeychainAccess;
provides RevealPathService with DBusSendRevealPathService;
provides TrayMenuController with AppindicatorTrayMenuController;
+ provides QuickAccessService with NautilusBookmarks, DolphinPlaces;
opens org.cryptomator.linux.tray to org.cryptomator.integrations.api;
+ opens org.cryptomator.linux.quickaccess to org.cryptomator.integrations.api;
+ opens org.cryptomator.linux.autostart to org.cryptomator.integrations.api;
}
\ No newline at end of file
diff --git a/src/main/java/org/cryptomator/linux/autostart/FreedesktopAutoStartService.java b/src/main/java/org/cryptomator/linux/autostart/FreedesktopAutoStartService.java
new file mode 100644
index 0000000..43b9cb0
--- /dev/null
+++ b/src/main/java/org/cryptomator/linux/autostart/FreedesktopAutoStartService.java
@@ -0,0 +1,81 @@
+package org.cryptomator.linux.autostart;
+
+import org.cryptomator.integrations.autostart.AutoStartProvider;
+import org.cryptomator.integrations.autostart.ToggleAutoStartFailedException;
+import org.cryptomator.integrations.common.CheckAvailability;
+import org.cryptomator.integrations.common.OperatingSystem;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.Objects;
+
+/**
+ * Enables autostart for Linux desktop environments following the freedesktop standard.
+ *
+ * This service is based on version 0.5 of the freedesktop autostart-spec.
+ */
+@CheckAvailability
+@OperatingSystem(OperatingSystem.Value.LINUX)
+public class FreedesktopAutoStartService implements AutoStartProvider {
+
+ private static final Logger LOG = LoggerFactory.getLogger(FreedesktopAutoStartService.class);
+ private static final String CMD_PROPERTY = "cryptomator.integrationsLinux.autoStartCmd";
+ private static final String AUTOSTART_FILENAME = "Cryptomator.desktop";
+ private static final String CONTENT_TEMPLATE = """
+ [Desktop Entry]
+ Type=Application
+ Exec=%s
+ Hidden=false
+ NoDisplay=false
+ X-GNOME-Autostart-enabled=true
+ Name=Cryptomator
+ Comment=Created with %s
+ """;
+
+ private final Path autostartFile;
+ private final String content;
+ private final boolean hasExecValue;
+
+ public FreedesktopAutoStartService() {
+ var xdgConfigDirString = Objects.requireNonNullElse(System.getenv("XDG_CONFIG_HOME"), System.getProperty("user.home") + "/.config");
+ this.autostartFile = Path.of(xdgConfigDirString, "autostart", AUTOSTART_FILENAME);
+
+ var execValue = System.getProperty(CMD_PROPERTY,"");
+ this.hasExecValue = !execValue.isBlank();
+ this.content = CONTENT_TEMPLATE.formatted(execValue, this.getClass().getName());
+ }
+
+ @Override
+ public synchronized void enable() throws ToggleAutoStartFailedException {
+ try {
+ Files.writeString(autostartFile, content, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING);
+ } catch (IOException e) {
+ throw new ToggleAutoStartFailedException("Failed to activate Cryptomator autostart for GNOME desktop environment.", e);
+ }
+ }
+
+ @Override
+ public synchronized void disable() throws ToggleAutoStartFailedException {
+ try {
+ Files.deleteIfExists(autostartFile);
+ } catch (IOException e) {
+ throw new ToggleAutoStartFailedException("Failed to deactivate Cryptomator autostart for GNOME desktop environment.", e);
+ }
+ }
+
+ @Override
+ public synchronized boolean isEnabled() {
+ return Files.exists(autostartFile);
+ }
+
+ @CheckAvailability
+ public boolean isSupported() {
+ //TODO: might need to research which Desktop Environments support this
+ return hasExecValue && Files.exists(autostartFile.getParent());
+ }
+
+}
diff --git a/src/main/java/org/cryptomator/linux/keychain/KDEWalletKeychainAccess.java b/src/main/java/org/cryptomator/linux/keychain/KDEWalletKeychainAccess.java
index 52e0771..3b3fd98 100644
--- a/src/main/java/org/cryptomator/linux/keychain/KDEWalletKeychainAccess.java
+++ b/src/main/java/org/cryptomator/linux/keychain/KDEWalletKeychainAccess.java
@@ -48,7 +48,7 @@ public boolean isLocked() {
}
@Override
- public void storePassphrase(String key, String displayName, CharSequence passphrase) throws KeychainAccessException {
+ public void storePassphrase(String key, String displayName, CharSequence passphrase, boolean ignored) throws KeychainAccessException {
CheckUtil.checkState(wallet.isPresent(), "Keychain not supported.");
wallet.get().storePassphrase(key, passphrase);
}
diff --git a/src/main/java/org/cryptomator/linux/keychain/SecretServiceKeychainAccess.java b/src/main/java/org/cryptomator/linux/keychain/SecretServiceKeychainAccess.java
index 500c212..86c6734 100644
--- a/src/main/java/org/cryptomator/linux/keychain/SecretServiceKeychainAccess.java
+++ b/src/main/java/org/cryptomator/linux/keychain/SecretServiceKeychainAccess.java
@@ -16,7 +16,7 @@
@OperatingSystem(OperatingSystem.Value.LINUX)
public class SecretServiceKeychainAccess implements KeychainAccessProvider {
- private static Logger LOG = LoggerFactory.getLogger(SecretServiceKeychainAccess.class);
+ private static final Logger LOG = LoggerFactory.getLogger(SecretServiceKeychainAccess.class);
private final String LABEL_FOR_SECRET_IN_KEYRING = "Cryptomator";
@@ -29,8 +29,11 @@ public String displayName() {
public boolean isSupported() {
try {
return SimpleCollection.isAvailable();
- } catch (ExceptionInInitializerError e) {
- LOG.warn("Initializing secret service keychain access failed", e.getException());
+ } catch (RuntimeException e) {
+ LOG.warn("Initializing secret service keychain access failed", e);
+ return false;
+ } catch (ExceptionInInitializerError err) {
+ LOG.warn("Initializing secret service keychain access failed", err.getException());
return false;
}
}
@@ -45,7 +48,7 @@ public boolean isLocked() {
}
@Override
- public void storePassphrase(String key, String displayName, CharSequence passphrase) throws KeychainAccessException {
+ public void storePassphrase(String key, String displayName, CharSequence passphrase, boolean ignored) throws KeychainAccessException {
try (SimpleCollection keyring = new SimpleCollection()) {
List list = keyring.getItems(createAttributes(key));
if (list == null || list.isEmpty()) {
diff --git a/src/main/java/org/cryptomator/linux/quickaccess/DolphinPlaces.java b/src/main/java/org/cryptomator/linux/quickaccess/DolphinPlaces.java
new file mode 100644
index 0000000..16bdbba
--- /dev/null
+++ b/src/main/java/org/cryptomator/linux/quickaccess/DolphinPlaces.java
@@ -0,0 +1,164 @@
+package org.cryptomator.linux.quickaccess;
+
+import org.cryptomator.integrations.common.CheckAvailability;
+import org.cryptomator.integrations.common.DisplayName;
+import org.cryptomator.integrations.common.OperatingSystem;
+import org.cryptomator.integrations.common.Priority;
+import org.cryptomator.integrations.quickaccess.QuickAccessService;
+import org.cryptomator.integrations.quickaccess.QuickAccessServiceException;
+import org.xml.sax.SAXException;
+
+import javax.xml.XMLConstants;
+import javax.xml.transform.Source;
+import javax.xml.transform.stream.StreamSource;
+import javax.xml.validation.SchemaFactory;
+import javax.xml.validation.Validator;
+import java.io.IOException;
+import java.io.StringReader;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.StandardOpenOption;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Implemenation of the {@link QuickAccessService} for KDE desktop environments using Dolphin file browser.
+ */
+@DisplayName("KDE Dolphin Places")
+@CheckAvailability
+@OperatingSystem(OperatingSystem.Value.LINUX)
+@Priority(90)
+public class DolphinPlaces implements QuickAccessService {
+
+ private static final int MAX_FILE_SIZE = 1 << 15; //xml is quite verbose
+ private static final Path PLACES_FILE = Path.of(System.getProperty("user.home"), ".local/share/user-places.xbel");
+ private static final Path TMP_FILE = Path.of(System.getProperty("java.io.tmpdir"), "user-places.xbel.cryptomator.tmp");
+ private static final Lock MODIFY_LOCK = new ReentrantLock();
+ private static final String ENTRY_TEMPLATE = """
+
+ %s
+
+
+
+
+
+ %s
+
+
+ """;
+
+
+ private static final Validator XML_VALIDATOR;
+
+ static {
+ SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
+ try (var schemaDefinition = DolphinPlaces.class.getResourceAsStream("/xbel-1.0.xsd")) {
+ Source schemaFile = new StreamSource(schemaDefinition);
+ XML_VALIDATOR = factory.newSchema(schemaFile).newValidator();
+ } catch (IOException | SAXException e) {
+ throw new IllegalStateException("Failed to load included XBEL schema definition file.", e);
+ }
+ }
+
+
+ @Override
+ public QuickAccessService.QuickAccessEntry add(Path target, String displayName) throws QuickAccessServiceException {
+ String id = UUID.randomUUID().toString();
+ try {
+ MODIFY_LOCK.lock();
+ if (Files.size(PLACES_FILE) > MAX_FILE_SIZE) {
+ throw new IOException("File %s exceeds size of %d bytes".formatted(PLACES_FILE, MAX_FILE_SIZE));
+ }
+ var placesContent = Files.readString(PLACES_FILE);
+ //validate
+ XML_VALIDATOR.validate(new StreamSource(new StringReader(placesContent)));
+ // modify
+ int insertIndex = placesContent.lastIndexOf(" MAX_FILE_SIZE) {
+ throw new IOException("File %s exceeds size of %d bytes".formatted(PLACES_FILE, MAX_FILE_SIZE));
+ }
+ var placesContent = Files.readString(PLACES_FILE);
+ int idIndex = placesContent.lastIndexOf(id);
+ if (idIndex == -1) {
+ isRemoved = true;
+ return; //we assume someone has removed our entry
+ }
+ //validate
+ XML_VALIDATOR.validate(new StreamSource(new StringReader(placesContent)));
+ //modify
+ int openingTagIndex = indexOfEntryOpeningTag(placesContent, idIndex);
+ var contentToWrite1 = placesContent.substring(0, openingTagIndex).stripTrailing();
+
+ int closingTagEndIndex = placesContent.indexOf('>', placesContent.indexOf(" MAX_FILE_SIZE) {
+ throw new IOException("File %s exceeds size of %d bytes".formatted(BOOKMARKS_FILE, MAX_FILE_SIZE));
+ }
+ //by reading all lines, we ensure that each line is terminated with EOL
+ var entries = Files.readAllLines(BOOKMARKS_FILE, StandardCharsets.UTF_8);
+ entries.add(entryLine);
+ Files.write(TMP_FILE, entries, StandardCharsets.UTF_8, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
+ Files.move(TMP_FILE, BOOKMARKS_FILE, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
+ return new NautilusQuickAccessEntry(entryLine);
+ } catch (IOException e) {
+ throw new QuickAccessServiceException("Adding entry to Nautilus bookmarks file failed.", e);
+ } finally {
+ BOOKMARKS_LOCK.unlock();
+ }
+ }
+
+ static class NautilusQuickAccessEntry implements QuickAccessEntry {
+
+ private final String line;
+ private volatile boolean isRemoved = false;
+
+ NautilusQuickAccessEntry(String line) {
+ this.line = line;
+ }
+
+ @Override
+ public void remove() throws QuickAccessServiceException {
+ try {
+ BOOKMARKS_LOCK.lock();
+ if (isRemoved) {
+ return;
+ }
+ if (Files.size(BOOKMARKS_FILE) > MAX_FILE_SIZE) {
+ throw new IOException("File %s exceeds size of %d bytes".formatted(BOOKMARKS_FILE, MAX_FILE_SIZE));
+ }
+ var entries = Files.readAllLines(BOOKMARKS_FILE);
+ if (entries.remove(line)) {
+ Files.write(TMP_FILE, entries, StandardCharsets.UTF_8, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
+ Files.move(TMP_FILE, BOOKMARKS_FILE, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
+ }
+ isRemoved = true;
+ } catch (IOException e) {
+ throw new QuickAccessServiceException("Removing entry from Nautilus bookmarks file failed", e);
+ } finally {
+ BOOKMARKS_LOCK.unlock();
+ }
+ }
+ }
+
+ @CheckAvailability
+ public static boolean isSupported() {
+ return Files.exists(BOOKMARKS_FILE);
+ }
+}
diff --git a/src/main/java/org/cryptomator/linux/revealpath/DBusSendRevealPathService.java b/src/main/java/org/cryptomator/linux/revealpath/DBusSendRevealPathService.java
index 39529ff..be39d6b 100644
--- a/src/main/java/org/cryptomator/linux/revealpath/DBusSendRevealPathService.java
+++ b/src/main/java/org/cryptomator/linux/revealpath/DBusSendRevealPathService.java
@@ -11,7 +11,6 @@
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Arrays;
-import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@@ -60,20 +59,10 @@ public void reveal(Path path) throws RevealFailedException {
@Override
public boolean isSupported() {
- CountDownLatch waitBarrier = new CountDownLatch(2);
- ProcessBuilder dbusSendExistsBuilder = new ProcessBuilder().command("test", " `command -v dbus-send`");
- ProcessBuilder fileManager1ExistsBuilder = createFileManager1Check();
-
try {
- var dbusSendExists = dbusSendExistsBuilder.start();
- dbusSendExists.onExit().thenRun(waitBarrier::countDown);
- var fileManager1Exists = fileManager1ExistsBuilder.start();
- fileManager1Exists.onExit().thenRun(waitBarrier::countDown);
-
- if (waitBarrier.await(TIMEOUT_THRESHOLD, TimeUnit.MILLISECONDS)) {
- if (dbusSendExists.exitValue() == 0 && fileManager1Exists.exitValue() == 0) {
- return parseOutputForFileManagerInterface(fileManager1Exists);
- }
+ var fileManager1Exists = createFileManager1Check().start();
+ if (fileManager1Exists.waitFor(TIMEOUT_THRESHOLD, TimeUnit.MILLISECONDS) && fileManager1Exists.exitValue() == 0) {
+ return parseOutputForFileManagerInterface(fileManager1Exists);
}
} catch (IOException | InterruptedException e) {
//NO-OP
@@ -90,7 +79,7 @@ public boolean isSupported() {
* @throws IOException if the Inputer reader on the process output cannot be created
*/
private boolean parseOutputForFileManagerInterface(Process fileManager1Process) throws IOException {
- if( fileManager1Process.isAlive()) {
+ if (fileManager1Process.isAlive()) {
throw new IllegalArgumentException("Process " + fileManager1Process + " must be terminated to read output.");
}
try (var reader = fileManager1Process.inputReader(StandardCharsets.UTF_8)) {
diff --git a/src/main/java/org/cryptomator/linux/tray/AppindicatorTrayMenuController.java b/src/main/java/org/cryptomator/linux/tray/AppindicatorTrayMenuController.java
index ea37e2f..71924ca 100644
--- a/src/main/java/org/cryptomator/linux/tray/AppindicatorTrayMenuController.java
+++ b/src/main/java/org/cryptomator/linux/tray/AppindicatorTrayMenuController.java
@@ -102,6 +102,7 @@ private void addChildren(MemorySegment menu, List items) {
GCallback.allocate(new ActionItemCallback(a), ARENA),
menu,
0);
+ Gtk.widgetSetSensitive(gtkMenuItem, a.enabled());
Gtk.menuShellAppend(menu, gtkMenuItem);
}
case SeparatorItem _ -> {
diff --git a/src/main/resources/META-INF/services/org.cryptomator.integrations.autostart.AutoStartProvider b/src/main/resources/META-INF/services/org.cryptomator.integrations.autostart.AutoStartProvider
new file mode 100644
index 0000000..7b57cfb
--- /dev/null
+++ b/src/main/resources/META-INF/services/org.cryptomator.integrations.autostart.AutoStartProvider
@@ -0,0 +1 @@
+org.cryptomator.linux.autostart.FreedesktopAutoStartService
\ No newline at end of file
diff --git a/src/main/resources/META-INF/services/org.cryptomator.integrations.quickaccess.QuickAccessService b/src/main/resources/META-INF/services/org.cryptomator.integrations.quickaccess.QuickAccessService
new file mode 100644
index 0000000..35c597f
--- /dev/null
+++ b/src/main/resources/META-INF/services/org.cryptomator.integrations.quickaccess.QuickAccessService
@@ -0,0 +1,2 @@
+org.cryptomator.linux.quickaccess.NautilusBookmarks
+org.cryptomator.linux.quickaccess.DolphinPlaces
\ No newline at end of file
diff --git a/src/main/resources/xbel-1.0.dtd b/src/main/resources/xbel-1.0.dtd
new file mode 100644
index 0000000..59013c2
--- /dev/null
+++ b/src/main/resources/xbel-1.0.dtd
@@ -0,0 +1,94 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/xbel-1.0.xsd b/src/main/resources/xbel-1.0.xsd
new file mode 100644
index 0000000..12d1314
--- /dev/null
+++ b/src/main/resources/xbel-1.0.xsd
@@ -0,0 +1,143 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/test/java/org/cryptomator/linux/autostart/FreedesktopAutoStartIT.java b/src/test/java/org/cryptomator/linux/autostart/FreedesktopAutoStartIT.java
new file mode 100644
index 0000000..a89983f
--- /dev/null
+++ b/src/test/java/org/cryptomator/linux/autostart/FreedesktopAutoStartIT.java
@@ -0,0 +1,44 @@
+package org.cryptomator.linux.autostart;
+
+import org.cryptomator.integrations.autostart.AutoStartProvider;
+import org.cryptomator.integrations.common.IntegrationsLoader;
+import org.cryptomator.integrations.quickaccess.QuickAccessService;
+import org.cryptomator.linux.quickaccess.DolphinPlaces;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestMethodOrder;
+
+@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
+public class FreedesktopAutoStartIT {
+
+ FreedesktopAutoStartService inTest = new FreedesktopAutoStartService();
+
+ @Test
+ @DisplayName("If freedesktop dirs are present, isSupported returns true and service is contained in the service provider stream")
+ @Disabled
+ public void testSupport() {
+ Assertions.assertTrue(inTest.isSupported());
+
+ var optionalService = IntegrationsLoader.loadAll(AutoStartProvider.class).filter(s -> s.getClass().getName().equals("org.cryptomator.linux.autostart.FreedesktopAutoStartService")).findAny();
+ Assertions.assertTrue(optionalService.isPresent());
+ }
+
+ @Test
+ @Order(1)
+ public void testAutostartEnable() {
+ Assertions.assertDoesNotThrow(() -> inTest.enable());
+ Assertions.assertTrue(inTest.isEnabled());
+ }
+
+
+ @Test
+ @Order(2)
+ public void testAutostartDisable() {
+ Assertions.assertDoesNotThrow(() -> inTest.disable());
+ Assertions.assertFalse(inTest.isEnabled());
+ }
+}
diff --git a/src/test/java/org/cryptomator/linux/quickaccess/DolphinPlacesIT.java b/src/test/java/org/cryptomator/linux/quickaccess/DolphinPlacesIT.java
new file mode 100644
index 0000000..99242c6
--- /dev/null
+++ b/src/test/java/org/cryptomator/linux/quickaccess/DolphinPlacesIT.java
@@ -0,0 +1,35 @@
+package org.cryptomator.linux.quickaccess;
+
+import org.cryptomator.integrations.common.IntegrationsLoader;
+import org.cryptomator.integrations.quickaccess.QuickAccessService;
+import org.cryptomator.integrations.quickaccess.QuickAccessServiceException;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import java.nio.file.Path;
+import java.time.Duration;
+
+public class DolphinPlacesIT {
+
+ @Test
+ @DisplayName("If dolphin is installed, isSupported returns true and service is contained in the service provider stream")
+ @Disabled
+ public void testSupport() {
+ Assertions.assertTrue(DolphinPlaces.isSupported());
+
+ var optionalService = IntegrationsLoader.loadAll(QuickAccessService.class).filter(s -> s.getClass().getName().equals("org.cryptomator.linux.quickaccess.DolphinPlaces")).findAny();
+ Assertions.assertTrue(optionalService.isPresent());
+ }
+
+ @Test
+ @DisplayName("Adds for 20s an entry to the Dolphin sidebar")
+ @Disabled
+ public void testSidebarIntegration(@TempDir Path tmpdir) throws QuickAccessServiceException, InterruptedException {
+ var entry = new DolphinPlaces().add(tmpdir, "integrations-linux");
+ Thread.sleep(Duration.ofSeconds(20));
+ entry.remove();
+ }
+}
diff --git a/src/test/java/org/cryptomator/linux/quickaccess/DolphinPlacesTest.java b/src/test/java/org/cryptomator/linux/quickaccess/DolphinPlacesTest.java
new file mode 100644
index 0000000..0075374
--- /dev/null
+++ b/src/test/java/org/cryptomator/linux/quickaccess/DolphinPlacesTest.java
@@ -0,0 +1,14 @@
+package org.cryptomator.linux.quickaccess;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+public class DolphinPlacesTest {
+
+ @Test
+ @DisplayName("Class can be loaded and object instantiated")
+ public void testInit() {
+ Assertions.assertDoesNotThrow(DolphinPlaces::new);
+ }
+}
diff --git a/src/test/java/org/cryptomator/linux/quickaccess/NautilusBookmarksIT.java b/src/test/java/org/cryptomator/linux/quickaccess/NautilusBookmarksIT.java
new file mode 100644
index 0000000..9bb00dc
--- /dev/null
+++ b/src/test/java/org/cryptomator/linux/quickaccess/NautilusBookmarksIT.java
@@ -0,0 +1,35 @@
+package org.cryptomator.linux.quickaccess;
+
+import org.cryptomator.integrations.common.IntegrationsLoader;
+import org.cryptomator.integrations.quickaccess.QuickAccessService;
+import org.cryptomator.integrations.quickaccess.QuickAccessServiceException;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import java.nio.file.Path;
+import java.time.Duration;
+
+public class NautilusBookmarksIT {
+
+ @Test
+ @DisplayName("If nautilus is installed, isSupported returns true and service is contained in the service provider stream")
+ @Disabled
+ public void testSupport() {
+ Assertions.assertTrue(NautilusBookmarks.isSupported());
+
+ var optionalService = IntegrationsLoader.loadAll(QuickAccessService.class).filter(s -> s.getClass().getName().equals("org.cryptomator.linux.quickaccess.NautilusBookmarks")).findAny();
+ Assertions.assertTrue(optionalService.isPresent());
+ }
+
+ @Test
+ @DisplayName("Adds for 20s an entryto the Nautilus sidebar")
+ @Disabled
+ public void testSidebarIntegration(@TempDir Path tmpdir) throws QuickAccessServiceException, InterruptedException {
+ var entry = new NautilusBookmarks().add(tmpdir, "integrations-linux");
+ Thread.sleep(Duration.ofSeconds(20));
+ entry.remove();
+ }
+}