implement event views for all filesystemevents

This commit is contained in:
Armin Schrenk
2025-02-26 18:36:52 +01:00
parent 2239a3e6a3
commit b76a7d6895
3 changed files with 112 additions and 65 deletions

View File

@@ -1,30 +1,35 @@
package org.cryptomator.ui.eventview;
import org.cryptomator.common.ObservableUtil;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.cryptofs.event.ConflictResolutionFailedEvent;
import org.cryptomator.cryptofs.event.ConflictResolvedEvent;
import org.cryptomator.cryptofs.event.FilesystemEvent;
import org.cryptomator.cryptofs.event.DecryptionFailedEvent;
import org.cryptomator.event.VaultEvent;
import org.cryptomator.integrations.revealpath.RevealFailedException;
import org.cryptomator.integrations.revealpath.RevealPathService;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.controls.FontAwesome5Icon;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.geometry.Side;
import javafx.scene.control.Button;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import java.nio.file.Path;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.function.Function;
public class EventListCellController implements FxController {
@@ -34,14 +39,16 @@ public class EventListCellController implements FxController {
private final Optional<RevealPathService> revealService;
private final ResourceBundle resourceBundle;
private final ObjectProperty<VaultEvent> event;
private final StringProperty eventMessage;
private final StringProperty eventDescription;
private final ObjectProperty<FontAwesome5Icon> eventIcon;
private final ObservableValue<Boolean> vaultUnlocked;
private final ObservableValue<String> message;
private final ObservableValue<String> description;
private final ObservableValue<FontAwesome5Icon> icon;
@FXML
ContextMenu basicEventActions;
@FXML
ContextMenu conflictResoledEventActions;
ContextMenu eventActions;
@FXML
Button eventActionsButton;
@@ -51,72 +58,118 @@ public class EventListCellController implements FxController {
this.revealService = revealService;
this.resourceBundle = resourceBundle;
this.event = new SimpleObjectProperty<>(null);
this.message = ObservableUtil.mapWithDefault(event, e -> e.getClass().getName(), "");
this.description = ObservableUtil.mapWithDefault(event, this::selectDescription, "");
this.icon = ObservableUtil.mapWithDefault(event, this::selectIcon, FontAwesome5Icon.BELL);
event.addListener(this::hideContextMenus);
this.eventMessage = new SimpleStringProperty();
this.eventDescription = new SimpleStringProperty();
this.eventIcon = new SimpleObjectProperty<>();
this.vaultUnlocked = ObservableUtil.mapWithDefault(event.flatMap(e -> e.v().unlockedProperty()), Function.identity(), false);
this.message = Bindings.createStringBinding(this::selectMessage, vaultUnlocked, eventMessage);
this.description = Bindings.createStringBinding(this::selectDescription, vaultUnlocked, eventDescription);
this.icon = Bindings.createObjectBinding(this::selectIcon, vaultUnlocked, eventIcon);
}
private void hideContextMenus(Observable observable, VaultEvent oldValue, VaultEvent newValue) {
basicEventActions.hide();
conflictResoledEventActions.hide();
}
public void setEvent(VaultEvent item) {
public void setEvent(@NotNull VaultEvent item) {
event.set(item);
eventDescription.setValue("Vault " + item.v().getDisplayName());
eventActions.hide();
eventActions.getItems().clear();
addAction("generic.action.dismiss", () -> events.remove(item));
switch (item.actualEvent()) {
case ConflictResolvedEvent fse -> this.adjustToConflictResolvedEvent(fse);
case ConflictResolutionFailedEvent fse -> this.adjustToConflictEvent(fse);
case DecryptionFailedEvent fse -> this.adjustToDecryptionFailedEvent(fse);
}
}
private FontAwesome5Icon selectIcon(VaultEvent e) {
return FontAwesome5Icon.FILE;
private void adjustToConflictResolvedEvent(ConflictResolvedEvent cre) {
eventIcon.setValue(FontAwesome5Icon.FILE);
eventMessage.setValue("Resolved conflict, new file is " + cre.resolvedCleartextPath()); //TODO:localize
if (revealService.isPresent()) {
addAction("event.conflictResolved.showDecrypted", () -> this.showResolvedConflict(cre));
}
}
private String selectDescription(VaultEvent e) {
return switch (e.actualEvent()) {
case ConflictResolvedEvent _-> "A conflict is resolved!";
default -> "Something happened";
};
private void adjustToConflictEvent(ConflictResolutionFailedEvent cfe) {
eventIcon.setValue(FontAwesome5Icon.TIMES);
eventMessage.setValue("Failed to resolve conflict for " + cfe.conflictingCiphertextPath()); //TODO:localize
if (revealService.isPresent()) {
addAction("event.conflictFailed.showEncrypted", () -> reveal(cfe.conflictingCiphertextPath()));
}
}
private void adjustToDecryptionFailedEvent(DecryptionFailedEvent dfe) {
eventIcon.setValue(FontAwesome5Icon.BAN);
eventMessage.setValue("Cannot decrypt " + dfe.ciphertextPath()); //TODO:localize
if (revealService.isPresent()) {
addAction("event.decryptionFailed.showEncrypted", () -> reveal(dfe.ciphertextPath()));
}
}
private void addAction(String localizationKey, Runnable action) {
var entry = new MenuItem(resourceBundle.getString(localizationKey));
entry.getStyleClass().addLast("add-vault-menu-item");
entry.setOnAction(_ -> action.run());
eventActions.getItems().addLast(entry);
}
private FontAwesome5Icon selectIcon() {
if (vaultUnlocked.getValue()) {
return eventIcon.getValue();
} else {
return FontAwesome5Icon.LOCK;
}
}
private String selectMessage() {
if (vaultUnlocked.getValue()) {
return eventMessage.getValue();
} else {
var e = event.getValue();
return "Event for " + (e != null ? e.v().getDisplayName() : ""); //TODO: localize
}
}
private String selectDescription() {
if (vaultUnlocked.getValue()) {
return eventDescription.getValue();
} else {
return "Unlock the vault to display details."; //TODO: localize
}
}
@FXML
public void toggleEventActionsMenu() {
var e = event.get();
if (e != null) {
var contextMenu = switch (e.actualEvent()) {
case ConflictResolvedEvent _ -> conflictResoledEventActions;
default -> basicEventActions;
};
if (contextMenu.isShowing()) {
contextMenu.hide();
if (eventActions.isShowing()) {
eventActions.hide();
} else {
contextMenu.show(eventActionsButton, Side.BOTTOM, 0.0, 0.0);
eventActions.show(eventActionsButton, Side.BOTTOM, 0.0, 0.0);
}
}
}
@FXML
public void dismissEvent() {
events.remove(event.getValue());
}
@FXML
public void showResolvedConflict() {
if (event.getValue() instanceof VaultEvent(_, Vault v, FilesystemEvent fse) && fse instanceof ConflictResolvedEvent cre) {
if (v.isUnlocked()) {
var mountUri = v.getMountPoint().uri();
var internalPath = cre.resolvedCleartextPath().toString().substring(1);
var actualPath = Path.of(mountUri.getPath().concat(internalPath).substring(1));
var s = revealService.orElseThrow(() -> new IllegalStateException("Function requiring revealService called, but service not available"));
try {
s.reveal(actualPath);
} catch (RevealFailedException e) {
LOG.warn("Failed to show resolved file conflict", e);
}
}
private void showResolvedConflict(ConflictResolvedEvent cre) {
var v = event.getValue().v();
if (v.isUnlocked()) {
var mountUri = v.getMountPoint().uri();
var internalPath = cre.resolvedCleartextPath().toString().substring(1);
var actualPath = Path.of(mountUri.getPath().concat(internalPath).substring(1));
reveal(actualPath);
}
}
private void reveal(Path p) {
try {
revealService.orElseThrow(() -> new IllegalStateException("Function requiring revealService called, but service not available")) //
.reveal(p);
} catch (RevealFailedException e) {
LOG.warn("Failed to show path {}",p, e);
}
}
//-- property accessors --
public ObservableValue<String> messageProperty() {
return message;
@@ -142,8 +195,4 @@ public class EventListCellController implements FxController {
return icon.getValue();
}
public boolean isRevealServicePresent() {
return revealService.isPresent();
}
}

View File

@@ -33,16 +33,8 @@
</Button>
<fx:define>
<ContextMenu fx:id="basicEventActions">
<items>
<MenuItem styleClass="add-vault-menu-item" text="Dismiss" onAction="#dismissEvent" />
</items>
</ContextMenu>
<ContextMenu fx:id="conflictResoledEventActions">
<items>
<MenuItem styleClass="add-vault-menu-item" text="Dismiss" onAction="#dismissEvent" />
<MenuItem styleClass="add-vault-menu-item" text="Show" onAction="#showResolvedConflict" visible="${controller.revealServicePresent}"/>
</items>
<ContextMenu fx:id="eventActions">
<!-- entries are added in the controller -->
</ContextMenu>
</fx:define>
</HBox>

View File

@@ -1,7 +1,13 @@
# Locale Specific CSS files such as CJK, RTL,...
additionalStyleSheets=
#Test
event.conflictResolved.showDecrypted=Show decrypted file
event.conflictFailed.showEncrypted=Show encrypted file
event.decryptionFailed.showEncrypted=Show encrypted file
# Generics
generic.action.dismiss=Dismiss
## Button
generic.button.apply=Apply
generic.button.back=Back