From f2881b84b1150dd9624d811ff73d40c068a235d5 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Sat, 29 Jun 2024 01:15:07 +0200 Subject: [PATCH 1/4] first impl draft --- README.md | 2 +- src/main/java/module-info.java | 3 + .../FreedesktopAutoStartService.java | 83 +++++++++++++++++++ ...r.integrations.autostart.AutoStartProvider | 1 + .../autostart/FreeDesktopAutoStartIT.java | 28 +++++++ 5 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/cryptomator/linux/autostart/FreedesktopAutoStartService.java create mode 100644 src/main/resources/META-INF/services/org.cryptomator.integrations.autostart.AutoStartProvider create mode 100644 src/test/java/org/cryptomator/linux/autostart/FreeDesktopAutoStartIT.java diff --git a/README.md b/README.md index 9b3ecdf..fb57a47 100644 --- a/README.md +++ b/README.md @@ -5,4 +5,4 @@ Linux-specific implemenations of the [integrations-api](https://github.com/crypt This project uses the following JVM properties: * `cryptomator.integrationsLinux.trayIconsDir` - specifies the directory from which svg images for the tray icon are loaded - +* `cryptomator.integrationsLinux.autoStartCmd` - specifies the command used for starting Cryptomator \ No newline at end of file diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 9ec0149..dedd2b8 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -1,6 +1,8 @@ +import org.cryptomator.integrations.autostart.AutoStartProvider; import org.cryptomator.integrations.keychain.KeychainAccessProvider; 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.revealpath.DBusSendRevealPathService; @@ -14,6 +16,7 @@ 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; 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..331d4c2 --- /dev/null +++ b/src/main/java/org/cryptomator/linux/autostart/FreedesktopAutoStartService.java @@ -0,0 +1,83 @@ +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; + + 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); + if (execValue == null) { + LOG.debug("Property {} not set, using command path", CMD_PROPERTY); + execValue = ProcessHandle.current().info().command().orElse(""); + } + this.content = CONTENT_TEMPLATE.formatted(execValue, this.getClass().getName()); + } + + @Override + public 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 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 boolean isEnabled() { + return Files.exists(autostartFile); + } + + @CheckAvailability + public boolean isSupported() { + //TODO: might need to research which Desktop Environments support this + return Files.exists(autostartFile.getParent()); + } + +} 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/test/java/org/cryptomator/linux/autostart/FreeDesktopAutoStartIT.java b/src/test/java/org/cryptomator/linux/autostart/FreeDesktopAutoStartIT.java new file mode 100644 index 0000000..4c3da1a --- /dev/null +++ b/src/test/java/org/cryptomator/linux/autostart/FreeDesktopAutoStartIT.java @@ -0,0 +1,28 @@ +package org.cryptomator.linux.autostart; + +import org.junit.jupiter.api.Assertions; +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 + @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()); + } +} From c8956e2dceafea5ad0daf94232037c726186b424 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Tue, 16 Jul 2024 17:44:54 +0200 Subject: [PATCH 2/4] renamed test class --- ...{FreeDesktopAutoStartIT.java => FreedesktopAutoStartIT.java} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/test/java/org/cryptomator/linux/autostart/{FreeDesktopAutoStartIT.java => FreedesktopAutoStartIT.java} (94%) diff --git a/src/test/java/org/cryptomator/linux/autostart/FreeDesktopAutoStartIT.java b/src/test/java/org/cryptomator/linux/autostart/FreedesktopAutoStartIT.java similarity index 94% rename from src/test/java/org/cryptomator/linux/autostart/FreeDesktopAutoStartIT.java rename to src/test/java/org/cryptomator/linux/autostart/FreedesktopAutoStartIT.java index 4c3da1a..d593628 100644 --- a/src/test/java/org/cryptomator/linux/autostart/FreeDesktopAutoStartIT.java +++ b/src/test/java/org/cryptomator/linux/autostart/FreedesktopAutoStartIT.java @@ -7,7 +7,7 @@ import org.junit.jupiter.api.TestMethodOrder; @TestMethodOrder(MethodOrderer.OrderAnnotation.class) -public class FreeDesktopAutoStartIT { +public class FreedesktopAutoStartIT { FreedesktopAutoStartService inTest = new FreedesktopAutoStartService(); From 6ae79bbd880fb960cf95ecc43146649481a8ac54 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Tue, 16 Jul 2024 17:46:47 +0200 Subject: [PATCH 3/4] make service threadsafe (within a service instance) --- .../linux/autostart/FreedesktopAutoStartService.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/cryptomator/linux/autostart/FreedesktopAutoStartService.java b/src/main/java/org/cryptomator/linux/autostart/FreedesktopAutoStartService.java index 331d4c2..d50496a 100644 --- a/src/main/java/org/cryptomator/linux/autostart/FreedesktopAutoStartService.java +++ b/src/main/java/org/cryptomator/linux/autostart/FreedesktopAutoStartService.java @@ -52,7 +52,7 @@ public FreedesktopAutoStartService() { } @Override - public void enable() throws ToggleAutoStartFailedException { + public synchronized void enable() throws ToggleAutoStartFailedException { try { Files.writeString(autostartFile, content, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING); } catch (IOException e) { @@ -61,7 +61,7 @@ public void enable() throws ToggleAutoStartFailedException { } @Override - public void disable() throws ToggleAutoStartFailedException { + public synchronized void disable() throws ToggleAutoStartFailedException { try { Files.deleteIfExists(autostartFile); } catch (IOException e) { @@ -70,7 +70,7 @@ public void disable() throws ToggleAutoStartFailedException { } @Override - public boolean isEnabled() { + public synchronized boolean isEnabled() { return Files.exists(autostartFile); } From 2c0f834e91c0f49b7fe8b6181b6eddad342c79cc Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Tue, 16 Jul 2024 17:47:55 +0200 Subject: [PATCH 4/4] add additional sanity condition for isSupported method --- .../linux/autostart/FreedesktopAutoStartService.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/cryptomator/linux/autostart/FreedesktopAutoStartService.java b/src/main/java/org/cryptomator/linux/autostart/FreedesktopAutoStartService.java index d50496a..1c06d78 100644 --- a/src/main/java/org/cryptomator/linux/autostart/FreedesktopAutoStartService.java +++ b/src/main/java/org/cryptomator/linux/autostart/FreedesktopAutoStartService.java @@ -38,6 +38,7 @@ public class FreedesktopAutoStartService implements AutoStartProvider { 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"); @@ -45,9 +46,10 @@ public FreedesktopAutoStartService() { var execValue = System.getProperty(CMD_PROPERTY); if (execValue == null) { - LOG.debug("Property {} not set, using command path", CMD_PROPERTY); + LOG.debug("JVM property {} not set, using command path", CMD_PROPERTY); execValue = ProcessHandle.current().info().command().orElse(""); } + this.hasExecValue = execValue.isBlank(); this.content = CONTENT_TEMPLATE.formatted(execValue, this.getClass().getName()); } @@ -77,7 +79,7 @@ public synchronized boolean isEnabled() { @CheckAvailability public boolean isSupported() { //TODO: might need to research which Desktop Environments support this - return Files.exists(autostartFile.getParent()); + return hasExecValue && Files.exists(autostartFile.getParent()); } }