mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-19 03:01:27 +00:00
Merge branch 'develop' into feature/mount-provider
# Conflicts: # .github/workflows/appimage.yml # .github/workflows/mac-dmg.yml # .github/workflows/win-exe.yml # pom.xml # src/main/java/org/cryptomator/common/vaults/WebDavVolume.java # src/main/java/org/cryptomator/ui/traymenu/AwtTrayMenuController.java
This commit is contained in:
@@ -26,6 +26,7 @@ public class Environment {
|
||||
private static final String KEYCHAIN_PATHS_PROP_NAME = "cryptomator.integrationsWin.keychainPaths";
|
||||
private static final String P12_PATH_PROP_NAME = "cryptomator.p12Path";
|
||||
private static final String LOG_DIR_PROP_NAME = "cryptomator.logDir";
|
||||
private static final String LOOPBACK_ALIAS_PROP_NAME = "cryptomator.loopbackAlias";
|
||||
private static final String MOUNTPOINT_DIR_PROP_NAME = "cryptomator.mountPointsDir";
|
||||
private static final String MIN_PW_LENGTH_PROP_NAME = "cryptomator.minPwLength";
|
||||
private static final String APP_VERSION_PROP_NAME = "cryptomator.appVersion";
|
||||
@@ -45,6 +46,7 @@ public class Environment {
|
||||
logCryptomatorSystemProperty(IPC_SOCKET_PATH_PROP_NAME);
|
||||
logCryptomatorSystemProperty(KEYCHAIN_PATHS_PROP_NAME);
|
||||
logCryptomatorSystemProperty(LOG_DIR_PROP_NAME);
|
||||
logCryptomatorSystemProperty(LOOPBACK_ALIAS_PROP_NAME);
|
||||
logCryptomatorSystemProperty(PLUGIN_DIR_PROP_NAME);
|
||||
logCryptomatorSystemProperty(MOUNTPOINT_DIR_PROP_NAME);
|
||||
logCryptomatorSystemProperty(MIN_PW_LENGTH_PROP_NAME);
|
||||
@@ -90,6 +92,10 @@ public class Environment {
|
||||
return getPath(LOG_DIR_PROP_NAME).map(this::replaceHomeDir);
|
||||
}
|
||||
|
||||
public Optional<String> getLoopbackAlias() {
|
||||
return Optional.ofNullable(System.getProperty(LOOPBACK_ALIAS_PROP_NAME));
|
||||
}
|
||||
|
||||
public Optional<Path> getPluginDir() {
|
||||
return getPath(PLUGIN_DIR_PROP_NAME).map(this::replaceHomeDir);
|
||||
}
|
||||
@@ -112,22 +118,13 @@ public class Environment {
|
||||
}
|
||||
|
||||
public int getMinPwLength() {
|
||||
return getInt(MIN_PW_LENGTH_PROP_NAME, DEFAULT_MIN_PW_LENGTH);
|
||||
return Integer.getInteger(MIN_PW_LENGTH_PROP_NAME, DEFAULT_MIN_PW_LENGTH);
|
||||
}
|
||||
|
||||
public boolean showTrayIcon() {
|
||||
return Boolean.getBoolean(TRAY_ICON_PROP_NAME);
|
||||
}
|
||||
|
||||
private int getInt(String propertyName, int defaultValue) {
|
||||
String value = System.getProperty(propertyName);
|
||||
try {
|
||||
return Integer.parseInt(value);
|
||||
} catch (NumberFormatException e) { // includes "null" values
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<Path> getPath(String propertyName) {
|
||||
String value = System.getProperty(propertyName);
|
||||
return Optional.ofNullable(value).map(Paths::get);
|
||||
|
||||
@@ -41,6 +41,8 @@ public class VaultStats {
|
||||
private final LongProperty totalBytesDecrypted = new SimpleLongProperty();
|
||||
private final LongProperty filesRead = new SimpleLongProperty();
|
||||
private final LongProperty filesWritten = new SimpleLongProperty();
|
||||
private final LongProperty filesAccessed = new SimpleLongProperty();
|
||||
private final LongProperty totalFilesAccessed = new SimpleLongProperty();
|
||||
private final ObjectProperty<Instant> lastActivity = new SimpleObjectProperty<>();
|
||||
|
||||
@Inject
|
||||
@@ -82,6 +84,8 @@ public class VaultStats {
|
||||
var oldAccessCount = filesRead.get() + filesWritten.get();
|
||||
filesRead.set(stats.map(CryptoFileSystemStats::pollAmountOfAccessesRead).orElse(0L));
|
||||
filesWritten.set(stats.map(CryptoFileSystemStats::pollAmountOfAccessesWritten).orElse(0L));
|
||||
filesAccessed.set(stats.map(CryptoFileSystemStats::pollAmountOfAccesses).orElse(0L));
|
||||
totalFilesAccessed.set(stats.map(CryptoFileSystemStats::pollTotalAmountOfAccesses).orElse(0L));
|
||||
var newAccessCount = filesRead.get() + filesWritten.get();
|
||||
|
||||
// check for any I/O activity
|
||||
@@ -188,6 +192,19 @@ public class VaultStats {
|
||||
|
||||
public long getFilesWritten() {return filesWritten.get();}
|
||||
|
||||
public LongProperty filesAccessed() {
|
||||
return filesAccessed;}
|
||||
|
||||
public long getFilesAccessed() {return filesAccessed.get();}
|
||||
|
||||
public LongProperty totalFilesAccessed(){
|
||||
return totalFilesAccessed;
|
||||
}
|
||||
|
||||
public long getTotalFilesAccessed(){
|
||||
return totalFilesAccessed.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<Instant> lastActivityProperty() {
|
||||
return lastActivity;
|
||||
}
|
||||
|
||||
@@ -2,19 +2,20 @@ package org.cryptomator.ui.addvaultwizard;
|
||||
|
||||
import dagger.Lazy;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.common.settings.UiTheme;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultListManager;
|
||||
import org.cryptomator.integrations.uiappearance.Theme;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.cryptomator.ui.fxapp.FxApplicationStyle;
|
||||
import org.cryptomator.ui.fxapp.FxApplicationWindows;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.image.Image;
|
||||
@@ -23,6 +24,7 @@ import javafx.stage.Stage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Objects;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
import static org.cryptomator.common.Constants.CRYPTOMATOR_FILENAME_GLOB;
|
||||
@@ -40,12 +42,10 @@ public class ChooseExistingVaultController implements FxController {
|
||||
private final ObjectProperty<Vault> vault;
|
||||
private final VaultListManager vaultListManager;
|
||||
private final ResourceBundle resourceBundle;
|
||||
private final Settings settings;
|
||||
|
||||
private Image screenshot;
|
||||
private final ObservableValue<Image> screenshot;
|
||||
|
||||
@Inject
|
||||
ChooseExistingVaultController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_WELCOME) Lazy<Scene> welcomeScene, @FxmlScene(FxmlFile.ADDVAULT_SUCCESS) Lazy<Scene> successScene, FxApplicationWindows appWindows, ObjectProperty<Path> vaultPath, @AddVaultWizardWindow ObjectProperty<Vault> vault, VaultListManager vaultListManager, ResourceBundle resourceBundle, Settings settings) {
|
||||
ChooseExistingVaultController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_WELCOME) Lazy<Scene> welcomeScene, @FxmlScene(FxmlFile.ADDVAULT_SUCCESS) Lazy<Scene> successScene, FxApplicationWindows appWindows, ObjectProperty<Path> vaultPath, @AddVaultWizardWindow ObjectProperty<Vault> vault, VaultListManager vaultListManager, ResourceBundle resourceBundle, FxApplicationStyle applicationStyle) {
|
||||
this.window = window;
|
||||
this.welcomeScene = welcomeScene;
|
||||
this.successScene = successScene;
|
||||
@@ -54,16 +54,20 @@ public class ChooseExistingVaultController implements FxController {
|
||||
this.vault = vault;
|
||||
this.vaultListManager = vaultListManager;
|
||||
this.resourceBundle = resourceBundle;
|
||||
this.settings = settings;
|
||||
this.screenshot = applicationStyle.appliedThemeProperty().map(this::selectScreenshot);
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
private Image selectScreenshot(Theme theme) {
|
||||
String imageResourcePath;
|
||||
if (SystemUtils.IS_OS_MAC) {
|
||||
this.screenshot = new Image(getClass().getResource("/img/select-masterkey-mac"+(UiTheme.LIGHT == settings.theme().get()? "":"-dark")+".png").toString());
|
||||
imageResourcePath = switch (theme) {
|
||||
case LIGHT -> "/img/select-masterkey-mac.png";
|
||||
case DARK -> "/img/select-masterkey-mac-dark.png";
|
||||
};
|
||||
} else {
|
||||
this.screenshot = new Image(getClass().getResource("/img/select-masterkey-win.png").toString());
|
||||
imageResourcePath = "/img/select-masterkey-win.png";
|
||||
}
|
||||
return new Image((Objects.requireNonNull(getClass().getResource(imageResourcePath)).toString()));
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -92,8 +96,13 @@ public class ChooseExistingVaultController implements FxController {
|
||||
|
||||
/* Getter */
|
||||
|
||||
public Image getScreenshot() {
|
||||
public ObservableValue<Image> screenshotProperty() {
|
||||
return screenshot;
|
||||
}
|
||||
|
||||
public Image getScreenshot() {
|
||||
return screenshot.getValue();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ public enum FontAwesome5Icon {
|
||||
FILE("\uF15B"), //
|
||||
FILE_IMPORT("\uF56F"), //
|
||||
FOLDER_OPEN("\uF07C"), //
|
||||
FUNNEL("\uF0B0"), //
|
||||
HAND_HOLDING_HEART("\uF4BE"), //
|
||||
HEART("\uF004"), //
|
||||
HDD("\uF0A0"), //
|
||||
|
||||
@@ -12,6 +12,8 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.application.Application;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -24,9 +26,10 @@ public class FxApplicationStyle {
|
||||
private final Optional<UiAppearanceProvider> appearanceProvider;
|
||||
private final LicenseHolder licenseHolder;
|
||||
private final UiAppearanceListener systemInterfaceThemeListener = this::systemInterfaceThemeChanged;
|
||||
private final ObjectProperty<Theme> appliedTheme = new SimpleObjectProperty<>(Theme.LIGHT);
|
||||
|
||||
@Inject
|
||||
public FxApplicationStyle(Settings settings, Optional<UiAppearanceProvider> appearanceProvider, LicenseHolder licenseHolder){
|
||||
public FxApplicationStyle(Settings settings, Optional<UiAppearanceProvider> appearanceProvider, LicenseHolder licenseHolder) {
|
||||
this.settings = settings;
|
||||
this.appearanceProvider = appearanceProvider;
|
||||
this.licenseHolder = licenseHolder;
|
||||
@@ -91,6 +94,7 @@ public class FxApplicationStyle {
|
||||
} else {
|
||||
Application.setUserAgentStylesheet(stylesheet.toString());
|
||||
appearanceProvider.ifPresent(provider -> provider.adjustToTheme(Theme.LIGHT));
|
||||
appliedTheme.set(Theme.LIGHT);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,6 +107,11 @@ public class FxApplicationStyle {
|
||||
} else {
|
||||
Application.setUserAgentStylesheet(stylesheet.toString());
|
||||
appearanceProvider.ifPresent(provider -> provider.adjustToTheme(Theme.DARK));
|
||||
appliedTheme.set(Theme.DARK);
|
||||
}
|
||||
}
|
||||
|
||||
public ObjectProperty<Theme> appliedThemeProperty() {
|
||||
return appliedTheme;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,15 +8,34 @@ import org.cryptomator.ui.common.FxController;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.beans.binding.Binding;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.binding.BooleanExpression;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.ChoiceBox;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.scene.input.Clipboard;
|
||||
import javafx.scene.input.ClipboardContent;
|
||||
import javafx.util.StringConverter;
|
||||
import java.util.Arrays;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.cryptomator.cryptofs.health.api.DiagnosticResult.Severity;
|
||||
import static org.cryptomator.ui.health.Result.FixState.FIXABLE;
|
||||
import static org.cryptomator.ui.health.Result.FixState.FIXED;
|
||||
import static org.cryptomator.ui.health.Result.FixState.FIXING;
|
||||
import static org.cryptomator.ui.health.Result.FixState.FIX_FAILED;
|
||||
import static org.cryptomator.ui.health.Result.FixState.NOT_FIXABLE;
|
||||
|
||||
@HealthCheckScoped
|
||||
public class CheckDetailController implements FxController {
|
||||
|
||||
@@ -35,30 +54,48 @@ public class CheckDetailController implements FxController {
|
||||
private final Binding<Number> countOfCritSeverity;
|
||||
private final Binding<Boolean> warnOrCritsExist;
|
||||
private final ResultListCellFactory resultListCellFactory;
|
||||
private final ResultFixApplier resultFixApplier;
|
||||
private final ResourceBundle resourceBundle;
|
||||
|
||||
private final BooleanProperty fixAllInfoResultsExecuted;
|
||||
private final BooleanBinding fixAllInfoResultsPossible;
|
||||
private final ObjectProperty<Predicate<Result>> resultsFilter;
|
||||
|
||||
public ListView<Result> resultsListView;
|
||||
public ChoiceBox<DiagnosticResult.Severity> severityChoiceBox;
|
||||
public ChoiceBox<Result.FixState> fixStateChoiceBox;
|
||||
private Subscription resultSubscription;
|
||||
|
||||
@Inject
|
||||
public CheckDetailController(ObjectProperty<Check> selectedTask, ResultListCellFactory resultListCellFactory) {
|
||||
public CheckDetailController(ObjectProperty<Check> selectedTask, ResultListCellFactory resultListCellFactory, ResultFixApplier resultFixApplier, ResourceBundle resourceBundle) {
|
||||
this.resultListCellFactory = resultListCellFactory;
|
||||
this.resultFixApplier = resultFixApplier;
|
||||
this.resourceBundle = resourceBundle;
|
||||
this.results = EasyBind.wrapList(FXCollections.observableArrayList());
|
||||
this.check = selectedTask;
|
||||
this.checkState = selectedTask.flatMap(Check::stateProperty);
|
||||
this.checkName = selectedTask.map(Check::getName).orElse("");
|
||||
this.checkRunning = BooleanExpression.booleanExpression(checkState.map(Check.CheckState.RUNNING::equals).orElse(false));
|
||||
this.checkScheduled = BooleanExpression.booleanExpression(checkState.map(Check.CheckState.SCHEDULED::equals).orElse(false));
|
||||
this.checkSkipped =BooleanExpression.booleanExpression(checkState.map(Check.CheckState.SKIPPED::equals).orElse(false));
|
||||
this.checkSkipped = BooleanExpression.booleanExpression(checkState.map(Check.CheckState.SKIPPED::equals).orElse(false));
|
||||
this.checkSucceeded = BooleanExpression.booleanExpression(checkState.map(Check.CheckState.SUCCEEDED::equals).orElse(false));
|
||||
this.checkFailed = BooleanExpression.booleanExpression(checkState.map(Check.CheckState.ERROR::equals).orElse(false));
|
||||
this.checkCancelled = BooleanExpression.booleanExpression(checkState.map(Check.CheckState.CANCELLED::equals).orElse(false));
|
||||
this.checkFinished = checkSucceeded.or(checkFailed).or(checkCancelled);
|
||||
this.countOfWarnSeverity = results.reduce(countSeverity(DiagnosticResult.Severity.WARN));
|
||||
this.countOfCritSeverity = results.reduce(countSeverity(DiagnosticResult.Severity.CRITICAL));
|
||||
this.warnOrCritsExist = EasyBind.combine(checkSucceeded, countOfWarnSeverity, countOfCritSeverity, (suceeded, warns, crits) -> suceeded && (warns.longValue() > 0 || crits.longValue() > 0) );
|
||||
this.countOfWarnSeverity = results.reduce(countSeverity(Severity.WARN));
|
||||
this.countOfCritSeverity = results.reduce(countSeverity(Severity.CRITICAL));
|
||||
this.warnOrCritsExist = EasyBind.combine(checkSucceeded, countOfWarnSeverity, countOfCritSeverity, (suceeded, warns, crits) -> suceeded && (warns.longValue() > 0 || crits.longValue() > 0));
|
||||
this.fixAllInfoResultsExecuted = new SimpleBooleanProperty(false);
|
||||
this.fixAllInfoResultsPossible = Bindings.createBooleanBinding(() -> results.stream().anyMatch(this::isFixableInfoResult), results) //
|
||||
.and(fixAllInfoResultsExecuted.not());
|
||||
this.resultsFilter = new SimpleObjectProperty<>(r -> true);
|
||||
selectedTask.addListener(this::selectedTaskChanged);
|
||||
}
|
||||
|
||||
private boolean isFixableInfoResult(Result r) {
|
||||
return r.diagnosis().getSeverity() == Severity.INFO && r.getState() == FIXABLE;
|
||||
}
|
||||
|
||||
private void selectedTaskChanged(ObservableValue<? extends Check> observable, Check oldValue, Check newValue) {
|
||||
if (resultSubscription != null) {
|
||||
resultSubscription.unsubscribe();
|
||||
@@ -66,6 +103,8 @@ public class CheckDetailController implements FxController {
|
||||
if (newValue != null) {
|
||||
resultSubscription = EasyBind.bindContent(results, newValue.getResults());
|
||||
}
|
||||
severityChoiceBox.setValue(null);
|
||||
fixStateChoiceBox.setValue(null);
|
||||
}
|
||||
|
||||
private Function<Stream<? extends Result>, Long> countSeverity(DiagnosticResult.Severity severity) {
|
||||
@@ -74,8 +113,110 @@ public class CheckDetailController implements FxController {
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
resultsListView.setItems(results);
|
||||
resultsListView.setItems(results.filtered(resultsFilter));
|
||||
resultsListView.setCellFactory(resultListCellFactory);
|
||||
|
||||
severityChoiceBox.getItems().add(null);
|
||||
severityChoiceBox.getItems().addAll(Arrays.stream(DiagnosticResult.Severity.values()).toList());
|
||||
severityChoiceBox.setConverter(new SeverityStringifier());
|
||||
severityChoiceBox.setValue(null);
|
||||
|
||||
fixStateChoiceBox.getItems().add(null);
|
||||
fixStateChoiceBox.getItems().addAll(Arrays.stream(Result.FixState.values()).toList());
|
||||
fixStateChoiceBox.setConverter(new FixStateStringifier());
|
||||
fixStateChoiceBox.setValue(null);
|
||||
|
||||
resultsFilter.bind(Bindings.createObjectBinding(() -> this::filterResults, severityChoiceBox.valueProperty(), fixStateChoiceBox.valueProperty()));
|
||||
}
|
||||
|
||||
private boolean filterResults(Result r) {
|
||||
var desiredFixState = fixStateChoiceBox.getValue();
|
||||
var desiredSeverity = severityChoiceBox.getValue();
|
||||
return (desiredFixState == null || r.getState() == desiredFixState) && (desiredSeverity == null || r.diagnosis().getSeverity() == desiredSeverity);
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void fixAllInfoResults() {
|
||||
fixAllInfoResultsExecuted.setValue(true);
|
||||
results.stream().filter(this::isFixableInfoResult).forEach(resultFixApplier::fix);
|
||||
}
|
||||
|
||||
|
||||
@FXML
|
||||
public void copyResultDetails() {
|
||||
var result = resultsListView.getSelectionModel().getSelectedItem();
|
||||
if (result != null) {
|
||||
ClipboardContent clipboardContent = new ClipboardContent();
|
||||
clipboardContent.putString(result.diagnosis().toString());
|
||||
Clipboard.getSystemClipboard().setContent(clipboardContent);
|
||||
}
|
||||
}
|
||||
|
||||
/* -- Internal classes -- */
|
||||
|
||||
class SeverityStringifier extends StringConverter<Severity> {
|
||||
|
||||
@Override
|
||||
public String toString(Severity object) {
|
||||
if (object == null) {
|
||||
return resourceBundle.getString("health.result.severityFilter.all");
|
||||
}
|
||||
return switch (object) {
|
||||
case GOOD -> resourceBundle.getString("health.result.severityFilter.good");
|
||||
case INFO -> resourceBundle.getString("health.result.severityFilter.info");
|
||||
case WARN -> resourceBundle.getString("health.result.severityFilter.warn");
|
||||
case CRITICAL -> resourceBundle.getString("health.result.severityFilter.crit");
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Severity fromString(String string) {
|
||||
if (resourceBundle.getString("health.result.severityFilter.good").equals(string)) {
|
||||
return Severity.GOOD;
|
||||
} else if (resourceBundle.getString("health.result.severityFilter.info").equals(string)) {
|
||||
return Severity.INFO;
|
||||
} else if (resourceBundle.getString("health.result.severityFilter.warn").equals(string)) {
|
||||
return Severity.WARN;
|
||||
} else if (resourceBundle.getString("health.result.severityFilter.crit").equals(string)) {
|
||||
return Severity.CRITICAL;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FixStateStringifier extends StringConverter<Result.FixState> {
|
||||
|
||||
@Override
|
||||
public String toString(Result.FixState object) {
|
||||
if (object == null) {
|
||||
return resourceBundle.getString("health.result.fixStateFilter.all");
|
||||
}
|
||||
return switch (object) {
|
||||
case FIXABLE -> resourceBundle.getString("health.result.fixStateFilter.fixable");
|
||||
case NOT_FIXABLE -> resourceBundle.getString("health.result.fixStateFilter.notFixable");
|
||||
case FIXING -> resourceBundle.getString("health.result.fixStateFilter.fixing");
|
||||
case FIXED -> resourceBundle.getString("health.result.fixStateFilter.fixed");
|
||||
case FIX_FAILED -> resourceBundle.getString("health.result.fixStateFilter.fixFailed");
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result.FixState fromString(String string) {
|
||||
if (resourceBundle.getString("health.result.fixStateFilter.fixable").equals(string)) {
|
||||
return FIXABLE;
|
||||
} else if (resourceBundle.getString("health.result.fixStateFilter.notFixable").equals(string)) {
|
||||
return NOT_FIXABLE;
|
||||
} else if (resourceBundle.getString("health.result.fixStateFilter.fixing").equals(string)) {
|
||||
return FIXING;
|
||||
} else if (resourceBundle.getString("health.result.fixStateFilter.fixed").equals(string)) {
|
||||
return FIXED;
|
||||
} else if (resourceBundle.getString("health.result.fixStateFilter.fixFailed").equals(string)) {
|
||||
return FIX_FAILED;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Getter/Setter */
|
||||
@@ -175,4 +316,12 @@ public class CheckDetailController implements FxController {
|
||||
public Check getCheck() {
|
||||
return check.get();
|
||||
}
|
||||
|
||||
public ObservableValue<Boolean> fixAllInfoResultsPossibleProperty() {
|
||||
return fixAllInfoResultsPossible;
|
||||
}
|
||||
|
||||
public boolean getFixAllInfoResultsPossible() {
|
||||
return fixAllInfoResultsPossible.getValue();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ public class CheckExecutor {
|
||||
try (var masterkeyClone = masterkey.copy(); //
|
||||
var cryptor = CryptorProvider.forScheme(vaultConfig.getCipherCombo()).provide(masterkeyClone, csprng)) {
|
||||
c.getHealthCheck().check(vaultPath, vaultConfig, masterkeyClone, cryptor, diagnosis -> {
|
||||
Platform.runLater(() -> c.getResults().add(Result.create(diagnosis)));
|
||||
Platform.runLater(() -> c.getResults().add(Result.create(diagnosis, vaultPath, vaultConfig, masterkeyClone, cryptor)));
|
||||
highestResultSeverity = Comparators.max(highestResultSeverity, diagnosis.getSeverity());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ public class CheckStateIconView extends FontAwesome5IconView {
|
||||
this.severity = EasyBind.wrapNullable(check).mapObservable(Check::highestResultSeverityProperty).asOrdinary();
|
||||
this.glyph.bind(Bindings.createObjectBinding(this::glyphForState, state, severity));
|
||||
this.subscriptions = List.of( //
|
||||
EasyBind.includeWhen(getStyleClass(), "glyph-icon-muted", Bindings.equal(state, Check.CheckState.SKIPPED).or(Bindings.equal(state, Check.CheckState.CANCELLED))), //
|
||||
EasyBind.includeWhen(getStyleClass(), "glyph-icon-muted", Bindings.equal(state, Check.CheckState.SKIPPED).or(Bindings.equal(state, Check.CheckState.CANCELLED)).or(Bindings.equal(severity, DiagnosticResult.Severity.INFO))), //
|
||||
EasyBind.includeWhen(getStyleClass(), "glyph-icon-primary", Bindings.equal(severity, DiagnosticResult.Severity.GOOD)), //
|
||||
EasyBind.includeWhen(getStyleClass(), "glyph-icon-orange", Bindings.equal(severity, DiagnosticResult.Severity.WARN).or(Bindings.equal(severity, DiagnosticResult.Severity.CRITICAL))), //
|
||||
EasyBind.includeWhen(getStyleClass(), "glyph-icon-red", Bindings.equal(state, Check.CheckState.ERROR)) //
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
package org.cryptomator.ui.health;
|
||||
|
||||
import org.cryptomator.cryptofs.VaultConfig;
|
||||
import org.cryptomator.cryptofs.health.api.DiagnosticResult;
|
||||
import org.cryptomator.cryptolib.api.Cryptor;
|
||||
import org.cryptomator.cryptolib.api.Masterkey;
|
||||
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import java.nio.file.Path;
|
||||
|
||||
record Result(DiagnosticResult diagnosis, ObjectProperty<FixState> fixState) {
|
||||
|
||||
@@ -16,8 +20,8 @@ record Result(DiagnosticResult diagnosis, ObjectProperty<FixState> fixState) {
|
||||
FIX_FAILED
|
||||
}
|
||||
|
||||
public static Result create(DiagnosticResult diagnosis) {
|
||||
FixState initialState = diagnosis.getSeverity() == DiagnosticResult.Severity.WARN ? FixState.FIXABLE : FixState.NOT_FIXABLE;
|
||||
public static Result create(DiagnosticResult diagnosis, Path vaultPath, VaultConfig config, Masterkey masterkey, Cryptor cryptor) {
|
||||
FixState initialState = diagnosis.getFix(vaultPath, config, masterkey, cryptor).map( _f -> FixState.FIXABLE).orElse(FixState.NOT_FIXABLE);
|
||||
return new Result(diagnosis, new SimpleObjectProperty<>(initialState));
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,8 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||
@HealthCheckScoped
|
||||
class ResultFixApplier {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ResultFixApplier.class);
|
||||
|
||||
private final Path vaultPath;
|
||||
private final SecureRandom csprng;
|
||||
private final Masterkey masterkey;
|
||||
@@ -40,25 +42,34 @@ class ResultFixApplier {
|
||||
|
||||
public CompletionStage<Void> fix(Result result) {
|
||||
Preconditions.checkArgument(result.getState() == Result.FixState.FIXABLE);
|
||||
result.setState(Result.FixState.FIXING);
|
||||
return CompletableFuture.runAsync(() -> fix(result.diagnosis()), sequentialExecutor)
|
||||
return CompletableFuture.runAsync(() -> result.setState(Result.FixState.FIXING), Platform::runLater) //
|
||||
.thenRunAsync(() -> fix(result.diagnosis()), sequentialExecutor) //
|
||||
.whenCompleteAsync((unused, throwable) -> {
|
||||
var fixed = throwable == null ? Result.FixState.FIXED : Result.FixState.FIX_FAILED;
|
||||
result.setState(fixed);
|
||||
final Result.FixState s;
|
||||
if (throwable == null) {
|
||||
LOG.debug("Fix for {} applied successful.", result.diagnosis().getClass().getName());
|
||||
s = Result.FixState.FIXED;
|
||||
} else {
|
||||
LOG.error("Failed to apply fix for {}", result.diagnosis().getClass().getName(), throwable);
|
||||
s = Result.FixState.FIX_FAILED;
|
||||
}
|
||||
result.setState(s);
|
||||
}, Platform::runLater);
|
||||
}
|
||||
|
||||
public void fix(DiagnosticResult diagnosis) {
|
||||
Preconditions.checkArgument(diagnosis.getSeverity() == DiagnosticResult.Severity.WARN, "Unfixable result");
|
||||
private void fix(DiagnosticResult diagnosis) {
|
||||
try (var masterkeyClone = masterkey.copy(); //
|
||||
var cryptor = CryptorProvider.forScheme(vaultConfig.getCipherCombo()).provide(masterkeyClone, csprng)) {
|
||||
diagnosis.fix(vaultPath, vaultConfig, masterkeyClone, cryptor);
|
||||
diagnosis.getFix(vaultPath, vaultConfig, masterkeyClone, cryptor) //
|
||||
.orElseThrow(() -> new IllegalStateException("No fix for diagnosis " + diagnosis.getClass().getName() + " implemented.")) //
|
||||
.apply();
|
||||
} catch (Exception e) {
|
||||
throw new FixFailedException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static class FixFailedException extends CompletionException {
|
||||
|
||||
private FixFailedException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.binding.ObjectBinding;
|
||||
@@ -34,7 +33,6 @@ public class ResultListCellController implements FxController {
|
||||
private static final FontAwesome5Icon WARN_ICON = FontAwesome5Icon.EXCLAMATION_TRIANGLE;
|
||||
private static final FontAwesome5Icon CRIT_ICON = FontAwesome5Icon.TIMES;
|
||||
|
||||
private final Logger LOG = LoggerFactory.getLogger(ResultListCellController.class);
|
||||
|
||||
private final ObjectProperty<Result> result;
|
||||
private final ObservableValue<DiagnosticResult.Severity> severity;
|
||||
@@ -42,17 +40,17 @@ public class ResultListCellController implements FxController {
|
||||
private final ResultFixApplier fixApplier;
|
||||
private final ObservableValue<Result.FixState> fixState;
|
||||
private final ObjectBinding<FontAwesome5Icon> severityGlyph;
|
||||
private final ObjectBinding<FontAwesome5Icon> fixGlyph;
|
||||
private final BooleanBinding fixable;
|
||||
private final BooleanBinding fixing;
|
||||
private final BooleanBinding fixed;
|
||||
private final BooleanBinding fixFailed;
|
||||
private final BooleanBinding fixRunningOrDone;
|
||||
private final ObservableValue<FontAwesome5Icon> fixGlyph;
|
||||
private final List<Subscription> subscriptions;
|
||||
private final Tooltip fixSuccess;
|
||||
private final Tooltip fixFail;
|
||||
|
||||
private final Tooltip fixStateTip;
|
||||
private final Tooltip severityTip;
|
||||
private AutoAnimator fixRunningRotator;
|
||||
private final ResourceBundle resourceBundle;
|
||||
|
||||
/* FXML */
|
||||
public FontAwesome5IconView severityView;
|
||||
@@ -60,23 +58,54 @@ public class ResultListCellController implements FxController {
|
||||
|
||||
@Inject
|
||||
public ResultListCellController(ResultFixApplier fixApplier, ResourceBundle resourceBundle) {
|
||||
this.resourceBundle = resourceBundle;
|
||||
this.result = new SimpleObjectProperty<>(null);
|
||||
this.severity = result.map(Result::diagnosis).map(DiagnosticResult::getSeverity);
|
||||
this.description = result.map(Result::getDescription).orElse("");
|
||||
this.fixApplier = fixApplier;
|
||||
this.fixState = result.flatMap(Result::fixState);
|
||||
this.severityGlyph = Bindings.createObjectBinding(this::getSeverityGlyph, result);
|
||||
this.fixGlyph = Bindings.createObjectBinding(this::getFixGlyph, fixState);
|
||||
this.fixable = Bindings.createBooleanBinding(this::isFixable, fixState);
|
||||
this.fixing = Bindings.createBooleanBinding(this::isFixing, fixState);
|
||||
this.fixed = Bindings.createBooleanBinding(this::isFixed, fixState);
|
||||
this.fixFailed = Bindings.createBooleanBinding(this::isFixFailed, fixState);
|
||||
this.fixRunningOrDone = fixing.or(fixed).or(fixFailed);
|
||||
this.fixGlyph = fixState.map(this::getFixGlyph);
|
||||
this.subscriptions = new ArrayList<>();
|
||||
this.fixSuccess = new Tooltip(resourceBundle.getString("health.fix.successTip"));
|
||||
this.fixFail = new Tooltip(resourceBundle.getString("health.fix.failTip"));
|
||||
fixSuccess.setShowDelay(Duration.millis(100));
|
||||
fixFail.setShowDelay(Duration.millis(100));
|
||||
|
||||
this.fixStateTip = new Tooltip();
|
||||
fixStateTip.textProperty().bind(fixState.map(this::getFixStateDescription));
|
||||
fixStateTip.setShowDelay(Duration.millis(100));
|
||||
|
||||
this.severityTip = new Tooltip();
|
||||
severityTip.textProperty().bind(severity.map(this::getSeverityDescription));
|
||||
severityTip.setShowDelay(Duration.millis(150));
|
||||
}
|
||||
|
||||
public FontAwesome5Icon getFixGlyph(Result.FixState state) {
|
||||
return switch (state) {
|
||||
case FIXING -> FontAwesome5Icon.SPINNER;
|
||||
case FIXED -> FontAwesome5Icon.CHECK;
|
||||
case FIX_FAILED -> FontAwesome5Icon.TIMES;
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
private String getFixStateDescription(Result.FixState fixState) {
|
||||
return switch (fixState) {
|
||||
case FIXED -> resourceBundle.getString("health.fix.successTip");
|
||||
case FIX_FAILED -> resourceBundle.getString("health.fix.failTip");
|
||||
default -> "";
|
||||
};
|
||||
}
|
||||
|
||||
private String getSeverityDescription(DiagnosticResult.Severity severity) {
|
||||
return resourceBundle.getString(switch (severity) {
|
||||
case GOOD -> "health.result.severityTip.good";
|
||||
case INFO -> "health.result.severityTip.info";
|
||||
case WARN -> "health.result.severityTip.warn";
|
||||
case CRITICAL -> "health.result.severityTip.crit";
|
||||
});
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -93,22 +122,19 @@ public class ResultListCellController implements FxController {
|
||||
.onCondition(fixing) //
|
||||
.afterStop(() -> fixView.setRotate(0)) //
|
||||
.build();
|
||||
fixState.addListener(((observable, oldValue, newValue) -> {
|
||||
if (newValue == Result.FixState.FIXED || newValue == Result.FixState.FIX_FAILED) {
|
||||
Tooltip.install(fixView, fixStateTip);
|
||||
}
|
||||
}));
|
||||
Tooltip.install(severityView, severityTip);
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void fix() {
|
||||
Result r = result.get();
|
||||
if (r != null) {
|
||||
fixApplier.fix(r).whenCompleteAsync(this::fixFinished, Platform::runLater);
|
||||
}
|
||||
}
|
||||
|
||||
private void fixFinished(Void unused, Throwable exception) {
|
||||
if (exception != null) {
|
||||
LOG.error("Failed to apply fix", exception);
|
||||
Tooltip.install(fixView, fixFail);
|
||||
} else {
|
||||
Tooltip.install(fixView, fixSuccess);
|
||||
fixApplier.fix(r);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,20 +178,12 @@ public class ResultListCellController implements FxController {
|
||||
};
|
||||
}
|
||||
|
||||
public ObjectBinding<FontAwesome5Icon> fixGlyphProperty() {
|
||||
public ObservableValue<FontAwesome5Icon> fixGlyphProperty() {
|
||||
return fixGlyph;
|
||||
}
|
||||
|
||||
public FontAwesome5Icon getFixGlyph() {
|
||||
if (fixState.getValue() == null) {
|
||||
return null;
|
||||
}
|
||||
return switch (fixState.getValue()) {
|
||||
case NOT_FIXABLE, FIXABLE -> null;
|
||||
case FIXING -> FontAwesome5Icon.SPINNER;
|
||||
case FIXED -> FontAwesome5Icon.CHECK;
|
||||
case FIX_FAILED -> FontAwesome5Icon.TIMES;
|
||||
};
|
||||
return fixGlyph.getValue();
|
||||
}
|
||||
|
||||
public BooleanBinding fixableProperty() {
|
||||
|
||||
@@ -9,8 +9,10 @@ import javax.inject.Inject;
|
||||
import javafx.animation.Animation;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.DoubleBinding;
|
||||
import javafx.beans.binding.LongBinding;
|
||||
import javafx.beans.property.LongProperty;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.fxml.FXML;
|
||||
@@ -21,6 +23,7 @@ import javafx.scene.chart.XYChart.Series;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.util.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
@VaultStatisticsScoped
|
||||
public class VaultStatisticsController implements FxController {
|
||||
@@ -32,6 +35,7 @@ public class VaultStatisticsController implements FxController {
|
||||
private final VaultStats stats;
|
||||
private final Series<Number, Number> readData;
|
||||
private final Series<Number, Number> writeData;
|
||||
private final Series<Number, Number> accessData;
|
||||
private final Timeline ioAnimation;
|
||||
private final LongBinding bpsRead;
|
||||
private final LongBinding bpsWritten;
|
||||
@@ -44,15 +48,20 @@ public class VaultStatisticsController implements FxController {
|
||||
private final LongBinding totalBytesDecrypted;
|
||||
private final LongBinding filesRead;
|
||||
private final LongBinding filesWritten;
|
||||
private final LongBinding filesAccessed;
|
||||
private final LongBinding totalFilesAccessed;
|
||||
private final LongBinding bpsEncrypted;
|
||||
private final LongBinding bpsDecrypted;
|
||||
|
||||
public AreaChart<Number, Number> readChart;
|
||||
public AreaChart<Number, Number> writeChart;
|
||||
public AreaChart<Number, Number> accessChart;
|
||||
public NumberAxis readChartXAxis;
|
||||
public NumberAxis readChartYAxis;
|
||||
public NumberAxis writeChartXAxis;
|
||||
public NumberAxis writeChartYAxis;
|
||||
public NumberAxis accessChartXAxis;
|
||||
public NumberAxis accessChartYAxis;
|
||||
|
||||
@Inject
|
||||
public VaultStatisticsController(VaultStatisticsComponent component, @VaultStatisticsWindow Stage window, @VaultStatisticsWindow Vault vault) {
|
||||
@@ -60,6 +69,7 @@ public class VaultStatisticsController implements FxController {
|
||||
this.stats = vault.getStats();
|
||||
this.readData = new Series<>();
|
||||
this.writeData = new Series<>();
|
||||
this.accessData = new Series<>();
|
||||
this.bpsRead = WeakBindings.bindLong(stats.bytesPerSecondReadProperty());
|
||||
this.bpsWritten = WeakBindings.bindLong(stats.bytesPerSecondWrittenProperty());
|
||||
this.cacheHitRate = WeakBindings.bindDouble(stats.cacheHitRateProperty());
|
||||
@@ -71,11 +81,13 @@ public class VaultStatisticsController implements FxController {
|
||||
this.totalBytesEncrypted = WeakBindings.bindLong(stats.totalBytesEncryptedProperty());
|
||||
this.filesRead = WeakBindings.bindLong(stats.filesRead());
|
||||
this.filesWritten = WeakBindings.bindLong(stats.filesWritten());
|
||||
this.filesAccessed = WeakBindings.bindLong(stats.filesAccessed());
|
||||
this.totalFilesAccessed = WeakBindings.bindLong(stats.totalFilesAccessed());
|
||||
this.bpsEncrypted = WeakBindings.bindLong(stats.bytesPerSecondEncryptedProperty());
|
||||
this.bpsDecrypted = WeakBindings.bindLong(stats.bytesPerSecondDecryptedProperty());
|
||||
|
||||
this.ioAnimation = new Timeline(); //TODO Research better timer
|
||||
ioAnimation.getKeyFrames().add(new KeyFrame(Duration.seconds(IO_SAMPLING_INTERVAL), new IoSamplingAnimationHandler(readData, writeData)));
|
||||
ioAnimation.getKeyFrames().add(new KeyFrame(Duration.seconds(IO_SAMPLING_INTERVAL), new IoSamplingAnimationHandler(readData, writeData, accessData)));
|
||||
ioAnimation.setCycleCount(Animation.INDEFINITE);
|
||||
ioAnimation.play();
|
||||
|
||||
@@ -89,6 +101,7 @@ public class VaultStatisticsController implements FxController {
|
||||
public void initialize() {
|
||||
readChart.getData().addAll(readData);
|
||||
writeChart.getData().addAll(writeData);
|
||||
accessChart.getData().addAll(accessData);
|
||||
}
|
||||
|
||||
private class IoSamplingAnimationHandler implements EventHandler<ActionEvent> {
|
||||
@@ -96,16 +109,21 @@ public class VaultStatisticsController implements FxController {
|
||||
private long step = IO_SAMPLING_STEPS;
|
||||
private final Series<Number, Number> decryptedBytesRead;
|
||||
private final Series<Number, Number> encryptedBytesWrite;
|
||||
private final Series<Number, Number> accessedFiles;
|
||||
private final long[] maxBuf = new long[IO_SAMPLING_STEPS];
|
||||
private final long[] maxAccessBuf = new long[IO_SAMPLING_STEPS];
|
||||
|
||||
public IoSamplingAnimationHandler(Series<Number, Number> readData, Series<Number, Number> writeData) {
|
||||
|
||||
public IoSamplingAnimationHandler(Series<Number, Number> readData, Series<Number, Number> writeData, Series<Number, Number> accessData) {
|
||||
this.decryptedBytesRead = readData;
|
||||
this.encryptedBytesWrite = writeData;
|
||||
this.accessedFiles = accessData;
|
||||
|
||||
// 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));
|
||||
accessedFiles.getData().add(new Data<>(i, 0));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,17 +132,22 @@ public class VaultStatisticsController implements FxController {
|
||||
final long currentStep = step++;
|
||||
final long decBytes = stats.bytesPerSecondReadProperty().get();
|
||||
final long encBytes = stats.bytesPerSecondWrittenProperty().get();
|
||||
final long accFiles = stats.filesAccessed().get();
|
||||
|
||||
maxBuf[(int) currentStep % IO_SAMPLING_STEPS] = Math.max(decBytes, encBytes);
|
||||
long allTimeMax = Arrays.stream(maxBuf).max().orElse(0l);
|
||||
long allTimeMax = Arrays.stream(maxBuf).max().orElse(0L);
|
||||
maxAccessBuf[(int) currentStep % IO_SAMPLING_STEPS] = accFiles;
|
||||
long allTimeMaxAccessedFiles = Arrays.stream(maxAccessBuf).max().orElse(0L);
|
||||
|
||||
// remove oldest value:
|
||||
decryptedBytesRead.getData().remove(0);
|
||||
encryptedBytesWrite.getData().remove(0);
|
||||
accessedFiles.getData().remove(0);
|
||||
|
||||
// add latest value:
|
||||
decryptedBytesRead.getData().add(new Data<>(currentStep, decBytes));
|
||||
encryptedBytesWrite.getData().add(new Data<>(currentStep, encBytes));
|
||||
accessedFiles.getData().add(new Data<>(currentStep, accFiles));
|
||||
|
||||
// adjust ranges:
|
||||
readChartXAxis.setLowerBound(currentStep - IO_SAMPLING_STEPS * 1.0);
|
||||
@@ -133,6 +156,9 @@ public class VaultStatisticsController implements FxController {
|
||||
writeChartXAxis.setLowerBound(currentStep - IO_SAMPLING_STEPS * 1.0);
|
||||
writeChartXAxis.setUpperBound(currentStep);
|
||||
writeChartYAxis.setUpperBound(allTimeMax);
|
||||
accessChartXAxis.setLowerBound(currentStep - IO_SAMPLING_STEPS * 1.0);
|
||||
accessChartXAxis.setUpperBound(currentStep);
|
||||
accessChartYAxis.setUpperBound(allTimeMaxAccessedFiles);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,4 +233,12 @@ public class VaultStatisticsController implements FxController {
|
||||
public LongBinding filesWrittenProperty() {return filesWritten;}
|
||||
|
||||
public long getFilesWritten() {return filesWritten.get();}
|
||||
|
||||
public LongBinding filesAccessedProperty() {return filesAccessed;}
|
||||
|
||||
public long getFilesAccessed() {return filesAccessed.get();}
|
||||
|
||||
public LongBinding totalFilesAccessedProperty() {return totalFilesAccessed;}
|
||||
|
||||
public long getTotalFilesAccessed() {return totalFilesAccessed.get();}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.cryptomator.ui.traymenu;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.integrations.common.CheckAvailability;
|
||||
import org.cryptomator.integrations.common.Priority;
|
||||
@@ -19,6 +20,8 @@ import java.awt.PopupMenu;
|
||||
import java.awt.SystemTray;
|
||||
import java.awt.Toolkit;
|
||||
import java.awt.TrayIcon;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.List;
|
||||
|
||||
@CheckAvailability
|
||||
@@ -28,6 +31,7 @@ public class AwtTrayMenuController implements TrayMenuController {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AwtTrayMenuController.class);
|
||||
|
||||
private final PopupMenu menu = new PopupMenu();
|
||||
private TrayIcon trayIcon;
|
||||
|
||||
@CheckAvailability
|
||||
public static boolean isAvailable() {
|
||||
@@ -37,7 +41,7 @@ public class AwtTrayMenuController implements TrayMenuController {
|
||||
@Override
|
||||
public void showTrayIcon(byte[] rawImageData, Runnable defaultAction, String tooltip) throws TrayMenuException {
|
||||
var image = Toolkit.getDefaultToolkit().createImage(rawImageData);
|
||||
var trayIcon = new TrayIcon(image, tooltip, menu);
|
||||
trayIcon = new TrayIcon(image, tooltip, menu);
|
||||
|
||||
trayIcon.setImageAutoSize(true);
|
||||
if (SystemUtils.IS_OS_WINDOWS) {
|
||||
@@ -59,8 +63,14 @@ public class AwtTrayMenuController implements TrayMenuController {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBeforeOpenMenu(Runnable runnable) {
|
||||
|
||||
public void onBeforeOpenMenu(Runnable listener) {
|
||||
Preconditions.checkNotNull(this.trayIcon);
|
||||
this.trayIcon.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
listener.run();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void addChildren(Menu menu, List<TrayMenuItem> items) {
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.cryptomator.ui.traymenu;
|
||||
import com.google.common.base.Preconditions;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultListManager;
|
||||
import org.cryptomator.integrations.tray.ActionItem;
|
||||
import org.cryptomator.integrations.tray.SeparatorItem;
|
||||
import org.cryptomator.integrations.tray.SubMenuItem;
|
||||
@@ -63,6 +64,11 @@ public class TrayMenuBuilder {
|
||||
|
||||
try (var image = getClass().getResourceAsStream(SystemUtils.IS_OS_MAC_OSX ? TRAY_ICON_MAC : TRAY_ICON)) {
|
||||
trayMenu.showTrayIcon(image.readAllBytes(), this::showMainWindow, "Cryptomator");
|
||||
trayMenu.onBeforeOpenMenu(() -> {
|
||||
for (Vault vault : vaults) {
|
||||
VaultListManager.redetermineVaultState(vault);
|
||||
}
|
||||
});
|
||||
rebuildMenu();
|
||||
initialized = true;
|
||||
} catch (IOException e) {
|
||||
|
||||
Reference in New Issue
Block a user