mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-19 11:11:28 +00:00
implement event views for all filesystemevents
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user