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(); + } +}