diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8b6dc80..835a5e0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,6 +1,8 @@ name: Build on: - [push] + push: + pull_request_target: + types: [labeled] jobs: build: name: Build and Test @@ -31,4 +33,4 @@ jobs: with: prerelease: true token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }} - generate_release_notes: true \ No newline at end of file + generate_release_notes: true diff --git a/.github/workflows/publish-central.yml b/.github/workflows/publish-central.yml index 746b083..96472cd 100644 --- a/.github/workflows/publish-central.yml +++ b/.github/workflows/publish-central.yml @@ -21,8 +21,6 @@ jobs: server-id: ossrh # Value of the distributionManagement/repository/id field of the pom.xml server-username: MAVEN_USERNAME # env variable for username in deploy server-password: MAVEN_PASSWORD # env variable for token in deploy - gpg-private-key: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} # Value of the GPG private key to import - gpg-passphrase: MAVEN_GPG_PASSPHRASE # env variable for GPG private key passphrase - name: Enforce project version ${{ github.event.inputs.tag }} run: mvn versions:set -B -DnewVersion=${{ github.event.inputs.tag }} - name: Deploy @@ -35,4 +33,5 @@ jobs: --add-opens=java.desktop/java.awt.font=ALL-UNNAMED MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }} MAVEN_PASSWORD: ${{ secrets.OSSRH_PASSWORD }} - MAVEN_GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }} \ No newline at end of file + MAVEN_GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }} + MAVEN_GPG_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} \ No newline at end of file diff --git a/.github/workflows/publish-github.yml b/.github/workflows/publish-github.yml index 56ef8f0..c29d40c 100644 --- a/.github/workflows/publish-github.yml +++ b/.github/workflows/publish-github.yml @@ -13,8 +13,6 @@ jobs: distribution: 'zulu' java-version: 22 cache: 'maven' - gpg-private-key: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} # Value of the GPG private key to import - gpg-passphrase: MAVEN_GPG_PASSPHRASE # env variable for GPG private key passphrase - name: Enforce project version ${{ github.event.release.tag_name }} run: mvn versions:set -B -DnewVersion=${{ github.event.release.tag_name }} - name: Deploy @@ -22,6 +20,7 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} MAVEN_GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }} + MAVEN_GPG_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} - name: Slack Notification uses: rtCamp/action-slack-notify@v2 env: diff --git a/README.md b/README.md index 9b3ecdf..8f216e3 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,11 @@ Linux-specific implemenations of the [integrations-api](https://github.com/crypt # Config -This project uses the following JVM properties: +This library 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 +## Building Requirements + +* JDK 22 +* Maven 3.9.6 diff --git a/pom.xml b/pom.xml index c0a9068..3d7a0e1 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 org.cryptomator integrations-linux - 1.4.5 + 1.5.0 integrations-linux Provides optional Linux services used by Cryptomator @@ -40,17 +40,17 @@ - 1.3.1 + 1.4.0 2.0.1-alpha 1.4.0 - 2.0.13 - 1.4.0 + 2.0.16 + 1.4.1 - 5.10.2 + 5.10.3 - 9.1.0 + 10.0.3 1.6.8 @@ -87,6 +87,12 @@ ${junit.version} test + + org.slf4j + slf4j-simple + ${slf4j.version} + test + @@ -102,12 +108,12 @@ org.apache.maven.plugins maven-surefire-plugin - 3.2.5 + 3.3.1 org.apache.maven.plugins maven-enforcer-plugin - 3.4.1 + 3.5.0 check-preconditions @@ -140,7 +146,7 @@ maven-javadoc-plugin - 3.6.3 + 3.8.0 attach-javadocs @@ -242,10 +248,7 @@ sign - - --pinentry-mode - loopback - + bc @@ -268,7 +271,7 @@ org.sonatype.plugins nexus-staging-maven-plugin - 1.6.13 + 1.7.0 true 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(); + } +}