mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-30 08:26:19 +00:00
Merge branch 'develop' into feature/modular
# Conflicts: # pom.xml # src/main/resources/license/THIRD-PARTY.txt
This commit is contained in:
@@ -23,11 +23,10 @@ module org.cryptomator.desktop {
|
||||
requires org.slf4j;
|
||||
requires org.apache.commons.lang3;
|
||||
requires dagger;
|
||||
requires com.auth0.jwt;
|
||||
|
||||
/* TODO: filename-based modules: */
|
||||
requires static javax.inject; /* ugly dagger/guava crap */
|
||||
requires java.jwt;
|
||||
requires com.fasterxml.jackson.databind; // TODO: tmp fix, waiting for https://github.com/auth0/java-jwt/pull/484
|
||||
requires logback.classic;
|
||||
requires logback.core;
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ public abstract class CommonsModule {
|
||||
private static final int NUM_CORE_BG_THREADS = 6;
|
||||
private static final long BG_THREAD_KEEPALIVE_SECONDS = 60l;
|
||||
|
||||
@SuppressWarnings("SpellCheckingInspection")
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named("licensePublicKey")
|
||||
|
||||
@@ -123,8 +123,8 @@ public class Environment {
|
||||
return Stream.empty();
|
||||
} else {
|
||||
Iterable<String> iter = Splitter.on(separator).split(value);
|
||||
Spliterator<String> spliter = Spliterators.spliteratorUnknownSize(iter.iterator(), Spliterator.ORDERED | Spliterator.IMMUTABLE);
|
||||
return StreamSupport.stream(spliter, false);
|
||||
Spliterator<String> spliterator = Spliterators.spliteratorUnknownSize(iter.iterator(), Spliterator.ORDERED | Spliterator.IMMUTABLE);
|
||||
return StreamSupport.stream(spliterator, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,17 +60,17 @@ public class SemVerComparator implements Comparator<String> {
|
||||
final int commonCompCount = Math.min(vComps1.length, vComps2.length);
|
||||
|
||||
for (int i = 0; i < commonCompCount; i++) {
|
||||
int subversionComparisionResult = 0;
|
||||
int subversionComparisonResult = 0;
|
||||
try {
|
||||
final int v1 = Integer.parseInt(vComps1[i]);
|
||||
final int v2 = Integer.parseInt(vComps2[i]);
|
||||
subversionComparisionResult = v1 - v2;
|
||||
subversionComparisonResult = v1 - v2;
|
||||
} catch (NumberFormatException ex) {
|
||||
// ok, lets compare this fragment lexicographically
|
||||
subversionComparisionResult = vComps1[i].compareTo(vComps2[i]);
|
||||
subversionComparisonResult = vComps1[i].compareTo(vComps2[i]);
|
||||
}
|
||||
if (subversionComparisionResult != 0) {
|
||||
return subversionComparisionResult;
|
||||
if (subversionComparisonResult != 0) {
|
||||
return subversionComparisonResult;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ public class Settings {
|
||||
public static final int MIN_PORT = 1024;
|
||||
public static final int MAX_PORT = 65535;
|
||||
public static final boolean DEFAULT_ASKED_FOR_UPDATE_CHECK = false;
|
||||
public static final boolean DEFAULT_CHECK_FOR_UDPATES = false;
|
||||
public static final boolean DEFAULT_CHECK_FOR_UPDATES = false;
|
||||
public static final boolean DEFAULT_START_HIDDEN = false;
|
||||
public static final int DEFAULT_PORT = 42427;
|
||||
public static final int DEFAULT_NUM_TRAY_NOTIFICATIONS = 3;
|
||||
@@ -46,7 +46,7 @@ public class Settings {
|
||||
|
||||
private final ObservableList<VaultSettings> directories = FXCollections.observableArrayList(VaultSettings::observables);
|
||||
private final BooleanProperty askedForUpdateCheck = new SimpleBooleanProperty(DEFAULT_ASKED_FOR_UPDATE_CHECK);
|
||||
private final BooleanProperty checkForUpdates = new SimpleBooleanProperty(DEFAULT_CHECK_FOR_UDPATES);
|
||||
private final BooleanProperty checkForUpdates = new SimpleBooleanProperty(DEFAULT_CHECK_FOR_UPDATES);
|
||||
private final BooleanProperty startHidden = new SimpleBooleanProperty(DEFAULT_START_HIDDEN);
|
||||
private final IntegerProperty port = new SimpleIntegerProperty(DEFAULT_PORT);
|
||||
private final IntegerProperty numTrayNotifications = new SimpleIntegerProperty(DEFAULT_NUM_TRAY_NOTIFICATIONS);
|
||||
|
||||
@@ -101,7 +101,7 @@ public class SettingsProvider implements Supplier<Settings> {
|
||||
if (settings == null) {
|
||||
return;
|
||||
}
|
||||
final Optional<Path> settingsPath = env.getSettingsPath().findFirst(); // alway save to preferred (first) path
|
||||
final Optional<Path> settingsPath = env.getSettingsPath().findFirst(); // always save to preferred (first) path
|
||||
settingsPath.ifPresent(path -> {
|
||||
Runnable saveCommand = () -> this.save(settings, path);
|
||||
ScheduledFuture<?> scheduledTask = scheduler.schedule(saveCommand, SAVE_DELAY_MS, TimeUnit.MILLISECONDS);
|
||||
|
||||
@@ -31,7 +31,7 @@ import java.util.Random;
|
||||
public class VaultSettings {
|
||||
|
||||
public static final boolean DEFAULT_UNLOCK_AFTER_STARTUP = false;
|
||||
public static final boolean DEFAULT_REAVEAL_AFTER_MOUNT = true;
|
||||
public static final boolean DEFAULT_REVEAL_AFTER_MOUNT = true;
|
||||
public static final boolean DEFAULT_USES_INDIVIDUAL_MOUNTPATH = false;
|
||||
public static final boolean DEFAULT_USES_READONLY_MODE = false;
|
||||
public static final String DEFAULT_MOUNT_FLAGS = "";
|
||||
@@ -47,7 +47,7 @@ public class VaultSettings {
|
||||
private final StringProperty displayName = new SimpleStringProperty();
|
||||
private final StringProperty winDriveLetter = new SimpleStringProperty();
|
||||
private final BooleanProperty unlockAfterStartup = new SimpleBooleanProperty(DEFAULT_UNLOCK_AFTER_STARTUP);
|
||||
private final BooleanProperty revealAfterMount = new SimpleBooleanProperty(DEFAULT_REAVEAL_AFTER_MOUNT);
|
||||
private final BooleanProperty revealAfterMount = new SimpleBooleanProperty(DEFAULT_REVEAL_AFTER_MOUNT);
|
||||
private final BooleanProperty useCustomMountPath = new SimpleBooleanProperty(DEFAULT_USES_INDIVIDUAL_MOUNTPATH);
|
||||
private final StringProperty customMountPath = new SimpleStringProperty();
|
||||
private final BooleanProperty usesReadOnlyMode = new SimpleBooleanProperty(DEFAULT_USES_READONLY_MODE);
|
||||
|
||||
@@ -44,7 +44,7 @@ class VaultSettingsJsonAdapter {
|
||||
String customMountPath = null;
|
||||
String winDriveLetter = null;
|
||||
boolean unlockAfterStartup = VaultSettings.DEFAULT_UNLOCK_AFTER_STARTUP;
|
||||
boolean revealAfterMount = VaultSettings.DEFAULT_REAVEAL_AFTER_MOUNT;
|
||||
boolean revealAfterMount = VaultSettings.DEFAULT_REVEAL_AFTER_MOUNT;
|
||||
boolean useCustomMountPath = VaultSettings.DEFAULT_USES_INDIVIDUAL_MOUNTPATH;
|
||||
boolean usesReadOnlyMode = VaultSettings.DEFAULT_USES_READONLY_MODE;
|
||||
String mountFlags = VaultSettings.DEFAULT_MOUNT_FLAGS;
|
||||
|
||||
@@ -138,7 +138,7 @@ public class VaultModule {
|
||||
|
||||
// see https://github.com/billziss-gh/winfsp/blob/5d0b10d0b643652c00ebb4704dc2bb28e7244973/src/dll/fuse/fuse_main.c#L53-L62 for syntax guide
|
||||
// see https://github.com/billziss-gh/winfsp/blob/5d0b10d0b643652c00ebb4704dc2bb28e7244973/src/dll/fuse/fuse.c#L295-L319 for options (-o <...>)
|
||||
// see https://github.com/billziss-gh/winfsp/wiki/Frequently-Asked-Questions/5ba00e4be4f5e938eaae6ef1500b331de12dee77 (FUSE 4.) on why the given defaults were choosen
|
||||
// see https://github.com/billziss-gh/winfsp/wiki/Frequently-Asked-Questions/5ba00e4be4f5e938eaae6ef1500b331de12dee77 (FUSE 4.) on why the given defaults were chosen
|
||||
private String getWindowsFuseDefaultMountFlags(StringBinding mountName, ReadOnlyBooleanProperty readOnly) {
|
||||
assert SystemUtils.IS_OS_WINDOWS;
|
||||
StringBuilder flags = new StringBuilder();
|
||||
|
||||
@@ -46,7 +46,7 @@ public class VaultState extends ObservableValueBase<VaultState.Value> implements
|
||||
UNLOCKED,
|
||||
|
||||
/**
|
||||
* Unknown state due to preceeding unrecoverable exceptions.
|
||||
* Unknown state due to preceding unrecoverable exceptions.
|
||||
*/
|
||||
ERROR;
|
||||
}
|
||||
|
||||
@@ -35,8 +35,8 @@ public class VaultStats {
|
||||
private final LongProperty bytesPerSecondEncrypted = new SimpleLongProperty();
|
||||
private final LongProperty bytesPerSecondDecrypted = new SimpleLongProperty();
|
||||
private final DoubleProperty cacheHitRate = new SimpleDoubleProperty();
|
||||
private final LongProperty toalBytesRead = new SimpleLongProperty();
|
||||
private final LongProperty toalBytesWritten = new SimpleLongProperty();
|
||||
private final LongProperty totalBytesRead = new SimpleLongProperty();
|
||||
private final LongProperty totalBytesWritten = new SimpleLongProperty();
|
||||
private final LongProperty totalBytesEncrypted = new SimpleLongProperty();
|
||||
private final LongProperty totalBytesDecrypted = new SimpleLongProperty();
|
||||
private final LongProperty filesRead = new SimpleLongProperty();
|
||||
@@ -75,8 +75,8 @@ public class VaultStats {
|
||||
cacheHitRate.set(stats.map(this::getCacheHitRate).orElse(0.0));
|
||||
bytesPerSecondDecrypted.set(stats.map(CryptoFileSystemStats::pollBytesDecrypted).orElse(0L));
|
||||
bytesPerSecondEncrypted.set(stats.map(CryptoFileSystemStats::pollBytesEncrypted).orElse(0L));
|
||||
toalBytesRead.set(stats.map(CryptoFileSystemStats::pollTotalBytesRead).orElse(0L));
|
||||
toalBytesWritten.set(stats.map(CryptoFileSystemStats::pollTotalBytesWritten).orElse(0L));
|
||||
totalBytesRead.set(stats.map(CryptoFileSystemStats::pollTotalBytesRead).orElse(0L));
|
||||
totalBytesWritten.set(stats.map(CryptoFileSystemStats::pollTotalBytesWritten).orElse(0L));
|
||||
totalBytesEncrypted.set(stats.map(CryptoFileSystemStats::pollTotalBytesEncrypted).orElse(0L));
|
||||
totalBytesDecrypted.set(stats.map(CryptoFileSystemStats::pollTotalBytesDecrypted).orElse(0L));
|
||||
var oldAccessCount = filesRead.get() + filesWritten.get();
|
||||
@@ -146,7 +146,7 @@ public class VaultStats {
|
||||
return bytesPerSecondEncrypted;
|
||||
}
|
||||
|
||||
public long getBytesPerSecondEnrypted() {
|
||||
public long getBytesPerSecondEncrypted() {
|
||||
return bytesPerSecondEncrypted.get();
|
||||
}
|
||||
|
||||
@@ -164,13 +164,13 @@ public class VaultStats {
|
||||
return cacheHitRate.get();
|
||||
}
|
||||
|
||||
public LongProperty toalBytesReadProperty() {return toalBytesRead;}
|
||||
public LongProperty totalBytesReadProperty() {return totalBytesRead;}
|
||||
|
||||
public long getTotalBytesRead() { return toalBytesRead.get();}
|
||||
public long getTotalBytesRead() { return totalBytesRead.get();}
|
||||
|
||||
public LongProperty toalBytesWrittenProperty() {return toalBytesWritten;}
|
||||
public LongProperty totalBytesWrittenProperty() {return totalBytesWritten;}
|
||||
|
||||
public long getTotalBytesWritten() { return toalBytesWritten.get();}
|
||||
public long getTotalBytesWritten() { return totalBytesWritten.get();}
|
||||
|
||||
public LongProperty totalBytesEncryptedProperty() {return totalBytesEncrypted;}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import java.util.function.Consumer;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Takes a Volume and usess it to mount an unlocked vault
|
||||
* Takes a Volume and uses it to mount an unlocked vault
|
||||
*/
|
||||
public interface Volume {
|
||||
|
||||
@@ -24,7 +24,7 @@ public interface Volume {
|
||||
boolean isSupported();
|
||||
|
||||
/**
|
||||
* Gets the coresponding enum type of the {@link VolumeImpl volume implementation ("VolumeImpl")} that is implemented by this Volume.
|
||||
* Gets the corresponding enum type of the {@link VolumeImpl volume implementation ("VolumeImpl")} that is implemented by this Volume.
|
||||
*
|
||||
* @return the type of implementation as defined by the {@link VolumeImpl VolumeImpl enum}
|
||||
*/
|
||||
|
||||
@@ -67,7 +67,7 @@ public class WebDavVolume implements Volume {
|
||||
throw new IllegalStateException("Mounting requires unlocked WebDAV servlet.");
|
||||
}
|
||||
|
||||
//on windows, prevent an automatic drive letter selection in the upstream library. Either we choose already a specifc one or there is no free.
|
||||
//on windows, prevent an automatic drive letter selection in the upstream library. Either we choose already a specific one or there is no free.
|
||||
Supplier<String> driveLetterSupplier;
|
||||
if (System.getProperty("os.name").toLowerCase().contains("windows") && vaultSettings.winDriveLetter().isEmpty().get()) {
|
||||
driveLetterSupplier = () -> windowsDriveLetters.getAvailableDriveLetter().orElse(null);
|
||||
|
||||
@@ -11,12 +11,12 @@ import java.io.File;
|
||||
*
|
||||
* @param <E> Event type the policy possibly reacts to
|
||||
*/
|
||||
public class LaunchAndSizeBasedTriggerinPolicy<E> extends TriggeringPolicyBase<E> {
|
||||
public class LaunchAndSizeBasedTriggeringPolicy<E> extends TriggeringPolicyBase<E> {
|
||||
|
||||
LaunchBasedTriggeringPolicy<E> launchBasedTriggeringPolicy;
|
||||
SizeBasedTriggeringPolicy<E> sizeBasedTriggeringPolicy;
|
||||
|
||||
public LaunchAndSizeBasedTriggerinPolicy(FileSize threshold) {
|
||||
public LaunchAndSizeBasedTriggeringPolicy(FileSize threshold) {
|
||||
this.launchBasedTriggeringPolicy = new LaunchBasedTriggeringPolicy<>();
|
||||
this.sizeBasedTriggeringPolicy = new SizeBasedTriggeringPolicy<>();
|
||||
sizeBasedTriggeringPolicy.setMaxFileSize(threshold);
|
||||
@@ -85,7 +85,7 @@ public class LoggerModule {
|
||||
appender.setContext(context);
|
||||
appender.setFile(logDir.resolve(LOGFILE_NAME).toString());
|
||||
appender.setEncoder(encoder);
|
||||
LaunchAndSizeBasedTriggerinPolicy triggeringPolicy = new LaunchAndSizeBasedTriggerinPolicy(FileSize.valueOf(LOG_MAX_SIZE));
|
||||
LaunchAndSizeBasedTriggeringPolicy triggeringPolicy = new LaunchAndSizeBasedTriggeringPolicy(FileSize.valueOf(LOG_MAX_SIZE));
|
||||
triggeringPolicy.setContext(context);
|
||||
triggeringPolicy.start();
|
||||
appender.setTriggeringPolicy(triggeringPolicy);
|
||||
|
||||
@@ -62,8 +62,8 @@ public class FxmlLoaderFactory {
|
||||
}
|
||||
Parent root = loader.getRoot();
|
||||
// TODO: discuss if we can remove language-specific stylesheets
|
||||
// List<String> addtionalStyleSheets = Splitter.on(',').omitEmptyStrings().splitToList(resourceBundle.getString("additionalStyleSheets"));
|
||||
// addtionalStyleSheets.forEach(styleSheet -> root.getStylesheets().add("/css/" + styleSheet));
|
||||
// List<String> additionalStyleSheets = Splitter.on(',').omitEmptyStrings().splitToList(resourceBundle.getString("additionalStyleSheets"));
|
||||
// additionalStyleSheets.forEach(styleSheet -> root.getStylesheets().add("/css/" + styleSheet));
|
||||
return sceneFactory.apply(root);
|
||||
}
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ public final class WeakBindings {
|
||||
* @param observable The observable
|
||||
* @return a IntegerBinding weakly referenced from the given observable
|
||||
*/
|
||||
public static IntegerBinding bindInterger(ObservableValue<Number> observable) {
|
||||
public static IntegerBinding bindInteger(ObservableValue<Number> observable) {
|
||||
return new IntegerBinding() {
|
||||
{
|
||||
bind(observable);
|
||||
|
||||
@@ -12,8 +12,8 @@ import javafx.scene.layout.StackPane;
|
||||
public class NiceSecurePasswordField extends StackPane {
|
||||
|
||||
private static final String STYLE_CLASS = "nice-secure-password-field";
|
||||
private static final String ICONS_STLYE_CLASS = "icons";
|
||||
private static final String REVEAL_BUTTON_STLYE_CLASS = "reveal-button";
|
||||
private static final String ICONS_STYLE_CLASS = "icons";
|
||||
private static final String REVEAL_BUTTON_STYLE_CLASS = "reveal-button";
|
||||
private static final int ICON_SPACING = 6;
|
||||
private static final double ICON_SIZE = 14.0;
|
||||
|
||||
@@ -30,7 +30,7 @@ public class NiceSecurePasswordField extends StackPane {
|
||||
iconContainer.setAlignment(Pos.CENTER_RIGHT);
|
||||
iconContainer.setMaxWidth(Double.NEGATIVE_INFINITY);
|
||||
iconContainer.setPrefWidth(42); // TODO
|
||||
iconContainer.getStyleClass().add(ICONS_STLYE_CLASS);
|
||||
iconContainer.getStyleClass().add(ICONS_STYLE_CLASS);
|
||||
StackPane.setAlignment(iconContainer, Pos.CENTER_RIGHT);
|
||||
|
||||
capsLockedIcon.setGlyph(FontAwesome5Icon.ARROW_UP);
|
||||
@@ -51,7 +51,7 @@ public class NiceSecurePasswordField extends StackPane {
|
||||
revealPasswordButton.setFocusTraversable(false);
|
||||
revealPasswordButton.visibleProperty().bind(passwordField.focusedProperty());
|
||||
revealPasswordButton.managedProperty().bind(passwordField.focusedProperty());
|
||||
revealPasswordButton.getStyleClass().add(REVEAL_BUTTON_STLYE_CLASS);
|
||||
revealPasswordButton.getStyleClass().add(REVEAL_BUTTON_STYLE_CLASS);
|
||||
|
||||
passwordField.revealPasswordProperty().bind(revealPasswordButton.selectedProperty());
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.scene.control.Label;
|
||||
|
||||
public class ThrougputLabel extends Label {
|
||||
public class ThroughputLabel extends Label {
|
||||
|
||||
private static final long KIBS_THRESHOLD = 1l << 7; // 0.128 kiB/s
|
||||
private static final long MIBS_THRESHOLD = 1l << 19; // 0.512 MiB/s
|
||||
@@ -18,7 +18,7 @@ public class ThrougputLabel extends Label {
|
||||
private final StringProperty mibsFormat = new SimpleStringProperty("%.3f");
|
||||
private final LongProperty bytesPerSecond = new SimpleLongProperty();
|
||||
|
||||
public ThrougputLabel() {
|
||||
public ThroughputLabel() {
|
||||
textProperty().bind(createStringBinding());
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ import javafx.collections.FXCollections;
|
||||
import javafx.concurrent.Worker;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.ListView;
|
||||
import java.time.Duration;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@@ -24,8 +26,7 @@ public class CheckDetailController implements FxController {
|
||||
private final EasyObservableList<DiagnosticResult> results;
|
||||
private final OptionalBinding<Worker.State> taskState;
|
||||
private final Binding<String> taskName;
|
||||
private final Binding<Number> taskDuration;
|
||||
private final ResultListCellFactory resultListCellFactory;
|
||||
private final Binding<String> taskDuration;
|
||||
private final Binding<Boolean> taskRunning;
|
||||
private final Binding<Boolean> taskScheduled;
|
||||
private final Binding<Boolean> taskFinished;
|
||||
@@ -35,17 +36,20 @@ public class CheckDetailController implements FxController {
|
||||
private final Binding<Boolean> taskCancelled;
|
||||
private final Binding<Number> countOfWarnSeverity;
|
||||
private final Binding<Number> countOfCritSeverity;
|
||||
private final ResultListCellFactory resultListCellFactory;
|
||||
private final ResourceBundle resourceBundle;
|
||||
|
||||
public ListView<DiagnosticResult> resultsListView;
|
||||
private Subscription resultSubscription;
|
||||
|
||||
@Inject
|
||||
public CheckDetailController(ObjectProperty<HealthCheckTask> selectedTask, ResultListCellFactory resultListCellFactory) {
|
||||
public CheckDetailController(ObjectProperty<HealthCheckTask> selectedTask, ResultListCellFactory resultListCellFactory, ResourceBundle resourceBundle) {
|
||||
this.resultListCellFactory = resultListCellFactory;
|
||||
this.resourceBundle = resourceBundle;
|
||||
this.results = EasyBind.wrapList(FXCollections.observableArrayList());
|
||||
this.taskState = EasyBind.wrapNullable(selectedTask).mapObservable(HealthCheckTask::stateProperty);
|
||||
this.taskName = EasyBind.wrapNullable(selectedTask).map(HealthCheckTask::getTitle).orElse("");
|
||||
this.taskDuration = EasyBind.wrapNullable(selectedTask).mapObservable(HealthCheckTask::durationInMillisProperty).orElse(-1L);
|
||||
this.resultListCellFactory = resultListCellFactory;
|
||||
this.taskDuration = EasyBind.wrapNullable(selectedTask).mapObservable(HealthCheckTask::durationInMillisProperty).orElse(-1L).map(this::millisToReadAbleDuration);
|
||||
this.taskRunning = EasyBind.wrapNullable(selectedTask).mapObservable(HealthCheckTask::runningProperty).orElse(false); //TODO: DOES NOT WORK
|
||||
this.taskScheduled = taskState.map(Worker.State.SCHEDULED::equals).orElse(false);
|
||||
this.taskNotStarted = taskState.map(Worker.State.READY::equals).orElse(false);
|
||||
@@ -87,11 +91,11 @@ public class CheckDetailController implements FxController {
|
||||
return taskName;
|
||||
}
|
||||
|
||||
public Number getTaskDuration() {
|
||||
public String getTaskDuration() {
|
||||
return taskDuration.getValue();
|
||||
}
|
||||
|
||||
public Binding<Number> taskDurationProperty() {
|
||||
public Binding<String> taskDurationProperty() {
|
||||
return taskDuration;
|
||||
}
|
||||
|
||||
@@ -167,4 +171,21 @@ public class CheckDetailController implements FxController {
|
||||
return taskCancelled;
|
||||
}
|
||||
|
||||
private String millisToReadAbleDuration(Number millis) {
|
||||
Duration tmp = Duration.ofMillis(millis.longValue());
|
||||
long hours = tmp.toHoursPart();
|
||||
long minutes = tmp.toMinutesPart();
|
||||
long seconds = tmp.toSecondsPart();
|
||||
if (hours != 0) {
|
||||
String hms_format = resourceBundle.getString("health.check.detail.hmsFormat");
|
||||
return String.format(hms_format, hours, minutes, seconds);
|
||||
} else if (minutes != 0) {
|
||||
String ms_format = resourceBundle.getString("health.check.detail.msFormat");
|
||||
return String.format(ms_format, minutes, seconds);
|
||||
} else {
|
||||
String s_format = resourceBundle.getString("health.check.detail.sFormat");
|
||||
return String.format(s_format, seconds);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,35 +4,81 @@ import org.cryptomator.ui.controls.FontAwesome5Icon;
|
||||
import org.cryptomator.ui.controls.FontAwesome5IconView;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.concurrent.Worker;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.ContentDisplay;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.util.Callback;
|
||||
|
||||
class CheckListCell extends ListCell<HealthCheckTask> {
|
||||
|
||||
private final FontAwesome5IconView stateIcon = new FontAwesome5IconView();
|
||||
private final Callback<HealthCheckTask, BooleanProperty> selectedGetter;
|
||||
private final ObjectProperty<State> stateProperty;
|
||||
|
||||
CheckListCell() {
|
||||
private CheckBox checkBox = new CheckBox();
|
||||
private BooleanProperty selectedProperty;
|
||||
|
||||
CheckListCell(Callback<HealthCheckTask, BooleanProperty> selectedGetter, ObservableValue<Boolean> switchIndicator) {
|
||||
this.selectedGetter = selectedGetter;
|
||||
this.stateProperty = new SimpleObjectProperty<>(State.SELECTION);
|
||||
switchIndicator.addListener(this::changeState);
|
||||
setPadding(new Insets(6));
|
||||
setAlignment(Pos.CENTER_LEFT);
|
||||
setContentDisplay(ContentDisplay.LEFT);
|
||||
getStyleClass().add("label");
|
||||
}
|
||||
|
||||
private void changeState(ObservableValue<? extends Boolean> observableValue, boolean oldValue, boolean newValue) {
|
||||
if (newValue) {
|
||||
stateProperty.set(State.RUN);
|
||||
} else {
|
||||
stateProperty.set(State.SELECTION);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateItem(HealthCheckTask item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
|
||||
if (item != null) {
|
||||
textProperty().bind(item.titleProperty());
|
||||
setText(item.getTitle());
|
||||
}
|
||||
switch (stateProperty.get()) {
|
||||
case SELECTION -> updateItemSelection(item, empty);
|
||||
case RUN -> updateItemRun(item, empty);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateItemSelection(HealthCheckTask item, boolean empty) {
|
||||
if (!empty) {
|
||||
setGraphic(checkBox);
|
||||
|
||||
if (selectedProperty != null) {
|
||||
checkBox.selectedProperty().unbindBidirectional(selectedProperty);
|
||||
}
|
||||
selectedProperty = selectedGetter.call(item);
|
||||
if (selectedProperty != null) {
|
||||
checkBox.selectedProperty().bindBidirectional(selectedProperty);
|
||||
}
|
||||
} else {
|
||||
setGraphic(null);
|
||||
setText(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateItemRun(HealthCheckTask item, boolean empty) {
|
||||
if (item != null) {
|
||||
item.stateProperty().addListener(this::stateChanged);
|
||||
graphicProperty().bind(Bindings.createObjectBinding(() -> graphicForState(item.getState()),item.stateProperty()));
|
||||
graphicProperty().bind(Bindings.createObjectBinding(() -> graphicForState(item.getState()), item.stateProperty()));
|
||||
stateIcon.setGlyph(glyphForState(item.getState()));
|
||||
} else {
|
||||
textProperty().unbind();
|
||||
graphicProperty().unbind();
|
||||
setGraphic(null);
|
||||
setText(null);
|
||||
@@ -61,4 +107,9 @@ class CheckListCell extends ListCell<HealthCheckTask> {
|
||||
case SUCCEEDED -> FontAwesome5Icon.CHECK;
|
||||
};
|
||||
}
|
||||
|
||||
private enum State {
|
||||
SELECTION,
|
||||
RUN;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ public class CheckListController implements FxController {
|
||||
private final ReportWriter reportWriter;
|
||||
private final ExecutorService executorService;
|
||||
private final ObjectProperty<HealthCheckTask> selectedTask;
|
||||
private final Lazy<ErrorComponent.Builder> errorComponenBuilder;
|
||||
private final Lazy<ErrorComponent.Builder> errorComponentBuilder;
|
||||
private final SimpleObjectProperty<Worker<?>> runningTask;
|
||||
private final Binding<Boolean> running;
|
||||
private final Binding<Boolean> finished;
|
||||
@@ -60,13 +60,13 @@ public class CheckListController implements FxController {
|
||||
|
||||
|
||||
@Inject
|
||||
public CheckListController(@HealthCheckWindow Stage window, Lazy<Collection<HealthCheckTask>> tasks, ReportWriter reportWriteTask, ObjectProperty<HealthCheckTask> selectedTask, ExecutorService executorService, Lazy<ErrorComponent.Builder> errorComponenBuilder) {
|
||||
public CheckListController(@HealthCheckWindow Stage window, Lazy<Collection<HealthCheckTask>> tasks, ReportWriter reportWriteTask, ObjectProperty<HealthCheckTask> selectedTask, ExecutorService executorService, Lazy<ErrorComponent.Builder> errorComponentBuilder) {
|
||||
this.window = window;
|
||||
this.tasks = FXCollections.observableArrayList(tasks.get());
|
||||
this.reportWriter = reportWriteTask;
|
||||
this.executorService = executorService;
|
||||
this.selectedTask = selectedTask;
|
||||
this.errorComponenBuilder = errorComponenBuilder;
|
||||
this.errorComponentBuilder = errorComponentBuilder;
|
||||
this.runningTask = new SimpleObjectProperty<>();
|
||||
this.running = EasyBind.wrapNullable(runningTask).mapObservable(Worker::runningProperty).orElse(false);
|
||||
this.finished = EasyBind.wrapNullable(runningTask).mapObservable(Worker::stateProperty).map(END_STATES::contains).orElse(false);
|
||||
@@ -84,17 +84,7 @@ public class CheckListController implements FxController {
|
||||
@FXML
|
||||
public void initialize() {
|
||||
checksListView.setItems(tasks);
|
||||
checksListView.setCellFactory(CheckBoxListCell.forListView(listPickIndicators::get, new StringConverter<HealthCheckTask>() {
|
||||
@Override
|
||||
public String toString(HealthCheckTask object) {
|
||||
return object.getTitle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HealthCheckTask fromString(String string) {
|
||||
return null;
|
||||
}
|
||||
}));
|
||||
checksListView.setCellFactory(view -> new CheckListCell(listPickIndicators::get, showResultScreen));
|
||||
selectedTask.bind(checksListView.getSelectionModel().selectedItemProperty());
|
||||
}
|
||||
|
||||
@@ -115,7 +105,7 @@ public class CheckListController implements FxController {
|
||||
runningTask.set(batchService);
|
||||
showResultScreen.set(true);
|
||||
checksListView.getSelectionModel().select(batch.get(0));
|
||||
checksListView.setCellFactory(view -> new CheckListCell());
|
||||
checksListView.refresh();
|
||||
window.sizeToScene();
|
||||
}
|
||||
|
||||
@@ -131,7 +121,7 @@ public class CheckListController implements FxController {
|
||||
reportWriter.writeReport(tasks);
|
||||
} catch (IOException e) {
|
||||
LOG.error("Failed to write health check report.", e);
|
||||
errorComponenBuilder.get().cause(e).window(window).returnToScene(window.getScene()).build().showErrorScene();
|
||||
errorComponentBuilder.get().cause(e).window(window).returnToScene(window.getScene()).build().showErrorScene();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ public interface KeyLoadingStrategy extends MasterkeyLoader {
|
||||
}
|
||||
|
||||
/**
|
||||
* Release any ressources or do follow-up tasks after loading a key.
|
||||
* Release any resources or do follow-up tasks after loading a key.
|
||||
*
|
||||
* @param unlockedSuccessfully <code>true</code> if successfully unlocked a vault with the loaded key
|
||||
* @implNote This method might be invoked multiple times, depending on whether multiple attempts to load a key are started.
|
||||
@@ -47,7 +47,7 @@ public interface KeyLoadingStrategy extends MasterkeyLoader {
|
||||
/**
|
||||
* A key loading strategy that will always fail by throwing a {@link MasterkeyLoadingFailedException}.
|
||||
*
|
||||
* @param exception The cause of the failure. If not alreay an {@link MasterkeyLoadingFailedException}, it will get wrapped.
|
||||
* @param exception The cause of the failure. If not already an {@link MasterkeyLoadingFailedException}, it will get wrapped.
|
||||
* @return A new KeyLoadingStrategy that will always fail with an {@link MasterkeyLoadingFailedException}.
|
||||
*/
|
||||
static KeyLoadingStrategy failed(Exception exception) {
|
||||
|
||||
@@ -32,7 +32,7 @@ public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy {
|
||||
public static final String SCHEME = "masterkeyfile";
|
||||
|
||||
private final Vault vault;
|
||||
private final MasterkeyFileAccess masterkeyFileAcccess;
|
||||
private final MasterkeyFileAccess masterkeyFileAccess;
|
||||
private final Stage window;
|
||||
private final Lazy<Scene> passphraseEntryScene;
|
||||
private final Lazy<Scene> selectMasterkeyFileScene;
|
||||
@@ -45,9 +45,9 @@ public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy {
|
||||
private boolean wrongPassword;
|
||||
|
||||
@Inject
|
||||
public MasterkeyFileLoadingStrategy(@KeyLoading Vault vault, MasterkeyFileAccess masterkeyFileAcccess, @KeyLoading Stage window, @FxmlScene(FxmlFile.UNLOCK_ENTER_PASSWORD) Lazy<Scene> passphraseEntryScene, @FxmlScene(FxmlFile.UNLOCK_SELECT_MASTERKEYFILE) Lazy<Scene> selectMasterkeyFileScene, UserInteractionLock<MasterkeyFileLoadingModule.PasswordEntry> passwordEntryLock, UserInteractionLock<MasterkeyFileLoadingModule.MasterkeyFileProvision> masterkeyFileProvisionLock, AtomicReference<char[]> password, AtomicReference<Path> filePath, MasterkeyFileLoadingFinisher finisher) {
|
||||
public MasterkeyFileLoadingStrategy(@KeyLoading Vault vault, MasterkeyFileAccess masterkeyFileAccess, @KeyLoading Stage window, @FxmlScene(FxmlFile.UNLOCK_ENTER_PASSWORD) Lazy<Scene> passphraseEntryScene, @FxmlScene(FxmlFile.UNLOCK_SELECT_MASTERKEYFILE) Lazy<Scene> selectMasterkeyFileScene, UserInteractionLock<MasterkeyFileLoadingModule.PasswordEntry> passwordEntryLock, UserInteractionLock<MasterkeyFileLoadingModule.MasterkeyFileProvision> masterkeyFileProvisionLock, AtomicReference<char[]> password, AtomicReference<Path> filePath, MasterkeyFileLoadingFinisher finisher) {
|
||||
this.vault = vault;
|
||||
this.masterkeyFileAcccess = masterkeyFileAcccess;
|
||||
this.masterkeyFileAccess = masterkeyFileAccess;
|
||||
this.window = window;
|
||||
this.passphraseEntryScene = passphraseEntryScene;
|
||||
this.selectMasterkeyFileScene = selectMasterkeyFileScene;
|
||||
@@ -68,7 +68,7 @@ public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy {
|
||||
filePath = getAlternateMasterkeyFilePath();
|
||||
}
|
||||
CharSequence passphrase = getPassphrase();
|
||||
return masterkeyFileAcccess.load(filePath, passphrase);
|
||||
return masterkeyFileAccess.load(filePath, passphrase);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new UnlockCancelledException("Unlock interrupted", e);
|
||||
|
||||
@@ -64,7 +64,7 @@ class AppLaunchEventHandler {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO dedup MainWindowController...
|
||||
// TODO deduplicate MainWindowController...
|
||||
private void addOrRevealVault(Path potentialVaultPath) {
|
||||
assert Platform.isFxApplicationThread();
|
||||
try {
|
||||
|
||||
@@ -22,7 +22,7 @@ import javafx.stage.Window;
|
||||
/**
|
||||
* The sequence of actions performed and checked during lock of a vault.
|
||||
* <p>
|
||||
* This class implements the Task interface, sucht that it can run in the background with some possible forground operations/requests to the ui, without blocking the main app.
|
||||
* This class implements the Task interface, sucht that it can run in the background with some possible foreground operations/requests to the ui, without blocking the main app.
|
||||
* If the task state is
|
||||
* <li>succeeded, the vault was successfully locked;</li>
|
||||
* <li>canceled, the lock was canceled;</li>
|
||||
|
||||
@@ -129,7 +129,7 @@ public class MainWindowTitleController implements FxController {
|
||||
}
|
||||
|
||||
public boolean isShowMinimizeButton() {
|
||||
// always show the minimize button if no tray icon is present OR it is explicitily enabled
|
||||
// always show the minimize button if no tray icon is present OR it is explicitly enabled
|
||||
return !trayMenuInitialized || settings.showMinimizeButton().get();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ public class AutoCompleter {
|
||||
if (Strings.isNullOrEmpty(prefix)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
int potentialMatchIdx = findIndexOfLexicographicallyPreceeding(0, dictionary.size(), prefix);
|
||||
int potentialMatchIdx = findIndexOfLexicographicallyPreceding(0, dictionary.size(), prefix);
|
||||
if (potentialMatchIdx < dictionary.size()) {
|
||||
String potentialMatch = dictionary.get(potentialMatchIdx);
|
||||
return potentialMatch.startsWith(prefix) ? Optional.of(potentialMatch) : Optional.empty();
|
||||
@@ -48,21 +48,21 @@ public class AutoCompleter {
|
||||
* @param prefix
|
||||
* @return index between [0, dictLen], i.e. index can exceed the upper bounds of {@link #dictionary}.
|
||||
*/
|
||||
private int findIndexOfLexicographicallyPreceeding(int begin, int end, String prefix) {
|
||||
private int findIndexOfLexicographicallyPreceding(int begin, int end, String prefix) {
|
||||
if (begin >= end) {
|
||||
return begin; // this is usually where a binary search ends "unsuccessful"
|
||||
}
|
||||
|
||||
int mid = (begin + end) / 2;
|
||||
String word = dictionary.get(mid);
|
||||
if (prefix.compareTo(word) <= 0) { // prefix preceeds or matches word
|
||||
if (prefix.compareTo(word) <= 0) { // prefix precedes or matches word
|
||||
// proceed in left half
|
||||
assert mid < end;
|
||||
return findIndexOfLexicographicallyPreceeding(0, mid, prefix);
|
||||
return findIndexOfLexicographicallyPreceding(0, mid, prefix);
|
||||
} else {
|
||||
// proceed in right half
|
||||
assert mid >= begin;
|
||||
return findIndexOfLexicographicallyPreceeding(mid + 1, end, prefix);
|
||||
return findIndexOfLexicographicallyPreceding(mid + 1, end, prefix);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ class WordEncoder {
|
||||
* @throws IllegalArgumentException If input is not a multiple of three bytes
|
||||
*/
|
||||
public String encodePadded(byte[] input) {
|
||||
Preconditions.checkArgument(input.length % 3 == 0, "input needs to be padded to a multipe of three");
|
||||
Preconditions.checkArgument(input.length % 3 == 0, "input needs to be padded to a multiple of three");
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < input.length; i += 3) {
|
||||
byte b1 = input[i];
|
||||
@@ -85,12 +85,12 @@ class WordEncoder {
|
||||
* @throws IllegalArgumentException If the encoded string doesn't consist of a multiple of two words or one of the words is unknown to this encoder.
|
||||
*/
|
||||
public byte[] decode(String encoded) {
|
||||
List<String> splitted = Splitter.on(DELIMITER).omitEmptyStrings().splitToList(Strings.nullToEmpty(encoded));
|
||||
Preconditions.checkArgument(splitted.size() % 2 == 0, "%s needs to be a multiple of two words", encoded);
|
||||
byte[] result = new byte[splitted.size() / 2 * 3];
|
||||
for (int i = 0; i < splitted.size(); i += 2) {
|
||||
String w1 = splitted.get(i);
|
||||
String w2 = splitted.get(i + 1);
|
||||
List<String> split = Splitter.on(DELIMITER).omitEmptyStrings().splitToList(Strings.nullToEmpty(encoded));
|
||||
Preconditions.checkArgument(split.size() % 2 == 0, "%s needs to be a multiple of two words", encoded);
|
||||
byte[] result = new byte[split.size() / 2 * 3];
|
||||
for (int i = 0; i < split.size(); i += 2) {
|
||||
String w1 = split.get(i);
|
||||
String w2 = split.get(i + 1);
|
||||
int firstWordIndex = indices.getOrDefault(w1, -1);
|
||||
int secondWordIndex = indices.getOrDefault(w2, -1);
|
||||
Preconditions.checkArgument(firstWordIndex != -1, "%s not in dictionary", w1);
|
||||
|
||||
@@ -65,8 +65,8 @@ public class VaultStatisticsController implements FxController {
|
||||
this.cacheHitRate = WeakBindings.bindDouble(stats.cacheHitRateProperty());
|
||||
this.cacheHitDegrees = cacheHitRate.multiply(-270);
|
||||
this.cacheHitPercentage = cacheHitRate.multiply(100);
|
||||
this.totalBytesRead = WeakBindings.bindLong(stats.toalBytesReadProperty());
|
||||
this.totalBytesWritten = WeakBindings.bindLong(stats.toalBytesWrittenProperty());
|
||||
this.totalBytesRead = WeakBindings.bindLong(stats.totalBytesReadProperty());
|
||||
this.totalBytesWritten = WeakBindings.bindLong(stats.totalBytesWrittenProperty());
|
||||
this.totalBytesDecrypted = WeakBindings.bindLong(stats.totalBytesDecryptedProperty());
|
||||
this.totalBytesEncrypted = WeakBindings.bindLong(stats.totalBytesEncryptedProperty());
|
||||
this.filesRead = WeakBindings.bindLong(stats.filesRead());
|
||||
@@ -102,7 +102,7 @@ public class VaultStatisticsController implements FxController {
|
||||
this.decryptedBytesRead = readData;
|
||||
this.encryptedBytesWrite = writeData;
|
||||
|
||||
// initialize data once and change value of datapoints later:
|
||||
// initialize data once and change value of data points later:
|
||||
for (int i = 0; i < IO_SAMPLING_STEPS; i++) {
|
||||
decryptedBytesRead.getData().add(new Data<>(i, 0));
|
||||
encryptedBytesWrite.getData().add(new Data<>(i, 0));
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<?import org.cryptomator.ui.controls.DataLabel?>
|
||||
<?import org.cryptomator.ui.controls.FormattedLabel?>
|
||||
<?import org.cryptomator.ui.controls.ThrougputLabel?>
|
||||
<?import org.cryptomator.ui.controls.ThroughputLabel?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.chart.AreaChart?>
|
||||
<?import javafx.scene.chart.NumberAxis?>
|
||||
@@ -37,7 +37,7 @@
|
||||
|
||||
<!-- Read -->
|
||||
<VBox prefWidth="300" prefHeight="300" spacing="6" alignment="CENTER">
|
||||
<ThrougputLabel styleClass="label-large" idleFormat="%stats.read.throughput.idle" kibsFormat="%stats.read.throughput.kibs" mibsFormat="%stats.read.throughput.mibs" bytesPerSecond="${controller.bpsRead}"/>
|
||||
<ThroughputLabel styleClass="label-large" idleFormat="%stats.read.throughput.idle" kibsFormat="%stats.read.throughput.kibs" mibsFormat="%stats.read.throughput.mibs" bytesPerSecond="${controller.bpsRead}"/>
|
||||
<AreaChart fx:id="readChart" styleClass="io-stats" createSymbols="false" animated="false">
|
||||
<xAxis>
|
||||
<NumberAxis fx:id="readChartXAxis" styleClass="io-stats" autoRanging="false" forceZeroInRange="false" side="BOTTOM"/>
|
||||
@@ -56,7 +56,7 @@
|
||||
|
||||
<!-- Write -->
|
||||
<VBox prefWidth="300" prefHeight="300" spacing="6" alignment="CENTER">
|
||||
<ThrougputLabel styleClass="label-large" idleFormat="%stats.write.throughput.idle" kibsFormat="%stats.write.throughput.kibs" mibsFormat="%stats.write.throughput.mibs" bytesPerSecond="${controller.bpsWritten}"/>
|
||||
<ThroughputLabel styleClass="label-large" idleFormat="%stats.write.throughput.idle" kibsFormat="%stats.write.throughput.kibs" mibsFormat="%stats.write.throughput.mibs" bytesPerSecond="${controller.bpsWritten}"/>
|
||||
<AreaChart fx:id="writeChart" styleClass="io-stats" createSymbols="false" animated="false">
|
||||
<xAxis>
|
||||
<NumberAxis fx:id="writeChartXAxis" styleClass="io-stats" autoRanging="false" forceZeroInRange="false" side="BOTTOM"/>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||
<?import org.cryptomator.ui.controls.ThrougputLabel?>
|
||||
<?import org.cryptomator.ui.controls.ThroughputLabel?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.Tooltip?>
|
||||
@@ -41,11 +41,11 @@
|
||||
<VBox spacing="6">
|
||||
<HBox alignment="CENTER_RIGHT" spacing="6">
|
||||
<Label styleClass="label-small,label-muted" text="%main.vaultDetail.bytesPerSecondRead"/>
|
||||
<ThrougputLabel styleClass="label-small,label-muted" alignment="CENTER_RIGHT" minWidth="60" idleFormat="%main.vaultDetail.throughput.idle" kibsFormat="%main.vaultDetail.throughput.kbps" mibsFormat="%main.vaultDetail.throughput.mbps" bytesPerSecond="${controller.vault.stats.bytesPerSecondRead}"/>
|
||||
<ThroughputLabel styleClass="label-small,label-muted" alignment="CENTER_RIGHT" minWidth="60" idleFormat="%main.vaultDetail.throughput.idle" kibsFormat="%main.vaultDetail.throughput.kbps" mibsFormat="%main.vaultDetail.throughput.mbps" bytesPerSecond="${controller.vault.stats.bytesPerSecondRead}"/>
|
||||
</HBox>
|
||||
<HBox alignment="CENTER_RIGHT" spacing="6">
|
||||
<Label styleClass="label-small,label-muted" text="%main.vaultDetail.bytesPerSecondWritten"/>
|
||||
<ThrougputLabel styleClass="label-small,label-muted" alignment="CENTER_RIGHT" minWidth="60" idleFormat="%main.vaultDetail.throughput.idle" kibsFormat="%main.vaultDetail.throughput.kbps" mibsFormat="%main.vaultDetail.throughput.mbps" bytesPerSecond="${controller.vault.stats.bytesPerSecondWritten}"/>
|
||||
<ThroughputLabel styleClass="label-small,label-muted" alignment="CENTER_RIGHT" minWidth="60" idleFormat="%main.vaultDetail.throughput.idle" kibsFormat="%main.vaultDetail.throughput.kbps" mibsFormat="%main.vaultDetail.throughput.mbps" bytesPerSecond="${controller.vault.stats.bytesPerSecondWritten}"/>
|
||||
</HBox>
|
||||
</VBox>
|
||||
</graphic>
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
</Button>
|
||||
</VBox>
|
||||
<Region VBox.vgrow="ALWAYS"/>
|
||||
<Label maxWidth="-Infinity" text="%vaultOptions.masterkey.recoveryKeyExpanation" wrapText="true"/>
|
||||
<Label maxWidth="-Infinity" text="%vaultOptions.masterkey.recoveryKeyExplanation" wrapText="true"/>
|
||||
<VBox spacing="6" alignment="CENTER">
|
||||
<Button text="%vaultOptions.masterkey.showRecoveryKeyBtn" onAction="#showRecoveryKey" contentDisplay="LEFT" maxWidth="Infinity">
|
||||
<graphic>
|
||||
|
||||
@@ -14,7 +14,7 @@ generic.button.done=Done
|
||||
generic.button.next=Next
|
||||
generic.button.print=Print
|
||||
## Error
|
||||
generic.error.title=An unexpected error occured
|
||||
generic.error.title=An unexpected error occurred
|
||||
generic.error.instruction=This should not have happened. Please report the error text below and include a description of what steps did lead to this error.
|
||||
|
||||
# Defaults
|
||||
@@ -131,7 +131,7 @@ migration.start.confirm=Yes, my vault is fully synced
|
||||
migration.run.enterPassword=Enter the password for "%s"
|
||||
migration.run.startMigrationBtn=Migrate Vault
|
||||
migration.run.progressHint=This might take some time…
|
||||
## Sucess
|
||||
## Success
|
||||
migration.success.nextStepsInstructions=Migrated "%s" successfully.\nYou can now unlock your vault.
|
||||
migration.success.unlockNow=Unlock Now
|
||||
## Missing file system capabilities
|
||||
@@ -148,24 +148,27 @@ migration.impossible.moreInfo=The vault can still be opened with an older versio
|
||||
|
||||
# Health Check
|
||||
health.title=Vault Health Check
|
||||
health.start.introduction=The Vault Health Check is a collection of checks to detect and possilby fix problems in the internal structure of your vault. Please note, that not all problems are fixable. You need the vault password to perform the checks.
|
||||
health.start.introduction=The Vault Health Check is a collection of checks to detect and possibly fix problems in the internal structure of your vault. Please note, that not all problems are fixable. You need the vault password to perform the checks.
|
||||
health.start.configValid=Reading and parsing vault configuration file was successful. Proceed to select checks.
|
||||
health.start.configInvalid=Error while reading and parsing the vault configuration file.
|
||||
health.checkList.header=Available Health Checks
|
||||
health.checkList.selectAllBox=Select All
|
||||
health.check.runBatchBtn=Run selected Checks
|
||||
health.check.runBatchBtn=Run Selected Checks
|
||||
## Detail view
|
||||
health.check.detail.noSelectedCheck=For results select a finished health check in the left list.
|
||||
health.check.detail.header=Results of %s
|
||||
health.check.detail.taskNotStarted=The check was not selected to run.
|
||||
health.check.detail.taskScheduled=The check is scheduled.
|
||||
health.check.detail.taskRunning=The check is currently running…
|
||||
health.check.detail.taskSucceeded=The check finished successfully after %d milliseconds.
|
||||
health.check.detail.taskSucceeded=The check finished successfully after %s.
|
||||
health.check.detail.taskFailed=The check exited due to an error.
|
||||
health.check.detail.taskCancelled=The check was cancelled.
|
||||
health.check.detail.problemCount=Found %d problems and %d unfixable errors.
|
||||
health.check.exportBtn=Export Report
|
||||
health.check.fixBtn=Fix
|
||||
health.check.detail.hmsFormat= %d hours, %2d minutes and %2d seconds
|
||||
health.check.detail.msFormat= %d minutes and %2d seconds
|
||||
health.check.detail.sFormat= %d seconds
|
||||
## Checks
|
||||
health.org.cryptomator.cryptofs.health.dirid.DirIdCheck=Directory Check
|
||||
|
||||
@@ -322,7 +325,7 @@ vaultOptions.mount.mountPoint.directoryPickerTitle=Pick an empty directory
|
||||
vaultOptions.masterkey=Password
|
||||
vaultOptions.masterkey.changePasswordBtn=Change Password
|
||||
vaultOptions.masterkey.forgetSavedPasswordBtn=Forget Saved Password
|
||||
vaultOptions.masterkey.recoveryKeyExpanation=A recovery key is your only means to restore access to a vault if you lose your password.
|
||||
vaultOptions.masterkey.recoveryKeyExplanation=A recovery key is your only means to restore access to a vault if you lose your password.
|
||||
vaultOptions.masterkey.showRecoveryKeyBtn=Display Recovery Key
|
||||
vaultOptions.masterkey.recoverPasswordBtn=Recover Password
|
||||
## Auto Lock
|
||||
|
||||
@@ -11,11 +11,8 @@ GNU General Public License for more details.
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
|
||||
Cryptomator uses 43 third-party dependencies under the following licenses:
|
||||
Cryptomator uses 40 third-party dependencies under the following licenses:
|
||||
Apache License v2.0:
|
||||
- Jackson-annotations (com.fasterxml.jackson.core:jackson-annotations:2.11.0 - http://github.com/FasterXML/jackson)
|
||||
- Jackson-core (com.fasterxml.jackson.core:jackson-core:2.11.0 - https://github.com/FasterXML/jackson-core)
|
||||
- jackson-databind (com.fasterxml.jackson.core:jackson-databind:2.11.0 - http://github.com/FasterXML/jackson)
|
||||
- jffi (com.github.jnr:jffi:1.2.23 - http://github.com/jnr/jffi)
|
||||
- jnr-a64asm (com.github.jnr:jnr-a64asm:1.0.0 - http://nexus.sonatype.org/oss-repository-hosting.html/jnr-a64asm)
|
||||
- jnr-constants (com.github.jnr:jnr-constants:0.9.15 - http://github.com/jnr/jnr-constants)
|
||||
@@ -28,15 +25,15 @@ Cryptomator uses 43 third-party dependencies under the following licenses:
|
||||
- javax.inject (javax.inject:javax.inject:1 - http://code.google.com/p/atinject/)
|
||||
- Java Native Access (net.java.dev.jna:jna:5.7.0 - https://github.com/java-native-access/jna)
|
||||
- Java Native Access Platform (net.java.dev.jna:jna-platform:5.7.0 - https://github.com/java-native-access/jna)
|
||||
- Apache Commons Lang (org.apache.commons:commons-lang3:3.11 - https://commons.apache.org/proper/commons-lang/)
|
||||
- Apache Commons Lang (org.apache.commons:commons-lang3:3.12.0 - https://commons.apache.org/proper/commons-lang/)
|
||||
- Apache HttpCore (org.apache.httpcomponents:httpcore:4.4.14 - http://hc.apache.org/httpcomponents-core-ga)
|
||||
- Jackrabbit WebDAV Library (org.apache.jackrabbit:jackrabbit-webdav:2.21.5 - http://jackrabbit.apache.org/jackrabbit-webdav/)
|
||||
- Jetty :: Http Utility (org.eclipse.jetty:jetty-http:10.0.2 - https://eclipse.org/jetty/jetty-http)
|
||||
- Jetty :: IO Utility (org.eclipse.jetty:jetty-io:10.0.2 - https://eclipse.org/jetty/jetty-io)
|
||||
- Jetty :: Security (org.eclipse.jetty:jetty-security:10.0.2 - https://eclipse.org/jetty/jetty-security)
|
||||
- Jetty :: Server Core (org.eclipse.jetty:jetty-server:10.0.2 - https://eclipse.org/jetty/jetty-server)
|
||||
- Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:10.0.2 - https://eclipse.org/jetty/jetty-servlet)
|
||||
- Jetty :: Utilities (org.eclipse.jetty:jetty-util:10.0.2 - https://eclipse.org/jetty/jetty-util)
|
||||
- Jetty :: Http Utility (org.eclipse.jetty:jetty-http:10.0.3 - https://eclipse.org/jetty/jetty-http)
|
||||
- Jetty :: IO Utility (org.eclipse.jetty:jetty-io:10.0.3 - https://eclipse.org/jetty/jetty-io)
|
||||
- Jetty :: Security (org.eclipse.jetty:jetty-security:10.0.3 - https://eclipse.org/jetty/jetty-security)
|
||||
- Jetty :: Server Core (org.eclipse.jetty:jetty-server:10.0.3 - https://eclipse.org/jetty/jetty-server)
|
||||
- Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:10.0.3 - https://eclipse.org/jetty/jetty-servlet)
|
||||
- Jetty :: Utilities (org.eclipse.jetty:jetty-util:10.0.3 - https://eclipse.org/jetty/jetty-util)
|
||||
- Jetty :: Servlet API and Schemas for JPMS and OSGi (org.eclipse.jetty.toolchain:jetty-servlet-api:4.0.6 - https://eclipse.org/jetty/jetty-servlet-api)
|
||||
BSD:
|
||||
- asm (org.ow2.asm:asm:7.1 - http://asm.ow2.org/)
|
||||
@@ -47,12 +44,12 @@ Cryptomator uses 43 third-party dependencies under the following licenses:
|
||||
Eclipse Public License - Version 1.0:
|
||||
- Jetty :: Servlet API and Schemas for JPMS and OSGi (org.eclipse.jetty.toolchain:jetty-servlet-api:4.0.6 - https://eclipse.org/jetty/jetty-servlet-api)
|
||||
Eclipse Public License - Version 2.0:
|
||||
- Jetty :: Http Utility (org.eclipse.jetty:jetty-http:10.0.2 - https://eclipse.org/jetty/jetty-http)
|
||||
- Jetty :: IO Utility (org.eclipse.jetty:jetty-io:10.0.2 - https://eclipse.org/jetty/jetty-io)
|
||||
- Jetty :: Security (org.eclipse.jetty:jetty-security:10.0.2 - https://eclipse.org/jetty/jetty-security)
|
||||
- Jetty :: Server Core (org.eclipse.jetty:jetty-server:10.0.2 - https://eclipse.org/jetty/jetty-server)
|
||||
- Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:10.0.2 - https://eclipse.org/jetty/jetty-servlet)
|
||||
- Jetty :: Utilities (org.eclipse.jetty:jetty-util:10.0.2 - https://eclipse.org/jetty/jetty-util)
|
||||
- Jetty :: Http Utility (org.eclipse.jetty:jetty-http:10.0.3 - https://eclipse.org/jetty/jetty-http)
|
||||
- Jetty :: IO Utility (org.eclipse.jetty:jetty-io:10.0.3 - https://eclipse.org/jetty/jetty-io)
|
||||
- Jetty :: Security (org.eclipse.jetty:jetty-security:10.0.3 - https://eclipse.org/jetty/jetty-security)
|
||||
- Jetty :: Server Core (org.eclipse.jetty:jetty-server:10.0.3 - https://eclipse.org/jetty/jetty-server)
|
||||
- Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:10.0.3 - https://eclipse.org/jetty/jetty-servlet)
|
||||
- Jetty :: Utilities (org.eclipse.jetty:jetty-util:10.0.3 - https://eclipse.org/jetty/jetty-util)
|
||||
Eclipse Public License - v 1.0:
|
||||
- Logback Classic Module (ch.qos.logback:logback-classic:1.2.3 - http://logback.qos.ch/logback-classic)
|
||||
- Logback Core Module (ch.qos.logback:logback-core:1.2.3 - http://logback.qos.ch/logback-core)
|
||||
@@ -73,11 +70,11 @@ Cryptomator uses 43 third-party dependencies under the following licenses:
|
||||
- Java Native Access (net.java.dev.jna:jna:5.7.0 - https://github.com/java-native-access/jna)
|
||||
- Java Native Access Platform (net.java.dev.jna:jna-platform:5.7.0 - https://github.com/java-native-access/jna)
|
||||
MIT License:
|
||||
- java jwt (com.auth0:java-jwt:3.15.0 - https://github.com/auth0/java-jwt)
|
||||
- java jwt (com.auth0:java-jwt:3.17.0 - https://github.com/auth0/java-jwt)
|
||||
- jnr-x86asm (com.github.jnr:jnr-x86asm:1.0.2 - http://github.com/jnr/jnr-x86asm)
|
||||
- jnr-fuse (com.github.serceman:jnr-fuse:0.5.5 - https://github.com/SerCeMan/jnr-fuse)
|
||||
- zxcvbn4j (com.nulab-inc:zxcvbn:1.5.2 - https://github.com/nulab/zxcvbn4j)
|
||||
- SLF4J API Module (org.slf4j:slf4j-api:1.7.30 - http://www.slf4j.org)
|
||||
- SLF4J API Module (org.slf4j:slf4j-api:1.7.31 - http://www.slf4j.org)
|
||||
The BSD 2-Clause License:
|
||||
- EasyBind (com.tobiasdiez:easybind:2.2 - https://github.com/tobiasdiez/EasyBind)
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import java.util.Optional;
|
||||
|
||||
public class LicenseCheckerTest {
|
||||
|
||||
@SuppressWarnings("SpellCheckingInspection")
|
||||
private static final String PUBLIC_KEY = """
|
||||
MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBgc4HZz+/fBbC7lmEww0AO3NK9wVZ\
|
||||
PDZ0VEnsaUFLEYpTzb90nITtJUcPUbvOsdZIZ1Q8fnbquAYgxXL5UgHMoywAib47\
|
||||
@@ -25,6 +26,7 @@ public class LicenseCheckerTest {
|
||||
|
||||
@Test
|
||||
public void testCheckValidLicense() {
|
||||
@SuppressWarnings("SpellCheckingInspection")
|
||||
String license = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCIsImtpZCI6InhaRGZacHJ5NFA5dlpQWnlHMmZOQlJqLTdMejVvbVZkbTd0SG9DZ1NOZlkifQ.eyJzdWIiOiJjcnlwdG9ib3RAZXhhbXBsZS5jb20iLCJpYXQiOjE1MTYyMzkwMjJ9.AQaBIKQdNCxmRJi2wLOcbagTgi39WhdWwgdpKTYSPicg-aPr_tst_RjmnqMemx3cBe0Blr4nEbj_lAtSKHz_i61fAUyI1xCIAZYbK9Q3ICHIHQl3AiuCpBwFl-k81OB4QDYiKpEc9gLN5dhW_VymJMsgOvyiC0UjC91f2AM7s46byDNj";
|
||||
|
||||
Optional<DecodedJWT> decoded = licenseChecker.check(license);
|
||||
@@ -35,6 +37,7 @@ public class LicenseCheckerTest {
|
||||
|
||||
@Test
|
||||
public void testCheckInvalidLicenseHeader() {
|
||||
@SuppressWarnings("SpellCheckingInspection")
|
||||
String license = "EyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCIsImtpZCI6InhaRGZacHJ5NFA5dlpQWnlHMmZOQlJqLTdMejVvbVZkbTd0SG9DZ1NOZlkifQ.eyJzdWIiOiJjcnlwdG9ib3RAZXhhbXBsZS5jb20iLCJpYXQiOjE1MTYyMzkwMjJ9.AQaBIKQdNCxmRJi2wLOcbagTgi39WhdWwgdpKTYSPicg-aPr_tst_RjmnqMemx3cBe0Blr4nEbj_lAtSKHz_i61fAUyI1xCIAZYbK9Q3ICHIHQl3AiuCpBwFl-k81OB4QDYiKpEc9gLN5dhW_VymJMsgOvyiC0UjC91f2AM7s46byDNj";
|
||||
|
||||
Optional<DecodedJWT> decoded = licenseChecker.check(license);
|
||||
@@ -44,6 +47,7 @@ public class LicenseCheckerTest {
|
||||
|
||||
@Test
|
||||
public void testCheckInvalidLicensePayload() {
|
||||
@SuppressWarnings("SpellCheckingInspection")
|
||||
String license = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCIsImtpZCI6InhaRGZacHJ5NFA5dlpQWnlHMmZOQlJqLTdMejVvbVZkbTd0SG9DZ1NOZlkifQ.EyJzdWIiOiJjcnlwdG9ib3RAZXhhbXBsZS5jb20iLCJpYXQiOjE1MTYyMzkwMjJ9.AQaBIKQdNCxmRJi2wLOcbagTgi39WhdWwgdpKTYSPicg-aPr_tst_RjmnqMemx3cBe0Blr4nEbj_lAtSKHz_i61fAUyI1xCIAZYbK9Q3ICHIHQl3AiuCpBwFl-k81OB4QDYiKpEc9gLN5dhW_VymJMsgOvyiC0UjC91f2AM7s46byDNj";
|
||||
|
||||
Optional<DecodedJWT> decoded = licenseChecker.check(license);
|
||||
@@ -53,6 +57,7 @@ public class LicenseCheckerTest {
|
||||
|
||||
@Test
|
||||
public void testCheckInvalidLicenseSignature() {
|
||||
@SuppressWarnings("SpellCheckingInspection")
|
||||
String license = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCIsImtpZCI6InhaRGZacHJ5NFA5dlpQWnlHMmZOQlJqLTdMejVvbVZkbTd0SG9DZ1NOZlkifQ.eyJzdWIiOiJjcnlwdG9ib3RAZXhhbXBsZS5jb20iLCJpYXQiOjE1MTYyMzkwMjJ9.aQaBIKQdNCxmRJi2wLOcbagTgi39WhdWwgdpKTYSPicg-aPr_tst_RjmnqMemx3cBe0Blr4nEbj_lAtSKHz_i61fAUyI1xCIAZYbK9Q3ICHIHQl3AiuCpBwFl-k81OB4QDYiKpEc9gLN5dhW_VymJMsgOvyiC0UjC91f2AM7s46byDNj";
|
||||
|
||||
Optional<DecodedJWT> decoded = licenseChecker.check(license);
|
||||
|
||||
@@ -44,6 +44,7 @@ public class SettingsJsonAdapterTest {
|
||||
Assertions.assertEquals(VolumeImpl.FUSE, settings.preferredVolumeImpl().get());
|
||||
}
|
||||
|
||||
@SuppressWarnings("SpellCheckingInspection")
|
||||
@ParameterizedTest(name = "fromJson() should throw IOException for input: {0}")
|
||||
@ValueSource(strings = { //
|
||||
"", //
|
||||
|
||||
@@ -40,6 +40,7 @@ public class VaultSettingsJsonAdapterTest {
|
||||
);
|
||||
}
|
||||
|
||||
@SuppressWarnings("SpellCheckingInspection")
|
||||
@Test
|
||||
public void testSerialize() throws IOException {
|
||||
VaultSettings vaultSettings = new VaultSettings("test");
|
||||
|
||||
@@ -24,7 +24,7 @@ public class IpcFactoryTest {
|
||||
private IpcProtocolImpl protocolHandler = Mockito.mock(IpcProtocolImpl.class);
|
||||
|
||||
@Test
|
||||
@DisplayName("Wihout IPC port files")
|
||||
@DisplayName("Without IPC port files")
|
||||
public void testNoIpcWithoutPortFile() throws IOException {
|
||||
IpcFactory inTest = new IpcFactory(environment, protocolHandler);
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import java.util.List;
|
||||
|
||||
public class ReadMeGeneratorTest {
|
||||
|
||||
@SuppressWarnings("SpellCheckingInspection")
|
||||
@ParameterizedTest
|
||||
@CsvSource({ //
|
||||
"test,test", //
|
||||
|
||||
@@ -49,6 +49,7 @@ public class SecurePasswordFieldTest {
|
||||
Assertions.assertEquals("anteater", pwField.getCharacters().toString());
|
||||
}
|
||||
|
||||
@SuppressWarnings("SpellCheckingInspection")
|
||||
@Test
|
||||
@DisplayName("\"anteater\".insert(3, \"b\")")
|
||||
public void insert2() {
|
||||
@@ -76,6 +77,7 @@ public class SecurePasswordFieldTest {
|
||||
Assertions.assertEquals("ant", pwField.getCharacters().toString());
|
||||
}
|
||||
|
||||
@SuppressWarnings("SpellCheckingInspection")
|
||||
@Test
|
||||
@DisplayName("\"anteater\".replace(0, 3, \"hand\")")
|
||||
public void replace1() {
|
||||
@@ -85,6 +87,7 @@ public class SecurePasswordFieldTest {
|
||||
Assertions.assertEquals("handeater", pwField.getCharacters().toString());
|
||||
}
|
||||
|
||||
@SuppressWarnings("SpellCheckingInspection")
|
||||
@Test
|
||||
@DisplayName("\"anteater\".replace(3, 6, \"keep\")")
|
||||
public void replace2() {
|
||||
@@ -94,6 +97,7 @@ public class SecurePasswordFieldTest {
|
||||
Assertions.assertEquals("antkeeper", pwField.getCharacters().toString());
|
||||
}
|
||||
|
||||
@SuppressWarnings("SpellCheckingInspection")
|
||||
@Test
|
||||
@DisplayName("\"anteater\".replace(0, 3, \"bee\")")
|
||||
public void replace3() {
|
||||
@@ -105,6 +109,7 @@ public class SecurePasswordFieldTest {
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("SpellCheckingInspection")
|
||||
@Test
|
||||
@DisplayName("entering NFC string leads to NFC char[]")
|
||||
public void enterNfcString() {
|
||||
@@ -125,6 +130,7 @@ public class SecurePasswordFieldTest {
|
||||
Assertions.assertEquals('\uDCA9', result.charAt(9));
|
||||
}
|
||||
|
||||
@SuppressWarnings("SpellCheckingInspection")
|
||||
@Test
|
||||
@DisplayName("entering NFD string leads to NFC char[]")
|
||||
public void enterNfdString() {
|
||||
|
||||
@@ -43,6 +43,7 @@ public class AutoCompleterTest {
|
||||
Assertions.assertEquals("tame", result.get());
|
||||
}
|
||||
|
||||
@SuppressWarnings("SpellCheckingInspection")
|
||||
@ParameterizedTest
|
||||
@DisplayName("find 'teach'")
|
||||
@ValueSource(strings = {"te", "tea", "teac", "teach"})
|
||||
@@ -52,6 +53,7 @@ public class AutoCompleterTest {
|
||||
Assertions.assertEquals("teach", result.get());
|
||||
}
|
||||
|
||||
@SuppressWarnings("SpellCheckingInspection")
|
||||
@ParameterizedTest
|
||||
@DisplayName("find 'teacher'")
|
||||
@ValueSource(strings = {"teache", "teacher"})
|
||||
|
||||
Reference in New Issue
Block a user