diff --git a/pom.xml b/pom.xml
index f7004f5..fba3c00 100644
--- a/pom.xml
+++ b/pom.xml
@@ -40,7 +40,7 @@
- 1.3.1
+ 1.4.0-beta2
2.0.1-alpha
1.4.0
2.0.13
diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java
index 9ec0149..727f498 100644
--- a/src/main/java/module-info.java
+++ b/src/main/java/module-info.java
@@ -1,8 +1,10 @@
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.keychain.KDEWalletKeychainAccess;
import org.cryptomator.linux.keychain.SecretServiceKeychainAccess;
+import org.cryptomator.linux.quickaccess.NautilusBookmarks;
import org.cryptomator.linux.revealpath.DBusSendRevealPathService;
import org.cryptomator.linux.tray.AppindicatorTrayMenuController;
@@ -17,6 +19,7 @@
provides KeychainAccessProvider with SecretServiceKeychainAccess, KDEWalletKeychainAccess;
provides RevealPathService with DBusSendRevealPathService;
provides TrayMenuController with AppindicatorTrayMenuController;
+ provides QuickAccessService with NautilusBookmarks;
opens org.cryptomator.linux.tray to org.cryptomator.integrations.api;
}
\ No newline at end of file
diff --git a/src/main/java/org/cryptomator/linux/quickaccess/NautilusBookmarks.java b/src/main/java/org/cryptomator/linux/quickaccess/NautilusBookmarks.java
new file mode 100644
index 0000000..8013e34
--- /dev/null
+++ b/src/main/java/org/cryptomator/linux/quickaccess/NautilusBookmarks.java
@@ -0,0 +1,97 @@
+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 java.io.IOException;
+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.concurrent.TimeUnit;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+@Priority(100)
+@CheckAvailability
+@OperatingSystem(OperatingSystem.Value.LINUX)
+@DisplayName("GNOME Nautilus Bookmarks")
+public class NautilusBookmarks implements QuickAccessService {
+
+ private static final int MAX_FILE_SIZE = 4096;
+ private static final Path BOOKMARKS_FILE = Path.of(System.getProperty("user.home"), ".config/gtk-3.0/bookmarks");
+ private static final Path TMP_FILE = BOOKMARKS_FILE.resolveSibling("bookmarks.cryptomator.tmp");
+ private static final Lock BOOKMARKS_LOCK = new ReentrantReadWriteLock().writeLock();
+
+ @Override
+ public QuickAccessService.QuickAccessEntry add(Path target, String displayName) throws QuickAccessServiceException {
+ String entryLine = "file://" + target.toAbsolutePath() + " " + displayName;
+ try {
+ BOOKMARKS_LOCK.lock();
+ if (Files.size(BOOKMARKS_FILE) > 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() {
+ try {
+ var nautilusExistsProc = new ProcessBuilder().command("test", "`command -v nautilus`").start();
+ if (nautilusExistsProc.waitFor(5000, TimeUnit.MILLISECONDS)) {
+ return nautilusExistsProc.exitValue() == 0;
+ }
+ } catch (IOException | InterruptedException e) {
+ //NO-OP
+ }
+ return false;
+ }
+}
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..d827498
--- /dev/null
+++ b/src/main/resources/META-INF/services/org.cryptomator.integrations.quickaccess.QuickAccessService
@@ -0,0 +1 @@
+org.cryptomator.linux.quickaccess.NautilusSidebarService
\ No newline at end of file
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..10e4654
--- /dev/null
+++ b/src/test/java/org/cryptomator/linux/quickaccess/NautilusBookmarksIT.java
@@ -0,0 +1,22 @@
+package org.cryptomator.linux.quickaccess;
+
+import org.cryptomator.integrations.quickaccess.QuickAccessServiceException;
+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("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();
+ }
+}