mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-14 08:41:28 +00:00
doodles
This commit is contained in:
@@ -259,7 +259,7 @@ public class Vault {
|
||||
|
||||
|
||||
private void consumeVaultEvent(FilesystemEvent e) {
|
||||
fileSystemEventRegistry.enque(this, e);
|
||||
fileSystemEventRegistry.enqueue(this, e);
|
||||
}
|
||||
|
||||
// ******************************************************************************
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
package org.cryptomator.event;
|
||||
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.cryptofs.event.FilesystemEvent;
|
||||
|
||||
public record FileSystemEventBucket(Vault vault, FilesystemEvent mostRecent, int count) implements Comparable<FileSystemEventBucket> {
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (other instanceof FileSystemEventBucket(Vault v2, FilesystemEvent e2, _)) {
|
||||
return vault.equals(v2) && mostRecent.getClass().equals(e2.getClass());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(FileSystemEventBucket other) {
|
||||
var timeResult = mostRecent.getTimestamp().compareTo(other.mostRecent().getTimestamp());
|
||||
if (timeResult != 0) {
|
||||
return timeResult;
|
||||
}
|
||||
var vaultIdResult = vault.getId().compareTo(other.vault.getId());
|
||||
if (vaultIdResult != 0) {
|
||||
return vaultIdResult;
|
||||
}
|
||||
return this.mostRecent.getClass().getName().compareTo(other.mostRecent.getClass().getName());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -7,61 +7,51 @@ 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.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.MapChangeListener;
|
||||
import javafx.collections.ObservableMap;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.TreeSet;
|
||||
import java.time.Instant;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ConcurrentSkipListMap;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* Angenommen:
|
||||
* Datenstruktur die
|
||||
* 1. Thread-Safe ist
|
||||
* ??
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
* 1. Wenn ein Set verwendet wird, dann können wir nach Timestamp sortieren, aber wir können einen Eintrag nur durch entfernen und hinzufügen updaten
|
||||
* 2. Wenn eine Map verwendet wird, dann können wir Einträge updaten. Aber
|
||||
*
|
||||
*/
|
||||
//TODO: Rename to aggregator
|
||||
//TODO: lru cache
|
||||
@Singleton
|
||||
public class FileSystemEventRegistry {
|
||||
|
||||
private static final int MAX_MAP_SIZE = 400;
|
||||
|
||||
public record Key(Vault vault, Path idPath, Class<? extends FilesystemEvent> c) {}
|
||||
public record Key(Vault vault, Path idPath, Class<? extends FilesystemEvent> c) {};
|
||||
|
||||
public record Value(FilesystemEvent mostRecentEvent, int count) {}
|
||||
|
||||
/**
|
||||
* Queue of elements to be inserted into the map
|
||||
*/
|
||||
private final ConcurrentMap<Key, Value> queue;
|
||||
/**
|
||||
* Least-recently-used cache.
|
||||
*/
|
||||
private final TreeSet<Key> lruCache;
|
||||
/**
|
||||
* Actual map
|
||||
*/
|
||||
private final ObservableMap<Key, Value> map;
|
||||
|
||||
private final ScheduledExecutorService scheduler;
|
||||
|
||||
private final AtomicBoolean queueHasElements;
|
||||
private final ConcurrentHashMap<Key, Value> map;
|
||||
private final AtomicBoolean hasUpdates;
|
||||
|
||||
@Inject
|
||||
public FileSystemEventRegistry(ScheduledExecutorService scheduledExecutorService) {
|
||||
this.queue = new ConcurrentHashMap<>();
|
||||
this.lruCache = new TreeSet<>(this::compareKeys);
|
||||
this.map = FXCollections.observableHashMap();
|
||||
this.scheduler = scheduledExecutorService;
|
||||
this.queueHasElements = new AtomicBoolean(false);
|
||||
scheduler.scheduleWithFixedDelay(() -> {
|
||||
if (queueHasElements.get()) {
|
||||
flush();
|
||||
}
|
||||
}, 1000, 1000, TimeUnit.MILLISECONDS);
|
||||
this.map = new ConcurrentHashMap<>();
|
||||
this.hasUpdates = new AtomicBoolean(false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -70,9 +60,9 @@ public class FileSystemEventRegistry {
|
||||
* @param v Vault where the event occurred
|
||||
* @param e Actual {@link FilesystemEvent}
|
||||
*/
|
||||
public synchronized void enque(Vault v, FilesystemEvent e) {
|
||||
public synchronized void enqueue(Vault v, FilesystemEvent e) {
|
||||
var key = computeKey(v, e);
|
||||
queue.compute(key, (k, val) -> {
|
||||
map.compute(key, (k, val) -> {
|
||||
if (val == null) {
|
||||
return new Value(e, 1);
|
||||
} else {
|
||||
@@ -80,45 +70,21 @@ public class FileSystemEventRegistry {
|
||||
}
|
||||
});
|
||||
|
||||
queueHasElements.set(true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Lists all entries in this map as {@link FileSystemEventBucket}. The list is sorted ascending by the timestamp of event occurral (and more if it is the same timestamp).
|
||||
* Must be executed on the JavaFX application thread
|
||||
*
|
||||
* @return a list of vault events, mainly sorted ascending by the event timestamp
|
||||
* @implNote Method is not synchronized, because it is only executed if executed by JavaFX application thread
|
||||
*/
|
||||
public List<FileSystemEventBucket> listAll() {
|
||||
if (!Platform.isFxApplicationThread()) {
|
||||
throw new IllegalStateException("Listing map entries must be performed on JavaFX application thread");
|
||||
}
|
||||
return lruCache.stream().map(key -> {
|
||||
var value = map.get(key);
|
||||
return new FileSystemEventBucket(key.vault(), value.mostRecentEvent(), value.count());
|
||||
}).toList();
|
||||
hasUpdates.set(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an event from the map.
|
||||
* <p>
|
||||
* To identify the event, a similar event (in the sense of map key) is given.
|
||||
* Must be executed on the JavaFX application thread
|
||||
*
|
||||
* @param v Vault where the event occurred
|
||||
* @param similar A similar {@link FilesystemEvent} (same class, same idPath)
|
||||
* @return the removed {@link Value}
|
||||
* @implNote Method is not synchronized, because it is only executed if executed by JavaFX application thread
|
||||
*/
|
||||
public Value remove(Vault v, FilesystemEvent similar) {
|
||||
if (!Platform.isFxApplicationThread()) {
|
||||
throw new IllegalStateException("Map removal must be performed on JavaFX application thread");
|
||||
}
|
||||
var key = computeKey(v, similar);
|
||||
lruCache.remove(key);
|
||||
return map.remove(key);
|
||||
public Value remove(Key key) {
|
||||
var result = map.remove(key);
|
||||
hasUpdates.set(true);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -129,93 +95,8 @@ public class FileSystemEventRegistry {
|
||||
* @implNote Method is not synchronized, because it is only executed if executed by JavaFX application thread
|
||||
*/
|
||||
public void clear() {
|
||||
if (!Platform.isFxApplicationThread()) {
|
||||
throw new IllegalStateException("Map removal must be performed on JavaFX application thread");
|
||||
}
|
||||
lruCache.clear();
|
||||
map.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes all changes from the queue into the map
|
||||
*/
|
||||
private synchronized void flush() {
|
||||
//Lock queue
|
||||
var latch = new CountDownLatch(1);
|
||||
Platform.runLater(() -> {
|
||||
queue.forEach(this::updateMap);
|
||||
queue.clear();
|
||||
latch.countDown();
|
||||
});
|
||||
try {
|
||||
latch.await();
|
||||
queueHasElements.set(false);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a single map entry
|
||||
*
|
||||
* @param k Key of the entry to update
|
||||
* @param v Value of the entry to update
|
||||
* @implNote Method is not synchronized, because it is only called on the (one-and-only) JavaFX application thread
|
||||
*/
|
||||
private void updateMap(Key k, Value v) {
|
||||
var entry = map.get(k);
|
||||
if (entry == null) {
|
||||
if (map.size() == MAX_MAP_SIZE) {
|
||||
var toRemove = lruCache.first();
|
||||
lruCache.remove(toRemove);
|
||||
map.remove(toRemove);
|
||||
}
|
||||
map.put(k, v);
|
||||
lruCache.add(k);
|
||||
} else {
|
||||
lruCache.remove(k);
|
||||
map.put(k, new Value(v.mostRecentEvent, entry.count + v.count));
|
||||
lruCache.add(k); //correct, because cache-sorting uses the map in comparsionMethod
|
||||
}
|
||||
}
|
||||
|
||||
/* Observability */
|
||||
|
||||
public void addListener(MapChangeListener<? super Key, ? super Value> mapChangeListener) {
|
||||
map.addListener(mapChangeListener);
|
||||
}
|
||||
|
||||
public void removeListener(MapChangeListener<? super Key, ? super Value> mapChangeListener) {
|
||||
map.removeListener(mapChangeListener);
|
||||
}
|
||||
|
||||
|
||||
/* Internal stuff */
|
||||
|
||||
/**
|
||||
* Comparsion method for the lru cache. During comparsion the map is accessed.
|
||||
* First the entries are compared by the event timestamp, then vaultId, then identifying path and lastly by class name.
|
||||
*
|
||||
* @param left a {@link Key} object
|
||||
* @param right another {@link Key} object, compared to {@code left}
|
||||
* @return a negative integer, zero, or a positive integer as the first argument is less than, equal to, or greater than the second.
|
||||
*/
|
||||
private int compareKeys(Key left, Key right) {
|
||||
var t1 = map.get(left).mostRecentEvent.getTimestamp();
|
||||
var t2 = map.get(right).mostRecentEvent.getTimestamp();
|
||||
var timeComparsion = t1.compareTo(t2);
|
||||
if (timeComparsion != 0) {
|
||||
return timeComparsion;
|
||||
}
|
||||
var vaultIdComparsion = left.vault.getId().compareTo(right.vault.getId());
|
||||
if (vaultIdComparsion != 0) {
|
||||
return vaultIdComparsion;
|
||||
}
|
||||
var pathComparsion = left.idPath.compareTo(right.idPath);
|
||||
if (pathComparsion != 0) {
|
||||
return pathComparsion;
|
||||
}
|
||||
return left.c.getName().compareTo(right.c.getName());
|
||||
hasUpdates.set(true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -236,4 +117,20 @@ public class FileSystemEventRegistry {
|
||||
return new Key(v, p, event.getClass());
|
||||
}
|
||||
|
||||
public boolean hasUpdates() {
|
||||
return hasUpdates.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones the map entries into a set.
|
||||
* <p>
|
||||
* The set is first cleared, then all map entries are added in one bulk operation. Sets the updates status of the event registry.
|
||||
*
|
||||
* @param targetCollection
|
||||
*/
|
||||
public void cloneTo(Collection<Map.Entry<Key, Value>> targetCollection) {
|
||||
targetCollection.clear();
|
||||
targetCollection.addAll(map.entrySet());
|
||||
hasUpdates.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ 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.event.FileSystemEventBucket;
|
||||
import org.cryptomator.integrations.revealpath.RevealFailedException;
|
||||
import org.cryptomator.integrations.revealpath.RevealPathService;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
@@ -41,6 +40,7 @@ import java.nio.file.Path;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.FormatStyle;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.function.Function;
|
||||
@@ -55,7 +55,7 @@ public class EventListCellController implements FxController {
|
||||
@Nullable
|
||||
private final RevealPathService revealService;
|
||||
private final ResourceBundle resourceBundle;
|
||||
private final ObjectProperty<FileSystemEventBucket> event;
|
||||
private final ObjectProperty<Map.Entry<FileSystemEventRegistry.Key, FileSystemEventRegistry.Value>> eventEntry;
|
||||
private final StringProperty eventMessage;
|
||||
private final StringProperty eventDescription;
|
||||
private final ObjectProperty<FontAwesome5Icon> eventIcon;
|
||||
@@ -81,14 +81,14 @@ public class EventListCellController implements FxController {
|
||||
this.fileSystemEventRegistry = fileSystemEventRegistry;
|
||||
this.revealService = revealService.orElseGet(() -> null);
|
||||
this.resourceBundle = resourceBundle;
|
||||
this.event = new SimpleObjectProperty<>(null);
|
||||
this.eventEntry = new SimpleObjectProperty<>(null);
|
||||
this.eventMessage = new SimpleStringProperty();
|
||||
this.eventDescription = new SimpleStringProperty();
|
||||
this.eventIcon = new SimpleObjectProperty<>();
|
||||
this.eventCount = ObservableUtil.mapWithDefault(event, e -> e.count() == 1? "" : "("+ e.count() +")", "");
|
||||
this.vaultUnlocked = ObservableUtil.mapWithDefault(event.flatMap(e -> e.vault().unlockedProperty()), Function.identity(), false);
|
||||
this.readableTime = ObservableUtil.mapWithDefault(event, e -> LOCAL_TIME_FORMATTER.format(e.mostRecent().getTimestamp()), "");
|
||||
this.readableDate = ObservableUtil.mapWithDefault(event, e -> LOCAL_DATE_FORMATTER.format(e.mostRecent().getTimestamp()), "");
|
||||
this.eventCount = ObservableUtil.mapWithDefault(eventEntry, e -> e.getValue().count() == 1? "" : "("+ e.getValue().count() +")", "");
|
||||
this.vaultUnlocked = ObservableUtil.mapWithDefault(eventEntry.flatMap(e -> e.getKey().vault().unlockedProperty()), Function.identity(), false);
|
||||
this.readableTime = ObservableUtil.mapWithDefault(eventEntry, e -> LOCAL_TIME_FORMATTER.format(e.getValue().mostRecentEvent().getTimestamp()), "");
|
||||
this.readableDate = ObservableUtil.mapWithDefault(eventEntry, e -> LOCAL_DATE_FORMATTER.format(e.getValue().mostRecentEvent().getTimestamp()), "");
|
||||
this.message = Bindings.createStringBinding(this::selectMessage, vaultUnlocked, eventMessage);
|
||||
this.description = Bindings.createStringBinding(this::selectDescription, vaultUnlocked, eventDescription);
|
||||
this.icon = Bindings.createObjectBinding(this::selectIcon, vaultUnlocked, eventIcon);
|
||||
@@ -108,13 +108,13 @@ public class EventListCellController implements FxController {
|
||||
return vaultUnlocked.getValue() && (eventActionsMenu.isShowing() || root.isHover());
|
||||
}
|
||||
|
||||
public void setEvent(@NotNull FileSystemEventBucket item) {
|
||||
event.set(item);
|
||||
public void setEventEntry(@NotNull Map.Entry<FileSystemEventRegistry.Key, FileSystemEventRegistry.Value> item) {
|
||||
eventEntry.set(item);
|
||||
eventActionsMenu.hide();
|
||||
eventActionsMenu.getItems().clear();
|
||||
eventTooltip.setText(item.vault().getDisplayName());
|
||||
addAction("generic.action.dismiss", () -> fileSystemEventRegistry.remove(item.vault(),item.mostRecent()));
|
||||
switch (item.mostRecent()) {
|
||||
eventTooltip.setText(item.getKey().vault().getDisplayName());
|
||||
addAction("generic.action.dismiss", () -> fileSystemEventRegistry.remove(item.getKey()));
|
||||
switch (item.getValue().mostRecentEvent()) {
|
||||
case ConflictResolvedEvent fse -> this.adjustToConflictResolvedEvent(fse);
|
||||
case ConflictResolutionFailedEvent fse -> this.adjustToConflictEvent(fse);
|
||||
case DecryptionFailedEvent fse -> this.adjustToDecryptionFailedEvent(fse);
|
||||
@@ -209,16 +209,18 @@ public class EventListCellController implements FxController {
|
||||
private String selectDescription() {
|
||||
if (vaultUnlocked.getValue()) {
|
||||
return eventDescription.getValue();
|
||||
} else {
|
||||
var e = event.getValue();
|
||||
} else if (eventEntry.getValue() != null) {
|
||||
var e = eventEntry.getValue().getKey();
|
||||
return resourceBundle.getString("eventView.entry.vaultLocked.description").formatted(e != null ? e.vault().getDisplayName() : "");
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@FXML
|
||||
public void toggleEventActionsMenu() {
|
||||
var e = event.get();
|
||||
var e = eventEntry.get();
|
||||
if (e != null) {
|
||||
if (eventActionsMenu.isShowing()) {
|
||||
eventActionsMenu.hide();
|
||||
@@ -232,7 +234,7 @@ public class EventListCellController implements FxController {
|
||||
if (!(p instanceof CryptoPath)) {
|
||||
throw new IllegalArgumentException("Path " + p + " is not a vault path");
|
||||
}
|
||||
var v = event.getValue().vault();
|
||||
var v = eventEntry.getValue().getKey().vault();
|
||||
if (!v.isUnlocked()) {
|
||||
return Path.of(System.getProperty("user.home"));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package org.cryptomator.ui.eventview;
|
||||
|
||||
import org.cryptomator.event.FileSystemEventBucket;
|
||||
import org.cryptomator.event.FileSystemEventRegistry;
|
||||
import org.cryptomator.ui.common.FxmlLoaderFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
@@ -12,9 +12,10 @@ import javafx.scene.control.ListView;
|
||||
import javafx.util.Callback;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.Map;
|
||||
|
||||
@EventViewScoped
|
||||
public class EventListCellFactory implements Callback<ListView<FileSystemEventBucket>, ListCell<FileSystemEventBucket>> {
|
||||
public class EventListCellFactory implements Callback<ListView<Map.Entry<FileSystemEventRegistry.Key, FileSystemEventRegistry.Value>>, ListCell<Map.Entry<FileSystemEventRegistry.Key, FileSystemEventRegistry.Value>>> {
|
||||
|
||||
private static final String FXML_PATH = "/fxml/eventview_cell.fxml";
|
||||
|
||||
@@ -27,7 +28,7 @@ public class EventListCellFactory implements Callback<ListView<FileSystemEventBu
|
||||
|
||||
|
||||
@Override
|
||||
public ListCell<FileSystemEventBucket> call(ListView<FileSystemEventBucket> eventListView) {
|
||||
public ListCell<Map.Entry<FileSystemEventRegistry.Key, FileSystemEventRegistry.Value>> call(ListView<Map.Entry<FileSystemEventRegistry.Key, FileSystemEventRegistry.Value>> eventListView) {
|
||||
try {
|
||||
FXMLLoader fxmlLoader = fxmlLoaders.load(FXML_PATH);
|
||||
return new Cell(fxmlLoader.getRoot(), fxmlLoader.getController());
|
||||
@@ -36,7 +37,7 @@ public class EventListCellFactory implements Callback<ListView<FileSystemEventBu
|
||||
}
|
||||
}
|
||||
|
||||
private static class Cell extends ListCell<FileSystemEventBucket> {
|
||||
private static class Cell extends ListCell<Map.Entry<FileSystemEventRegistry.Key, FileSystemEventRegistry.Value>> {
|
||||
|
||||
private final Parent root;
|
||||
private final EventListCellController controller;
|
||||
@@ -47,7 +48,7 @@ public class EventListCellFactory implements Callback<ListView<FileSystemEventBu
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateItem(FileSystemEventBucket item, boolean empty) {
|
||||
protected void updateItem(Map.Entry<FileSystemEventRegistry.Key, FileSystemEventRegistry.Value> item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
|
||||
if (empty || item == null) {
|
||||
@@ -57,7 +58,7 @@ public class EventListCellFactory implements Callback<ListView<FileSystemEventBu
|
||||
this.getStyleClass().addLast("list-cell");
|
||||
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
|
||||
setGraphic(root);
|
||||
controller.setEvent(item);
|
||||
controller.setEventEntry(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
package org.cryptomator.ui.eventview;
|
||||
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.event.FileSystemEventBucket;
|
||||
import org.cryptomator.event.FileSystemEventRegistry;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.fxapp.EventsUpdateCheck;
|
||||
|
||||
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;
|
||||
@@ -17,17 +16,15 @@ import javafx.fxml.FXML;
|
||||
import javafx.scene.control.ChoiceBox;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.util.StringConverter;
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@EventViewScoped
|
||||
public class EventViewController implements FxController {
|
||||
|
||||
private final FileSystemEventRegistry fileSystemEventRegistry;
|
||||
private final ObservableList<FileSystemEventBucket> eventList;
|
||||
private final FilteredList<FileSystemEventBucket> filteredEventList;
|
||||
private final FilteredList<Map.Entry<FileSystemEventRegistry.Key, FileSystemEventRegistry.Value>> filteredEventList;
|
||||
private final ObservableList<Vault> vaults;
|
||||
private final SortedList<FileSystemEventBucket> reversedEventList;
|
||||
private final SortedList<Map.Entry<FileSystemEventRegistry.Key, FileSystemEventRegistry.Value>> sortedEventList;
|
||||
private final ObservableList<Vault> choiceBoxEntries;
|
||||
private final ResourceBundle resourceBundle;
|
||||
private final EventListCellFactory cellFactory;
|
||||
@@ -35,20 +32,44 @@ public class EventViewController implements FxController {
|
||||
@FXML
|
||||
ChoiceBox<Vault> vaultFilterChoiceBox;
|
||||
@FXML
|
||||
ListView<FileSystemEventBucket> eventListView;
|
||||
ListView<Map.Entry<FileSystemEventRegistry.Key, FileSystemEventRegistry.Value>> eventListView;
|
||||
|
||||
@Inject
|
||||
public EventViewController(FileSystemEventRegistry fileSystemEventRegistry, ObservableList<Vault> vaults, ResourceBundle resourceBundle, EventListCellFactory cellFactory) {
|
||||
this.fileSystemEventRegistry = fileSystemEventRegistry;
|
||||
this.eventList = FXCollections.observableArrayList();
|
||||
this.filteredEventList = eventList.filtered(_ -> true);
|
||||
public EventViewController(EventsUpdateCheck eventsUpdateCheck, ObservableList<Vault> vaults, ResourceBundle resourceBundle, EventListCellFactory cellFactory) {
|
||||
this.filteredEventList = eventsUpdateCheck.getList().filtered(_ -> true);
|
||||
this.vaults = vaults;
|
||||
this.reversedEventList = new SortedList<>(filteredEventList, Comparator.reverseOrder());
|
||||
this.sortedEventList = new SortedList<>(filteredEventList, this::compareBuckets);
|
||||
this.choiceBoxEntries = FXCollections.observableArrayList();
|
||||
this.resourceBundle = resourceBundle;
|
||||
this.cellFactory = cellFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparsion method for the lru cache. During comparsion the map is accessed.
|
||||
* First the entries are compared by the event timestamp, then vaultId, then identifying path and lastly by class name.
|
||||
*
|
||||
* @param left a {@link FileSystemEventRegistry.Key} object
|
||||
* @param right another {@link FileSystemEventRegistry.Key} object, compared to {@code left}
|
||||
* @return a negative integer, zero, or a positive integer as the first argument is less than, equal to, or greater than the second.
|
||||
*/
|
||||
private int compareBuckets(Map.Entry<FileSystemEventRegistry.Key, FileSystemEventRegistry.Value> left, Map.Entry<FileSystemEventRegistry.Key, FileSystemEventRegistry.Value> right) {
|
||||
var t1 = left.getValue().mostRecentEvent().getTimestamp();
|
||||
var t2 = right.getValue().mostRecentEvent().getTimestamp();
|
||||
var timeComparison = t1.compareTo(t2);
|
||||
if (timeComparison != 0) {
|
||||
return -timeComparison; //we need the reverse timesorting
|
||||
}
|
||||
var vaultIdComparsion = left.getKey().vault().getId().compareTo(right.getKey().vault().getId());
|
||||
if (vaultIdComparsion != 0) {
|
||||
return vaultIdComparsion;
|
||||
}
|
||||
var pathComparsion = left.getKey().idPath().compareTo(right.getKey().idPath());
|
||||
if (pathComparsion != 0) {
|
||||
return pathComparsion;
|
||||
}
|
||||
return left.getKey().c().getName().compareTo(right.getKey().c().getName());
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
choiceBoxEntries.add(null);
|
||||
@@ -60,37 +81,25 @@ public class EventViewController implements FxController {
|
||||
}
|
||||
});
|
||||
|
||||
eventList.addAll(fileSystemEventRegistry.listAll());
|
||||
fileSystemEventRegistry.addListener((MapChangeListener<? super FileSystemEventRegistry.Key, ? super FileSystemEventRegistry.Value>) this::updateList);
|
||||
eventListView.setCellFactory(cellFactory);
|
||||
eventListView.setItems(reversedEventList);
|
||||
eventListView.setItems(sortedEventList);
|
||||
|
||||
vaultFilterChoiceBox.setItems(choiceBoxEntries);
|
||||
vaultFilterChoiceBox.valueProperty().addListener(this::applyVaultFilter);
|
||||
vaultFilterChoiceBox.setConverter(new VaultConverter(resourceBundle));
|
||||
}
|
||||
|
||||
private void updateList(MapChangeListener.Change<? extends FileSystemEventRegistry.Key, ? extends FileSystemEventRegistry.Value> change) {
|
||||
var vault = change.getKey().vault();
|
||||
if (change.wasRemoved()) {
|
||||
eventList.remove(new FileSystemEventBucket(vault, change.getValueRemoved().mostRecentEvent(), 0));
|
||||
}
|
||||
if (change.wasAdded()) {
|
||||
eventList.addLast(new FileSystemEventBucket(vault, change.getValueAdded().mostRecentEvent(), change.getValueAdded().count()));
|
||||
}
|
||||
}
|
||||
|
||||
private void applyVaultFilter(ObservableValue<? extends Vault> v, Vault oldV, Vault newV) {
|
||||
if (newV == null) {
|
||||
filteredEventList.setPredicate(_ -> true);
|
||||
} else {
|
||||
filteredEventList.setPredicate(e -> e.vault().equals(newV));
|
||||
filteredEventList.setPredicate(e -> e.getKey().vault().equals(newV));
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
void clearEvents() {
|
||||
fileSystemEventRegistry.clear();
|
||||
//fileSystemEventRegistry.clear();
|
||||
}
|
||||
|
||||
private static class VaultConverter extends StringConverter<Vault> {
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package org.cryptomator.ui.fxapp;
|
||||
|
||||
import org.cryptomator.event.FileSystemEventRegistry;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@FxApplicationScoped
|
||||
public class EventsUpdateCheck {
|
||||
|
||||
private final ObservableList<Map.Entry<FileSystemEventRegistry.Key, FileSystemEventRegistry.Value>> events;
|
||||
private final FileSystemEventRegistry eventRegistry;
|
||||
private final ScheduledFuture<?> scheduledTask;
|
||||
private final BooleanProperty unreadEvents;
|
||||
|
||||
@Inject
|
||||
public EventsUpdateCheck(FileSystemEventRegistry eventRegistry, ScheduledExecutorService scheduler, @Named("unreadEventsAvailable") BooleanProperty unreadEvents) {
|
||||
this.events = FXCollections.observableArrayList();
|
||||
this.eventRegistry = eventRegistry;
|
||||
this.unreadEvents = unreadEvents;
|
||||
this.scheduledTask = scheduler.scheduleWithFixedDelay(() -> {
|
||||
if (eventRegistry.hasUpdates()) {
|
||||
flush();
|
||||
}
|
||||
}, 1000, 1000, TimeUnit.MILLISECONDS);
|
||||
//TODO: allow the task to be canceled (to enable ui actions, e.g. when the contextMenu is open, the list should not be updated
|
||||
}
|
||||
|
||||
public ObservableList<Map.Entry<FileSystemEventRegistry.Key, FileSystemEventRegistry.Value>> getList() {
|
||||
return events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones the registry into the observable list
|
||||
*/
|
||||
private void flush() {
|
||||
var latch = new CountDownLatch(1);
|
||||
Platform.runLater(() -> {
|
||||
eventRegistry.cloneTo(events);
|
||||
unreadEvents.setValue(true);
|
||||
latch.countDown();
|
||||
});
|
||||
try {
|
||||
latch.await();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -43,6 +43,7 @@ public class FxApplication {
|
||||
this.autoUnlocker = autoUnlocker;
|
||||
}
|
||||
|
||||
//TODO: eventUpdater muss hier starten
|
||||
public void start() {
|
||||
LOG.trace("FxApplication.start()");
|
||||
applicationStyle.initialize();
|
||||
|
||||
@@ -69,7 +69,6 @@ public class VaultListController implements FxController {
|
||||
private final VaultListCellFactory cellFactory;
|
||||
private final AddVaultWizardComponent.Builder addVaultWizard;
|
||||
private final BooleanBinding emptyVaultList;
|
||||
private final FileSystemEventRegistry fileSystemEventRegistry;
|
||||
private final BooleanProperty newEventsPresent;
|
||||
private final VaultListManager vaultListManager;
|
||||
private final BooleanProperty draggingVaultOver = new SimpleBooleanProperty();
|
||||
@@ -97,7 +96,6 @@ public class VaultListController implements FxController {
|
||||
FxApplicationWindows appWindows, //
|
||||
Settings settings, //
|
||||
Dialogs dialogs, //
|
||||
FileSystemEventRegistry fileSystemEventRegistry, //
|
||||
@Named("unreadEventsAvailable") BooleanProperty unreadEvents) {
|
||||
this.mainWindow = mainWindow;
|
||||
this.vaults = vaults;
|
||||
@@ -111,13 +109,7 @@ public class VaultListController implements FxController {
|
||||
this.dialogs = dialogs;
|
||||
|
||||
this.emptyVaultList = Bindings.isEmpty(vaults);
|
||||
this.fileSystemEventRegistry = fileSystemEventRegistry;
|
||||
this.newEventsPresent = unreadEvents;
|
||||
fileSystemEventRegistry.addListener((MapChangeListener<? super FileSystemEventRegistry.Key, ? super FileSystemEventRegistry.Value>) change -> {
|
||||
if (change.wasAdded()) {
|
||||
newEventsPresent.setValue(true);
|
||||
}
|
||||
});
|
||||
|
||||
selectedVault.addListener(this::selectedVaultDidChange);
|
||||
cellSize = settings.compactMode.map(compact -> compact ? 30.0 : 60.0);
|
||||
|
||||
Reference in New Issue
Block a user