Skip to content

Commit

Permalink
Merge pull request #7 from cryptomator/feature/service-loading
Browse files Browse the repository at this point in the history
Integrated Service Loading
  • Loading branch information
overheadhunter authored Jan 20, 2022
2 parents a7cdfc8 + 6263463 commit d575f5d
Show file tree
Hide file tree
Showing 8 changed files with 165 additions and 0 deletions.
10 changes: 10 additions & 0 deletions src/main/java/module-info.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
import org.cryptomator.integrations.autostart.AutoStartProvider;
import org.cryptomator.integrations.keychain.KeychainAccessProvider;
import org.cryptomator.integrations.tray.TrayIntegrationProvider;
import org.cryptomator.integrations.uiappearance.UiAppearanceProvider;

module org.cryptomator.integrations.api {
exports org.cryptomator.integrations.autostart;
exports org.cryptomator.integrations.keychain;
exports org.cryptomator.integrations.tray;
exports org.cryptomator.integrations.uiappearance;

uses AutoStartProvider;
uses KeychainAccessProvider;
uses TrayIntegrationProvider;
uses UiAppearanceProvider;
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
package org.cryptomator.integrations.autostart;

import org.cryptomator.integrations.common.IntegrationsLoader;

import java.util.Optional;

public interface AutoStartProvider {

static Optional<AutoStartProvider> get() {
return IntegrationsLoader.load(AutoStartProvider.class);
}

void enable() throws ToggleAutoStartFailedException;

void disable() throws ToggleAutoStartFailedException;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package org.cryptomator.integrations.common;

import java.util.Arrays;
import java.util.Comparator;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.stream.Stream;

public class IntegrationsLoader {

/**
* Loads the best suited service, i.e. the one with the highest priority that is supported.
* <p>
* If two services are available with the same priority, it is unspecified which one will be returned.
*
* @param clazz Service class
* @param <T> Type of the service
* @return Highest priority service or empty if no supported service was found
*/
public static <T> Optional<T> load(Class<T> clazz) {
return loadAll(clazz).findFirst();
}

/**
* Loads all suited services ordered by priority in descending order.
*
* @param clazz Service class
* @param <T> Type of the service
* @return An ordered stream of all suited service candidates
*/
public static <T> Stream<T> loadAll(Class<T> clazz) {
return ServiceLoader.load(clazz)
.stream()
.filter(IntegrationsLoader::isSupportedOperatingSystem)
.sorted(Comparator.comparingInt(IntegrationsLoader::getPriority).reversed())
.map(ServiceLoader.Provider::get);
}

private static int getPriority(ServiceLoader.Provider<?> provider) {
var prio = provider.type().getAnnotation(Priority.class);
return prio == null ? Priority.DEFAULT : prio.value();
}

private static boolean isSupportedOperatingSystem(ServiceLoader.Provider<?> provider) {
var annotations = provider.type().getAnnotationsByType(OperatingSystem.class);
return annotations.length == 0 || Arrays.stream(annotations).anyMatch(OperatingSystem.Value::isCurrent);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package org.cryptomator.integrations.common;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Restricts the annotated integration provider to one or more operating system(s).
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Repeatable(OperatingSystem.OperatingSystems.class)
public @interface OperatingSystem {
Value value() default Value.UNKNOWN;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@interface OperatingSystems {
OperatingSystem[] value();
}

enum Value {
LINUX,
MAC,
WINDOWS,
UNKNOWN;

private static final String OS_NAME = System.getProperty("os.name", "").toLowerCase();

public static Value current() {
if (OS_NAME.contains("linux")) {
return LINUX;
} else if (OS_NAME.contains("mac")) {
return MAC;
} else if (OS_NAME.contains("windows")) {
return WINDOWS;
} else {
return UNKNOWN;
}
}

public static boolean isCurrent(OperatingSystem os) {
return current().equals(os.value());
}
}
}
23 changes: 23 additions & 0 deletions src/main/java/org/cryptomator/integrations/common/Priority.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.cryptomator.integrations.common;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Integration Priority.
* <p>
* If multiple implementations for an integration can be provided, the provider with the highest priority will be used.
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface Priority {
int DEFAULT = 0;
int FALLBACK = Integer.MIN_VALUE;

int value() default DEFAULT;
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
package org.cryptomator.integrations.keychain;

import org.cryptomator.integrations.common.IntegrationsLoader;

import java.util.stream.Stream;

/**
* This is the interface used by Cryptomator to store passwords securely in external keychains, such as system keychains or password managers.
*/
public interface KeychainAccessProvider {

static Stream<KeychainAccessProvider> get() {
return IntegrationsLoader.loadAll(KeychainAccessProvider.class).filter(KeychainAccessProvider::isSupported);
}

/**
* A name to display in UI elements. If required, this should be localized.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
package org.cryptomator.integrations.tray;

import org.cryptomator.integrations.common.IntegrationsLoader;

import java.util.Optional;

public interface TrayIntegrationProvider {

static Optional<TrayIntegrationProvider> get() {
return IntegrationsLoader.load(TrayIntegrationProvider.class);
}

/**
* Performs tasks required when the application is no longer showing any window and only accessible via
* system tray (or comparable facilities).
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
package org.cryptomator.integrations.uiappearance;

import org.cryptomator.integrations.common.IntegrationsLoader;

import java.util.Optional;

/**
* This is the interface used by Cryptomator to get os specific UI appearances and themes.
*/
public interface UiAppearanceProvider {

static Optional<UiAppearanceProvider> get() {
return IntegrationsLoader.load(UiAppearanceProvider.class);
}

/**
* Gets the best-matching theme for the OS's current L&amp;F. This might be an approximation, as the OS might support more variations than we do.
*
Expand Down

0 comments on commit d575f5d

Please sign in to comment.