Kaynağa Gözat

replace eventList with a eventMap in model to group events

Armin Schrenk 3 ay önce
ebeveyn
işleme
a86f42fa44

+ 0 - 9
src/main/java/org/cryptomator/common/CommonsModule.java

@@ -14,15 +14,12 @@ import org.cryptomator.common.settings.SettingsProvider;
 import org.cryptomator.common.vaults.VaultComponent;
 import org.cryptomator.common.vaults.VaultListModule;
 import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
-import org.cryptomator.event.VaultEvent;
 import org.cryptomator.integrations.revealpath.RevealPathService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import javax.inject.Named;
 import javax.inject.Singleton;
-import javafx.collections.FXCollections;
-import javafx.collections.ObservableList;
 import java.security.NoSuchAlgorithmException;
 import java.security.SecureRandom;
 import java.util.Comparator;
@@ -131,12 +128,6 @@ public abstract class CommonsModule {
 		return executorService;
 	}
 
-	@Provides
-	@Singleton
-	static ObservableList<VaultEvent> provideVaultEventQueue() {
-		return FXCollections.synchronizedObservableList(FXCollections.observableArrayList());
-	}
-
 	private static void handleUncaughtExceptionInBackgroundThread(Thread thread, Throwable throwable) {
 		LOG.error("Uncaught exception in " + thread.getName(), throwable);
 	}

+ 148 - 0
src/main/java/org/cryptomator/common/EventMap.java

@@ -0,0 +1,148 @@
+package org.cryptomator.common;
+
+import org.cryptomator.cryptofs.event.BrokenDirFileEvent;
+import org.cryptomator.cryptofs.event.BrokenFileNodeEvent;
+import org.cryptomator.cryptofs.event.ConflictResolutionFailedEvent;
+import org.cryptomator.cryptofs.event.ConflictResolvedEvent;
+import org.cryptomator.cryptofs.event.DecryptionFailedEvent;
+import org.cryptomator.cryptofs.event.FilesystemEvent;
+import org.cryptomator.event.VaultEvent;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import javafx.beans.InvalidationListener;
+import javafx.collections.FXCollections;
+import javafx.collections.MapChangeListener;
+import javafx.collections.ObservableMap;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Map containing {@link VaultEvent}s.
+ * The map is keyed by the ciphertext path of the affected resource _and_ the {@link FilesystemEvent}s class in order to group same events
+ */
+@Singleton
+public class EventMap implements ObservableMap<EventMap.EventKey, VaultEvent> {
+
+	public record EventKey(Path ciphertextPath, Class<? extends FilesystemEvent> c) {}
+
+	private final ObservableMap<EventMap.EventKey, VaultEvent> delegate;
+
+	@Inject
+	public EventMap() {
+		delegate = FXCollections.observableHashMap();
+	}
+
+	@Override
+	public void addListener(MapChangeListener<? super EventKey, ? super VaultEvent> mapChangeListener) {
+		delegate.addListener(mapChangeListener);
+	}
+
+	@Override
+	public void removeListener(MapChangeListener<? super EventKey, ? super VaultEvent> mapChangeListener) {
+		delegate.removeListener(mapChangeListener);
+	}
+
+	@Override
+	public int size() {
+		return delegate.size();
+	}
+
+	@Override
+	public boolean isEmpty() {
+		return delegate.isEmpty();
+	}
+
+	@Override
+	public boolean containsKey(Object key) {
+		return delegate.containsKey(key);
+	}
+
+	@Override
+	public boolean containsValue(Object value) {
+		return delegate.containsValue(value);
+	}
+
+	@Override
+	public VaultEvent get(Object key) {
+		return delegate.get(key);
+	}
+
+	@Override
+	public @Nullable VaultEvent put(EventKey key, VaultEvent value) {
+		return delegate.put(key, value);
+	}
+
+	@Override
+	public VaultEvent remove(Object key) {
+		return delegate.remove(key);
+	}
+
+	@Override
+	public void putAll(@NotNull Map<? extends EventKey, ? extends VaultEvent> m) {
+		delegate.putAll(m);
+	}
+
+	@Override
+	public void clear() {
+		delegate.clear();
+	}
+
+	@Override
+	public @NotNull Set<EventKey> keySet() {
+		return delegate.keySet();
+	}
+
+	@Override
+	public @NotNull Collection<VaultEvent> values() {
+		return delegate.values();
+	}
+
+	@Override
+	public @NotNull Set<Entry<EventKey, VaultEvent>> entrySet() {
+		return delegate.entrySet();
+	}
+
+	@Override
+	public void addListener(InvalidationListener invalidationListener) {
+		delegate.addListener(invalidationListener);
+	}
+
+	@Override
+	public void removeListener(InvalidationListener invalidationListener) {
+		delegate.removeListener(invalidationListener);
+	}
+
+	public synchronized void put(VaultEvent e) {
+		//compute key
+		var key = computeKey(e.actualEvent());
+		//if-else
+		var nullOrEntry = delegate.get(key);
+		if (nullOrEntry == null) {
+			delegate.put(key, e);
+		} else {
+			delegate.put(key, nullOrEntry.incrementCount(e.timestamp()));
+		}
+	}
+
+	public synchronized VaultEvent remove(VaultEvent similar) {
+		//compute key
+		var key = computeKey(similar.actualEvent());
+		return this.remove(key);
+	}
+
+	private EventKey computeKey(FilesystemEvent e) {
+		var p = switch (e) {
+			case DecryptionFailedEvent(Path ciphertextPath, _) -> ciphertextPath;
+			case ConflictResolvedEvent(_, _, _, Path resolvedCiphertext) -> resolvedCiphertext;
+			case ConflictResolutionFailedEvent(_, Path conflictingCiphertext, _) -> conflictingCiphertext;
+			case BrokenDirFileEvent(Path ciphertext) -> ciphertext;
+			case BrokenFileNodeEvent(_, Path ciphertext) -> ciphertext;
+		};
+		return new EventKey(p, e.getClass());
+	}
+}

