This commit is contained in:
Armin Schrenk
2025-03-20 16:41:30 +01:00
parent 1f60d9f5e8
commit d67085d57d
9 changed files with 176 additions and 241 deletions

View File

@@ -259,7 +259,7 @@ public class Vault {
private void consumeVaultEvent(FilesystemEvent e) {
fileSystemEventRegistry.enque(this, e);
fileSystemEventRegistry.enqueue(this, e);
}
// ******************************************************************************

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -43,6 +43,7 @@ public class FxApplication {
this.autoUnlocker = autoUnlocker;
}
//TODO: eventUpdater muss hier starten
public void start() {
LOG.trace("FxApplication.start()");
applicationStyle.initialize();

View File

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