diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 794888a..cb6510f 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 @@ -24,9 +26,9 @@ jobs: name: artifacts path: target/*.jar - name: Create Release - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 if: startsWith(github.ref, 'refs/tags/') 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/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index debd498..efb0bc4 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -13,7 +13,8 @@ jobs: analyse: name: Analyse runs-on: ubuntu-latest - if: "!contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]')" + # dependeabot has on push events only read-only access, but codeql requires write access + if: ${{ !(github.actor == 'dependabot[bot]' && contains(fromJSON('["push"]'), github.event_name)) }} steps: - uses: actions/checkout@v4 with: @@ -30,4 +31,4 @@ jobs: - name: Build run: mvn -B compile - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 \ No newline at end of file + uses: github/codeql-action/analyze@v3 diff --git a/pom.xml b/pom.xml index 66a349f..958e1d8 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 org.cryptomator integrations-api - 1.3.1 + 1.4.0 Cryptomator Integrations API Defines optional service interfaces that may be used by Cryptomator @@ -41,7 +41,7 @@ org.slf4j slf4j-api - 2.0.12 + 2.0.13 @@ -53,19 +53,19 @@ org.slf4j slf4j-simple - 2.0.12 + 2.0.13 test org.junit.jupiter junit-jupiter - 5.10.2 + 5.10.3 test org.mockito mockito-core - 5.10.0 + 5.12.0 test @@ -75,14 +75,14 @@ org.apache.maven.plugins maven-compiler-plugin - 3.12.1 + 3.13.0 17 maven-source-plugin - 3.3.0 + 3.3.1 attach-sources @@ -95,11 +95,11 @@ org.apache.maven.plugins maven-surefire-plugin - 3.2.5 + 3.3.1 maven-javadoc-plugin - 3.6.3 + 3.8.0 attach-javadocs @@ -148,7 +148,7 @@ maven-gpg-plugin - 3.1.0 + 3.2.4 sign-artifacts @@ -183,7 +183,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 7108775..9925772 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -1,3 +1,4 @@ +import org.cryptomator.integrations.quickaccess.QuickAccessService; import org.cryptomator.integrations.mount.MountService; import org.cryptomator.integrations.revealpath.RevealPathService; import org.cryptomator.integrations.tray.TrayMenuController; @@ -18,6 +19,7 @@ exports org.cryptomator.integrations.revealpath; exports org.cryptomator.integrations.tray; exports org.cryptomator.integrations.uiappearance; + exports org.cryptomator.integrations.quickaccess; uses AutoStartProvider; uses KeychainAccessProvider; @@ -26,4 +28,5 @@ uses TrayIntegrationProvider; uses TrayMenuController; uses UiAppearanceProvider; + uses QuickAccessService; } \ No newline at end of file diff --git a/src/main/java/org/cryptomator/integrations/common/DisplayName.java b/src/main/java/org/cryptomator/integrations/common/DisplayName.java new file mode 100644 index 0000000..7389220 --- /dev/null +++ b/src/main/java/org/cryptomator/integrations/common/DisplayName.java @@ -0,0 +1,25 @@ +package org.cryptomator.integrations.common; + +import org.jetbrains.annotations.ApiStatus; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * A humanreadable name of the annotated class. + *

+ * Checked in the default implementation of the {@link NamedServiceProvider#getName()} with lower priority. + * + * @see NamedServiceProvider + * @see LocalizedDisplayName + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@ApiStatus.Experimental +public @interface DisplayName { + String value(); +} diff --git a/src/main/java/org/cryptomator/integrations/common/LocalizedDisplayName.java b/src/main/java/org/cryptomator/integrations/common/LocalizedDisplayName.java new file mode 100644 index 0000000..3a05603 --- /dev/null +++ b/src/main/java/org/cryptomator/integrations/common/LocalizedDisplayName.java @@ -0,0 +1,39 @@ +package org.cryptomator.integrations.common; + +import org.jetbrains.annotations.ApiStatus; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * A humanreadable, localized name of the annotated class. + *

+ * Checked in the default implementation of the {@link NamedServiceProvider#getName()} with highest priority. + * + * @see NamedServiceProvider + * @see DisplayName + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@ApiStatus.Experimental +public @interface LocalizedDisplayName { + + /** + * Name of the localization bundle, where the display name is loaded from. + * + * @return Name of the localization bundle + */ + String bundle(); + + /** + * The localization key containing the display name. + * + * @return Localization key to use + */ + String key(); + +} diff --git a/src/main/java/org/cryptomator/integrations/common/NamedServiceProvider.java b/src/main/java/org/cryptomator/integrations/common/NamedServiceProvider.java new file mode 100644 index 0000000..692192b --- /dev/null +++ b/src/main/java/org/cryptomator/integrations/common/NamedServiceProvider.java @@ -0,0 +1,41 @@ +package org.cryptomator.integrations.common; + +import org.slf4j.LoggerFactory; + +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +/** + * A service provider with a human-readable, possibly localized name. + */ +public interface NamedServiceProvider { + + /** + * Get the name of this service provider. + * + * @return The name of the service provider + * @implNote The default implementation looks first for a {@link LocalizedDisplayName} and loads the name from the specified resource bundle/key. If the annotation is not present or loading the resource throws an exception, the code looks for {@link DisplayName} and uses its value. If none of the former annotations are present, it falls back to the qualified class name. + * @see DisplayName + * @see LocalizedDisplayName + */ + default String getName() { + var localizedDisplayName = this.getClass().getAnnotation(LocalizedDisplayName.class); + if (localizedDisplayName != null) { + try { + return ResourceBundle.getBundle(localizedDisplayName.bundle()) // + .getString(localizedDisplayName.key()); + } catch (MissingResourceException e) { + var clazz = this.getClass(); + var logger = LoggerFactory.getLogger(clazz); + logger.warn("Failed to load localized display name for {}. Falling back to not-localized display name/class name.", clazz.getName(), e); + } + } + + var displayName = this.getClass().getAnnotation(DisplayName.class); + if (displayName != null) { + return displayName.value(); + } else { + return this.getClass().getName(); + } + } +} diff --git a/src/main/java/org/cryptomator/integrations/keychain/KeychainAccessProvider.java b/src/main/java/org/cryptomator/integrations/keychain/KeychainAccessProvider.java index 3099528..979e38c 100644 --- a/src/main/java/org/cryptomator/integrations/keychain/KeychainAccessProvider.java +++ b/src/main/java/org/cryptomator/integrations/keychain/KeychainAccessProvider.java @@ -56,7 +56,24 @@ default void storePassphrase(String key, CharSequence passphrase) throws Keychai * @throws KeychainAccessException If storing the password failed */ @Blocking - void storePassphrase(String key, @Nullable String displayName, CharSequence passphrase) throws KeychainAccessException; + default void storePassphrase(String key, @Nullable String displayName, CharSequence passphrase) throws KeychainAccessException { + storePassphrase(key, displayName, passphrase, false); + } + + /** + * Associates a passphrase with a given key and a name for that key. + * + * @param key Key used to retrieve the passphrase via {@link #loadPassphrase(String)}. + * @param displayName The according name to the key. That's the name of the vault displayed in the UI. + * It's passed to the keychain as an additional information about the vault besides the key. + * The parameter does not need to be unique or be checked by the keychain. + * @param passphrase The secret to store in this keychain. + * @param requireOsAuthentication Defines, whether the user needs to authenticate to store a passphrase. + * The authentication mechanism is provided by the operating system dependent + * implementations of this API. + * @throws KeychainAccessException If storing the password failed + */ + void storePassphrase(String key, @Nullable String displayName, CharSequence passphrase, boolean requireOsAuthentication) throws KeychainAccessException; /** * @param key Unique key previously used while {@link #storePassphrase(String, String, CharSequence)} storing a passphrase}. diff --git a/src/main/java/org/cryptomator/integrations/quickaccess/QuickAccessService.java b/src/main/java/org/cryptomator/integrations/quickaccess/QuickAccessService.java new file mode 100644 index 0000000..bf084b2 --- /dev/null +++ b/src/main/java/org/cryptomator/integrations/quickaccess/QuickAccessService.java @@ -0,0 +1,56 @@ +package org.cryptomator.integrations.quickaccess; + +import org.cryptomator.integrations.common.IntegrationsLoader; +import org.cryptomator.integrations.common.NamedServiceProvider; +import org.jetbrains.annotations.Blocking; +import org.jetbrains.annotations.NotNull; + +import java.nio.file.Path; +import java.util.stream.Stream; + +/** + * Service adding a system path link to a quick access area of the OS or an application (e.g. file manager). + * + * @apiNote On purpose this service does not define, what an "link to a quick access area" is. The defintion depends on the OS. For example, the quick access area can be the home screen/desktop and the link would be an icon leading to the linked path. + */ +@FunctionalInterface +public interface QuickAccessService extends NamedServiceProvider { + + /** + * Creates an entry in the quick access area. + * + * @param target The filesystem path the quick access entry points to + * @param displayName The display name of the quick access entry + * @return a {@link QuickAccessEntry }, used to remove the entry again + * @throws QuickAccessServiceException if adding an entry to the quick access area fails + * @apiNote It depends on the service implementation wether the display name is used or not. + */ + @Blocking + QuickAccessEntry add(@NotNull Path target, @NotNull String displayName) throws QuickAccessServiceException; + + /** + * An entry of the quick access area, created by a service implementation. + */ + @FunctionalInterface + interface QuickAccessEntry { + + /** + * Removes this entry from the quick access area. + * + * @throws QuickAccessServiceException if removal fails. + * @implSpec Service implementations should make this function idempotent, i.e. after the method is called once and succeeded, consecutive calls should not change anything or throw an error. + */ + @Blocking + void remove() throws QuickAccessServiceException; + + } + + /** + * Loads all supported service providers. + * + * @return Stream of supported {@link QuickAccessService} implementations (may be empty) + */ + static Stream get() { + return IntegrationsLoader.loadAll(QuickAccessService.class); + } +} diff --git a/src/main/java/org/cryptomator/integrations/quickaccess/QuickAccessServiceException.java b/src/main/java/org/cryptomator/integrations/quickaccess/QuickAccessServiceException.java new file mode 100644 index 0000000..38612f0 --- /dev/null +++ b/src/main/java/org/cryptomator/integrations/quickaccess/QuickAccessServiceException.java @@ -0,0 +1,12 @@ +package org.cryptomator.integrations.quickaccess; + +public class QuickAccessServiceException extends Exception { + + public QuickAccessServiceException(String message) { + super(message); + } + + public QuickAccessServiceException(String message, Throwable t) { + super(message, t); + } +}