mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-14 08:41:28 +00:00
[skip ci] Merge branch 'main' into develop
This commit is contained in:
@@ -83,6 +83,9 @@
|
||||
</content_rating>
|
||||
|
||||
<releases>
|
||||
<release date="2025-04-29" version="1.16.0">
|
||||
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.16.0</url>
|
||||
</release>
|
||||
<release date="2025-04-09" version="1.15.3">
|
||||
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.15.3</url>
|
||||
</release>
|
||||
|
||||
2
pom.xml
2
pom.xml
@@ -3,7 +3,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>cryptomator</artifactId>
|
||||
<version>1.16.0-SNAPSHOT</version>
|
||||
<version>1.17.0-SNAPSHOT</version>
|
||||
<name>Cryptomator Desktop App</name>
|
||||
|
||||
<organization>
|
||||
|
||||
@@ -59,6 +59,7 @@ open module org.cryptomator.desktop {
|
||||
|
||||
uses org.cryptomator.common.locationpresets.LocationPresetsProvider;
|
||||
uses SSLContextProvider;
|
||||
uses org.cryptomator.event.NotificationHandler;
|
||||
|
||||
provides TrayMenuController with AwtTrayMenuController;
|
||||
provides Configurator with LogbackConfiguratorFactory;
|
||||
|
||||
160
src/main/java/org/cryptomator/common/EventMap.java
Normal file
160
src/main/java/org/cryptomator/common/EventMap.java
Normal file
@@ -0,0 +1,160 @@
|
||||
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.Comparator;
|
||||
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
|
||||
* <p>
|
||||
* Use {@link EventMap#put(VaultEvent)} to add an element and {@link EventMap#remove(VaultEvent)} to remove it.
|
||||
* <p>
|
||||
* The map is size restricted to {@value MAX_SIZE} elements. If a _new_ element (i.e. not already present) is added, the least recently added is removed.
|
||||
*/
|
||||
@Singleton
|
||||
public class EventMap implements ObservableMap<EventMap.EventKey, VaultEvent> {
|
||||
|
||||
private static final int MAX_SIZE = 300;
|
||||
|
||||
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) {
|
||||
if (size() == MAX_SIZE) {
|
||||
delegate.entrySet().stream() //
|
||||
.min(Comparator.comparing(entry -> entry.getValue().actualEvent().getTimestamp())) //
|
||||
.ifPresent(oldestEntry -> delegate.remove(oldestEntry.getKey()));
|
||||
}
|
||||
delegate.put(key, e);
|
||||
} else {
|
||||
delegate.put(key, nullOrEntry.incrementCount(e.actualEvent()));
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import org.cryptomator.cryptofs.event.FilesystemEvent;
|
||||
import org.cryptomator.cryptolib.api.CryptoException;
|
||||
import org.cryptomator.cryptolib.api.MasterkeyLoader;
|
||||
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
|
||||
import org.cryptomator.event.VaultEvent;
|
||||
import org.cryptomator.integrations.mount.MountFailedException;
|
||||
import org.cryptomator.integrations.mount.Mountpoint;
|
||||
import org.cryptomator.integrations.mount.UnmountFailedException;
|
||||
@@ -34,6 +35,7 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
|
||||
14
src/main/java/org/cryptomator/event/Answer.java
Normal file
14
src/main/java/org/cryptomator/event/Answer.java
Normal file
@@ -0,0 +1,14 @@
|
||||
package org.cryptomator.event;
|
||||
|
||||
public sealed interface Answer permits Answer.DoNothing, Answer.DoSomething {
|
||||
|
||||
|
||||
record DoNothing() implements Answer {}
|
||||
|
||||
record DoSomething(Runnable action) implements Answer {
|
||||
|
||||
void run() {
|
||||
action.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
15
src/main/java/org/cryptomator/event/NotificationHandler.java
Normal file
15
src/main/java/org/cryptomator/event/NotificationHandler.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package org.cryptomator.event;
|
||||
|
||||
import org.cryptomator.integrations.common.IntegrationsLoader;
|
||||
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public interface NotificationHandler {
|
||||
|
||||
Answer handle(VaultEvent e);
|
||||
|
||||
static Stream<NotificationHandler> loadAll() {
|
||||
return IntegrationsLoader.loadAll(ServiceLoader.load(NotificationHandler.class), NotificationHandler.class);
|
||||
}
|
||||
}
|
||||
27
src/main/java/org/cryptomator/event/VaultEvent.java
Normal file
27
src/main/java/org/cryptomator/event/VaultEvent.java
Normal file
@@ -0,0 +1,27 @@
|
||||
package org.cryptomator.event;
|
||||
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.cryptofs.event.FilesystemEvent;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
public record VaultEvent(Vault v, FilesystemEvent actualEvent, int count) implements Comparable<VaultEvent> {
|
||||
|
||||
public VaultEvent(Vault v, FilesystemEvent actualEvent) {
|
||||
this(v, actualEvent, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(VaultEvent other) {
|
||||
var timeResult = actualEvent.getTimestamp().compareTo(other.actualEvent().getTimestamp());
|
||||
if(timeResult != 0) {
|
||||
return timeResult;
|
||||
} else {
|
||||
return this.equals(other) ? 0 : this.actualEvent.getClass().getName().compareTo(other.actualEvent.getClass().getName());
|
||||
}
|
||||
}
|
||||
|
||||
public VaultEvent incrementCount(FilesystemEvent update) {
|
||||
return new VaultEvent(v, update, count+1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package org.cryptomator.ui.eventview;
|
||||
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@EventViewScoped
|
||||
public class UpdateEventViewController implements FxController {
|
||||
|
||||
@Inject
|
||||
public UpdateEventViewController() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -74,7 +74,6 @@
|
||||
<Tooltip text="%main.vaultDetail.decryptName.tooltip"/>
|
||||
</tooltip>
|
||||
</Button>
|
||||
|
||||
<Region HBox.hgrow="ALWAYS"/>
|
||||
|
||||
<Button text="%main.vaultDetail.stats" minWidth="120" onAction="#showVaultStatistics" contentDisplay="BOTTOM" prefHeight="72">
|
||||
|
||||
@@ -14,17 +14,15 @@
|
||||
spacing="12"
|
||||
alignment="CENTER_LEFT">
|
||||
<!-- Remark Check the containing list view for a fixed cell size before editing height properties -->
|
||||
<children>
|
||||
<VBox alignment="CENTER" minWidth="20">
|
||||
<FontAwesome5IconView fx:id="vaultStateView" glyph="${controller.glyph}" HBox.hgrow="NEVER" glyphSize="16"/>
|
||||
</VBox>
|
||||
<VBox spacing="4" HBox.hgrow="ALWAYS">
|
||||
<Label styleClass="header-label" text="${controller.vault.displayName}"/>
|
||||
<Label styleClass="detail-label" text="${controller.vault.displayablePath}" textOverrun="CENTER_ELLIPSIS" visible="${!controller.compactMode}" managed="${!controller.compactMode}">
|
||||
<tooltip>
|
||||
<Tooltip text="${controller.vault.displayablePath}"/>
|
||||
</tooltip>
|
||||
</Label>
|
||||
</VBox>
|
||||
</children>
|
||||
<VBox alignment="CENTER" minWidth="20">
|
||||
<FontAwesome5IconView fx:id="vaultStateView" glyph="${controller.glyph}" HBox.hgrow="NEVER" glyphSize="16"/>
|
||||
</VBox>
|
||||
<VBox spacing="4" HBox.hgrow="ALWAYS">
|
||||
<Label styleClass="header-label" text="${controller.vault.displayName}"/>
|
||||
<Label styleClass="detail-label" text="${controller.vault.displayablePath}" textOverrun="CENTER_ELLIPSIS" visible="${!controller.compactMode}" managed="${!controller.compactMode}">
|
||||
<tooltip>
|
||||
<Tooltip text="${controller.vault.displayablePath}"/>
|
||||
</tooltip>
|
||||
</Label>
|
||||
</VBox>
|
||||
</HBox>
|
||||
|
||||
Reference in New Issue
Block a user