+ 9 - 8
src/main/java/org/cryptomator/common/vaults/Vault.java

@@ -10,6 +10,7 @@ package org.cryptomator.common.vaults;
 
 import org.apache.commons.lang3.SystemUtils;
 import org.cryptomator.common.Constants;
+import org.cryptomator.common.EventMap;
 import org.cryptomator.common.mount.Mounter;
 import org.cryptomator.common.settings.Settings;
 import org.cryptomator.common.settings.VaultSettings;
@@ -44,12 +45,10 @@ import javafx.beans.property.BooleanProperty;
 import javafx.beans.property.ObjectProperty;
 import javafx.beans.property.ReadOnlyStringProperty;
 import javafx.beans.property.SimpleBooleanProperty;
-import javafx.collections.ObservableList;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.nio.file.ReadOnlyFileSystemException;
-import java.time.Instant;
 import java.util.EnumSet;
 import java.util.Objects;
 import java.util.Set;
@@ -79,7 +78,7 @@ public class Vault {
 	private final ObjectBinding<Mountpoint> mountPoint;
 	private final Mounter mounter;
 	private final Settings settings;
-	private final ObservableList<VaultEvent> eventList;
+	private final EventMap eventMap;
 	private final BooleanProperty showingStats;
 
 	private final AtomicReference<Mounter.MountHandle> mountHandle = new AtomicReference<>(null);
@@ -92,7 +91,7 @@ public class Vault {
 		  @Named("lastKnownException") ObjectProperty<Exception> lastKnownException, //
 		  VaultStats stats, //
 		  Mounter mounter, Settings settings, //
-		  ObservableList<VaultEvent> eventList) {
+		  EventMap eventMap) {
 		this.vaultSettings = vaultSettings;
 		this.configCache = configCache;
 		this.cryptoFileSystem = cryptoFileSystem;
@@ -109,7 +108,7 @@ public class Vault {
 		this.mountPoint = Bindings.createObjectBinding(this::getMountPoint, state);
 		this.mounter = mounter;
 		this.settings = settings;
-		this.eventList = eventList;
+		this.eventMap = eventMap;
 		this.showingStats = new SimpleBooleanProperty(false);
 		this.quickAccessEntry = new AtomicReference<>(null);
 	}
@@ -260,10 +259,12 @@ public class Vault {
 		}
 	}
 
+
 	private void consumeVaultEvent(FilesystemEvent e) {
-		//TODO: here we could implement a buffer to prevent event spam (due to many filesystem requests)
-		var timestamp = Instant.now();
-		Platform.runLater(() -> eventList.addLast(new VaultEvent(timestamp, this, e)));
+		var wrapper = new VaultEvent(this, e);
+		Platform.runLater(() -> {
+			eventMap.put(wrapper);
+		});
 	}
 
 	// ******************************************************************************

+ 6 - 2
src/main/java/org/cryptomator/event/VaultEvent.java

@@ -5,10 +5,10 @@ import org.cryptomator.cryptofs.event.FilesystemEvent;
 
 import java.time.Instant;
 
-public record VaultEvent(Instant timestamp, Vault v, FilesystemEvent actualEvent) implements Comparable<VaultEvent> {
+public record VaultEvent(Instant timestamp, Vault v, FilesystemEvent actualEvent, int count) implements Comparable<VaultEvent> {
 
 	public VaultEvent(Vault v, FilesystemEvent actualEvent) {
-		this(Instant.now(), v, actualEvent);
+		this(Instant.now(), v, actualEvent, 1);
 	}
 
 	@Override
@@ -20,4 +20,8 @@ public record VaultEvent(Instant timestamp, Vault v, FilesystemEvent actualEvent
 			return this.equals(other) ? 0 : this.actualEvent.getClass().getName().compareTo(other.actualEvent.getClass().getName());
 		}
 	}
+
+	public VaultEvent incrementCount(Instant timestamp) {
+		return new VaultEvent(timestamp, v, actualEvent, count+1);
+	}
 }

+ 6 - 8
src/main/java/org/cryptomator/ui/eventview/EventListCellController.java

@@ -1,5 +1,6 @@
 package org.cryptomator.ui.eventview;
 
+import org.cryptomator.common.EventMap;
 import org.cryptomator.common.Nullable;
 import org.cryptomator.common.ObservableUtil;
 import org.cryptomator.cryptofs.CryptoPath;
@@ -26,7 +27,6 @@ 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;
@@ -50,7 +50,7 @@ public class EventListCellController implements FxController {
 	private static final DateTimeFormatter LOCAL_DATE_FORMATTER = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT).withZone(ZoneId.systemDefault());
 	private static final DateTimeFormatter LOCAL_TIME_FORMATTER = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT).withZone(ZoneId.systemDefault());
 
