Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrated Service Loading #7

Merged
merged 4 commits into from
Jan 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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