replace eventList with a eventMap in model to group events

This commit is contained in:
Armin Schrenk
2025-03-07 11:21:23 +01:00
parent 3ba7e9ba00
commit a86f42fa44
7 changed files with 193 additions and 32 deletions

View File

@@ -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);
}

View File

@@ -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());
}
}

View File

@@ -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);
});
}
// ******************************************************************************

View File

@@ -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);
}
}

View File

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

View File

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

View File

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