-	private final ObservableList<VaultEvent> events;
+	private final EventMap eventMap;
 	@Nullable
 	private final RevealPathService revealService;
 	private final ResourceBundle resourceBundle;
@@ -65,9 +65,7 @@ public class EventListCellController implements FxController {
 	private final ObservableValue<String> description;
 	private final ObservableValue<FontAwesome5Icon> icon;
 	private final BooleanProperty actionsButtonVisible;
-
-	@FXML
-	private Tooltip eventTooltip;
+	private final Tooltip eventTooltip;
 
 	@FXML
 	HBox root;
@@ -77,8 +75,8 @@ public class EventListCellController implements FxController {
 	Button eventActionsButton;
 
 	@Inject
-	public EventListCellController(ObservableList<VaultEvent> events, Optional<RevealPathService> revealService, ResourceBundle resourceBundle) {
-		this.events = events;
+	public EventListCellController(EventMap eventMap, Optional<RevealPathService> revealService, ResourceBundle resourceBundle) {
+		this.eventMap = eventMap;
 		this.revealService = revealService.orElseGet(() -> null);
 		this.resourceBundle = resourceBundle;
 		this.event = new SimpleObjectProperty<>(null);
@@ -111,7 +109,7 @@ public class EventListCellController implements FxController {
 		eventActionsMenu.hide();
 		eventActionsMenu.getItems().clear();
 		eventTooltip.setText(item.v().getDisplayName());
-		addAction("generic.action.dismiss", () -> events.remove(item));
+		addAction("generic.action.dismiss", () -> eventMap.remove(item));
 		switch (item.actualEvent()) {
 			case ConflictResolvedEvent fse -> this.adjustToConflictResolvedEvent(fse);
 			case ConflictResolutionFailedEvent fse -> this.adjustToConflictEvent(fse);

+ 23 - 4
src/main/java/org/cryptomator/ui/eventview/EventViewController.java

@@ -1,5 +1,6 @@
 package org.cryptomator.ui.eventview;
 
+import org.cryptomator.common.EventMap;
 import org.cryptomator.common.vaults.Vault;
 import org.cryptomator.event.VaultEvent;
 import org.cryptomator.ui.common.FxController;
@@ -8,6 +9,7 @@ import javax.inject.Inject;
 import javafx.beans.value.ObservableValue;
 import javafx.collections.FXCollections;
 import javafx.collections.ListChangeListener;
+import javafx.collections.MapChangeListener;
 import javafx.collections.ObservableList;
 import javafx.collections.transformation.FilteredList;
 import javafx.collections.transformation.SortedList;
@@ -21,6 +23,7 @@ import java.util.ResourceBundle;
 @EventViewScoped
 public class EventViewController implements FxController {
 
+	private final EventMap eventMap;
 	private final ObservableList<VaultEvent> eventList;
 	private final FilteredList<VaultEvent> filteredEventList;
 	private final ObservableList<Vault> vaults;
@@ -35,8 +38,9 @@ public class EventViewController implements FxController {
 	ListView<VaultEvent> eventListView;
 
 	@Inject
-	public EventViewController(ObservableList<VaultEvent> eventList, ObservableList<Vault> vaults, ResourceBundle resourceBundle, EventListCellFactory cellFactory) {
-		this.eventList = eventList;
+	public EventViewController(EventMap eventMap, ObservableList<Vault> vaults, ResourceBundle resourceBundle, EventListCellFactory cellFactory) {
+		this.eventMap = eventMap;
+		this.eventList = FXCollections.observableArrayList();
 		this.filteredEventList = eventList.filtered(_ -> true);
 		this.vaults = vaults;
 		this.reversedEventList = new SortedList<>(filteredEventList, Comparator.reverseOrder());
@@ -55,6 +59,9 @@ public class EventViewController implements FxController {
 				choiceBoxEntries.addAll(c.getAddedSubList());
 			}
 		});
+
+		eventList.addAll(eventMap.values());
+		eventMap.addListener((MapChangeListener<? super EventMap.EventKey, ? super VaultEvent>) this::updateList);
 		eventListView.setCellFactory(cellFactory);
 		eventListView.setItems(reversedEventList);
 
@@ -63,6 +70,18 @@ public class EventViewController implements FxController {
 		vaultFilterChoiceBox.setConverter(new VaultConverter(resourceBundle));
 	}
 
+	private void updateList(MapChangeListener.Change<? extends EventMap.EventKey, ? extends VaultEvent> change) {
+		if (change.wasAdded() && change.wasRemoved()) {
+			//entry updated
+			eventList.remove(change.getValueRemoved());
+			eventList.addLast(change.getValueAdded());
+		} else if (change.wasAdded()) {
+			eventList.addLast(change.getValueAdded());
+		} else { //removed
+			eventList.remove(change.getValueRemoved());
+		}
+	}
+
 	private void applyVaultFilter(ObservableValue<? extends Vault> v, Vault oldV, Vault newV) {
 		if (newV == null) {
 			filteredEventList.setPredicate(_ -> true);
@@ -72,8 +91,8 @@ public class EventViewController implements FxController {
 	}
 
 	@FXML
-	void clearEventList() {
-		eventList.clear();
+	void clearEvents() {
+		eventMap.clear();
 	}
 
 	private static class VaultConverter extends StringConverter<Vault> {

+ 1 - 1
src/main/resources/fxml/eventview.fxml

@@ -21,7 +21,7 @@
 		</padding>
 		<ChoiceBox fx:id="vaultFilterChoiceBox"/>
 		<Region HBox.hgrow="ALWAYS"/>
-		<Button styleClass="button-right" onAction="#clearEventList" contentDisplay="GRAPHIC_ONLY">
+		<Button styleClass="button-right" onAction="#clearEvents" contentDisplay="GRAPHIC_ONLY">
 			<graphic>
 				<FontAwesome5IconView glyph="TRASH" glyphSize="16"/>
 			</graphic>