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/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/quickaccess/QuickAccessService.java b/src/main/java/org/cryptomator/integrations/quickaccess/QuickAccessService.java index cc6b1f3..bf084b2 100644 --- a/src/main/java/org/cryptomator/integrations/quickaccess/QuickAccessService.java +++ b/src/main/java/org/cryptomator/integrations/quickaccess/QuickAccessService.java @@ -1,6 +1,7 @@ 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; @@ -13,7 +14,7 @@ * @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 { +public interface QuickAccessService extends NamedServiceProvider { /** * Creates an entry in the quick access area.