Browse Source

implement event views for all filesystemevents

Armin Schrenk 3 months ago
parent
commit
b76a7d6895

+ 100 - 51
src/main/java/org/cryptomator/ui/eventview/EventListCellController.java

@@ -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);
 	}
 
+	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 void hideContextMenus(Observable observable, VaultEvent oldValue, VaultEvent newValue) {
-		basicEventActions.hide();
-		conflictResoledEventActions.hide();
+	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));
+		}
 	}
 
-	public void setEvent(VaultEvent item) {
-		event.set(item);
+	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 FontAwesome5Icon selectIcon(VaultEvent e) {
-			return FontAwesome5Icon.FILE;
+	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 String selectDescription(VaultEvent e) {
-		return switch (e.actualEvent()) {
-			case  ConflictResolvedEvent _-> "A conflict is resolved!";
-			default -> "Something happened";
-		};
+	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());
+	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);
+		}
 	}
 
-	@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 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();
-	}
-
 }

+ 2 - 10
src/main/resources/fxml/eventview_cell.fxml

@@ -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>

+ 6 - 0
src/main/resources/i18n/strings.properties

@@ -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