+ * 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