- increased vault version

- Showing "per vault" MAC authentication failure dialogs
This commit is contained in:
Sebastian Stenzel
2015-06-26 23:35:24 +02:00
parent 48f544ef91
commit 0d3a5b4e70
14 changed files with 407 additions and 138 deletions

View File

@@ -84,13 +84,11 @@ public final class WebDavServer {
/**
* @param workDir Path of encrypted folder.
* @param cryptor A fully initialized cryptor instance ready to en- or decrypt streams.
* @param failingMacCollection A (observable, thread-safe) collection, to which the names of resources are written, whose MAC
* authentication fails.
* @param name The name of the folder. Must be non-empty and only contain any of
* _ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789
* @param failingMacCollection A (observable, thread-safe) collection, to which the names of resources are written, whose MAC authentication fails.
* @param name The name of the folder. Must be non-empty and only contain any of _ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789
* @return servlet
*/
public ServletLifeCycleAdapter createServlet(final Path workDir, final Cryptor cryptor, final Collection<String> failingMacCollection, final String name) {
public ServletLifeCycleAdapter createServlet(final Path workDir, final Cryptor cryptor, final Collection<String> failingMacCollection, final Collection<String> whitelistedResourceCollection, final String name) {
try {
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("name empty");
@@ -101,7 +99,7 @@ public final class WebDavServer {
final URI uri = new URI(null, null, localConnector.getHost(), localConnector.getLocalPort(), "/" + UUID.randomUUID().toString() + "/" + name, null, null);
final ServletContextHandler servletContext = new ServletContextHandler(servletCollection, uri.getRawPath(), ServletContextHandler.SESSIONS);
final ServletHolder servlet = getWebDavServletHolder(workDir.toString(), cryptor, failingMacCollection);
final ServletHolder servlet = getWebDavServletHolder(workDir.toString(), cryptor, failingMacCollection, whitelistedResourceCollection);
servletContext.addServlet(servlet, "/*");
servletCollection.mapContexts();
@@ -113,8 +111,8 @@ public final class WebDavServer {
}
}
private ServletHolder getWebDavServletHolder(final String workDir, final Cryptor cryptor, final Collection<String> failingMacCollection) {
final ServletHolder result = new ServletHolder("Cryptomator-WebDAV-Servlet", new WebDavServlet(cryptor, failingMacCollection));
private ServletHolder getWebDavServletHolder(final String workDir, final Cryptor cryptor, final Collection<String> failingMacCollection, final Collection<String> whitelistedResourceCollection) {
final ServletHolder result = new ServletHolder("Cryptomator-WebDAV-Servlet", new WebDavServlet(cryptor, failingMacCollection, whitelistedResourceCollection));
result.setInitParameter(WebDavServlet.CFG_FS_ROOT, workDir);
return result;
}

View File

@@ -5,15 +5,22 @@ import java.util.Collection;
class CryptoWarningHandler {
private final Collection<String> resourcesWithInvalidMac;
private final Collection<String> whitelistedResources;
public CryptoWarningHandler(Collection<String> resourcesWithInvalidMac) {
public CryptoWarningHandler(Collection<String> resourcesWithInvalidMac, Collection<String> whitelistedResources) {
this.resourcesWithInvalidMac = resourcesWithInvalidMac;
this.whitelistedResources = whitelistedResources;
}
public void macAuthFailed(String resourceName) {
if (!resourcesWithInvalidMac.contains(resourceName)) {
resourcesWithInvalidMac.add(resourceName);
public void macAuthFailed(String resourcePath) {
// collection might be a list, but we don't want duplicates:
if (!resourcesWithInvalidMac.contains(resourcePath)) {
resourcesWithInvalidMac.add(resourcePath);
}
}
public boolean ignoreMac(String resourcePath) {
return whitelistedResources.contains(resourcePath);
}
}

View File

@@ -107,8 +107,8 @@ class EncryptedFile extends AbstractEncryptedNode implements FileConstants {
LOG.warn("Unexpected end of stream (possibly client hung up).");
} catch (MacAuthenticationFailedException e) {
LOG.warn("File integrity violation for " + getLocator().getResourcePath());
cryptoWarningHandler.macAuthFailed(getLocator().getResourcePath());
throw new IOException("Error decrypting file " + filePath.toString(), e);
// cryptoWarningHandler.macAuthFailed(getLocator().getResourcePath());
} catch (DecryptFailedException e) {
throw new IOException("Error decrypting file " + filePath.toString(), e);
}

View File

@@ -20,6 +20,7 @@ import org.apache.jackrabbit.webdav.io.OutputContext;
import org.apache.jackrabbit.webdav.lock.LockManager;
import org.cryptomator.crypto.Cryptor;
import org.cryptomator.crypto.exceptions.DecryptFailedException;
import org.cryptomator.crypto.exceptions.MacAuthenticationFailedException;
import org.eclipse.jetty.http.HttpHeader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -111,19 +112,23 @@ class EncryptedFilePart extends EncryptedFile {
public void spool(OutputContext outputContext) throws IOException {
assert Files.isRegularFile(filePath);
outputContext.setModificationTime(Files.getLastModifiedTime(filePath).toMillis());
try (final SeekableByteChannel channel = Files.newByteChannel(filePath, StandardOpenOption.READ)) {
final Long fileSize = cryptor.decryptedContentLength(channel);
try (final SeekableByteChannel c = Files.newByteChannel(filePath, StandardOpenOption.READ)) {
final Long fileSize = cryptor.decryptedContentLength(c);
final Pair<Long, Long> range = getUnionRange(fileSize);
final Long rangeLength = range.getRight() - range.getLeft() + 1;
outputContext.setContentLength(rangeLength);
outputContext.setProperty(HttpHeader.CONTENT_RANGE.asString(), getContentRangeHeader(range.getLeft(), range.getRight(), fileSize));
if (outputContext.hasStream()) {
cryptor.decryptRange(channel, outputContext.getOutputStream(), range.getLeft(), rangeLength);
cryptor.decryptRange(c, outputContext.getOutputStream(), range.getLeft(), rangeLength);
}
} catch (EOFException e) {
if (LOG.isDebugEnabled()) {
LOG.trace("Unexpected end of stream during delivery of partial content (client hung up).");
}
} catch (MacAuthenticationFailedException e) {
LOG.warn("File integrity violation for " + getLocator().getResourcePath());
cryptoWarningHandler.macAuthFailed(getLocator().getResourcePath());
throw new IOException("Error decrypting file " + filePath.toString(), e);
} catch (DecryptFailedException e) {
throw new IOException("Error decrypting file " + filePath.toString(), e);
}

View File

@@ -31,10 +31,10 @@ public class WebDavServlet extends AbstractWebdavServlet {
private final Cryptor cryptor;
private final CryptoWarningHandler cryptoWarningHandler;
public WebDavServlet(final Cryptor cryptor, final Collection<String> failingMacCollection) {
public WebDavServlet(final Cryptor cryptor, final Collection<String> failingMacCollection, final Collection<String> whitelistedResourceCollection) {
super();
this.cryptor = cryptor;
this.cryptoWarningHandler = new CryptoWarningHandler(failingMacCollection);
this.cryptoWarningHandler = new CryptoWarningHandler(failingMacCollection, whitelistedResourceCollection);
}
@Override

View File

@@ -7,7 +7,7 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder;
@JsonPropertyOrder(value = {"version", "scryptSalt", "scryptCostParam", "scryptBlockSize", "keyLength", "primaryMasterKey", "hMacMasterKey"})
public class KeyFile implements Serializable {
static final Integer CURRENT_VERSION = 1;
static final Integer CURRENT_VERSION = 2;
private static final long serialVersionUID = 8578363158959619885L;
private Integer version;

View File

@@ -1,8 +1,8 @@
package org.cryptomator.ui.controllers;
import javafx.application.Application;
import javafx.collections.ListChangeListener;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
import javafx.collections.WeakListChangeListener;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
@@ -11,14 +11,18 @@ import javafx.stage.Stage;
import javax.inject.Inject;
import org.cryptomator.ui.model.Vault;
public class MacWarningsController {
@FXML
private ListView<String> warningsList;
private Stage stage;
private final Application application;
private final ListChangeListener<? super String> macWarningsListener = this::warningsDidChange;
private final ListChangeListener<? super String> weakMacWarningsListener = new WeakListChangeListener<>(macWarningsListener);
private Stage stage;
private Vault vault;
@Inject
public MacWarningsController(Application application) {
@@ -27,6 +31,7 @@ public class MacWarningsController {
@FXML
private void didClickDismissButton(ActionEvent event) {
warningsList.getItems().removeListener(weakMacWarningsListener);
stage.hide();
}
@@ -35,14 +40,10 @@ public class MacWarningsController {
application.getHostServices().showDocument("https://cryptomator.org/help.html#macWarning");
}
public void setMacWarnings(ObservableList<String> macWarnings) {
this.warningsList.setItems(macWarnings);
this.warningsList.getItems().addListener(new WeakListChangeListener<String>(this::warningsDidChange));
}
// closes this window automatically, if all warnings disappeared (e.g. due to an unmount event)
private void warningsDidChange(Change<? extends String> change) {
if (change.getList().isEmpty()) {
if (change.getList().isEmpty() && stage != null) {
change.getList().removeListener(weakMacWarningsListener);
stage.hide();
}
}
@@ -55,4 +56,10 @@ public class MacWarningsController {
this.stage = stage;
}
public void setVault(Vault vault) {
this.vault = vault;
this.warningsList.setItems(vault.getNamesOfResourcesWithInvalidMac());
this.warningsList.getItems().addListener(weakMacWarningsListener);
}
}

View File

@@ -13,25 +13,21 @@ import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.ResourceBundle;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.collections.SetChangeListener;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.geometry.Side;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
@@ -51,8 +47,6 @@ import org.cryptomator.ui.controls.DirectoryListCell;
import org.cryptomator.ui.model.Vault;
import org.cryptomator.ui.model.VaultFactory;
import org.cryptomator.ui.settings.Settings;
import org.cryptomator.ui.util.ActiveWindowStyleSupport;
import org.cryptomator.ui.util.ObservableSetAggregator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -85,9 +79,6 @@ public class MainController implements Initializable, InitializationListener, Un
private final ControllerFactory controllerFactory;
private final Settings settings;
private final VaultFactory vaultFactoy;
private final ObservableList<String> aggregatedMacWarnings;
private final SetChangeListener<String> macWarningsAggregator;
private final AtomicBoolean macWarningsWindowVisible;
private ResourceBundle rb;
@@ -97,9 +88,6 @@ public class MainController implements Initializable, InitializationListener, Un
this.controllerFactory = controllerFactory;
this.settings = settings;
this.vaultFactoy = vaultFactoy;
this.aggregatedMacWarnings = FXCollections.observableList(new ArrayList<>());
this.macWarningsAggregator = new ObservableSetAggregator<>(this.aggregatedMacWarnings);
this.macWarningsWindowVisible = new AtomicBoolean();
}
@Override
@@ -110,8 +98,6 @@ public class MainController implements Initializable, InitializationListener, Un
vaultList.setItems(items);
vaultList.setCellFactory(this::createDirecoryListCell);
vaultList.getSelectionModel().getSelectedItems().addListener(this::selectedVaultDidChange);
aggregatedMacWarnings.addListener(this::macWarningsDidChange);
}
@FXML
@@ -233,12 +219,6 @@ public class MainController implements Initializable, InitializationListener, Un
showChangePasswordView(selectedVault);
}
private void macWarningsDidChange(ListChangeListener.Change<? extends String> change) {
if (aggregatedMacWarnings.size() > 0) {
Platform.runLater(this::showMacWarningsWindow);
}
}
// ****************************************
// Subcontroller for right panel
// ****************************************
@@ -293,7 +273,6 @@ public class MainController implements Initializable, InitializationListener, Un
@Override
public void didUnlock(UnlockController ctrl) {
ctrl.getVault().getNamesOfResourcesWithInvalidMac().addListener(this.macWarningsAggregator);
showUnlockedView(ctrl.getVault());
Platform.setImplicitExit(false);
}
@@ -306,7 +285,6 @@ public class MainController implements Initializable, InitializationListener, Un
@Override
public void didLock(UnlockedController ctrl) {
ctrl.getVault().getNamesOfResourcesWithInvalidMac().removeListener(this.macWarningsAggregator);
showUnlockView(ctrl.getVault());
if (getUnlockedDirectories().isEmpty()) {
Platform.setImplicitExit(true);
@@ -324,37 +302,6 @@ public class MainController implements Initializable, InitializationListener, Un
showUnlockView(ctrl.getVault());
}
private void showMacWarningsWindow() {
if (macWarningsWindowVisible.getAndSet(true) == false) {
try {
final FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/mac_warnings.fxml"), rb);
loader.setControllerFactory(controllerFactory);
final Parent root = loader.load();
final Stage stage = new Stage();
stage.setTitle(rb.getString("macWarnings.windowTitle"));
stage.setScene(new Scene(root));
stage.sizeToScene();
stage.setResizable(false);
stage.setOnHidden(this::onHideMacWarningsWindow);
ActiveWindowStyleSupport.startObservingFocus(stage);
final MacWarningsController ctrl = loader.getController();
ctrl.setMacWarnings(this.aggregatedMacWarnings);
ctrl.setStage(stage);
stage.show();
} catch (IOException e) {
throw new IllegalStateException("Failed to load fxml file.", e);
}
}
}
private void onHideMacWarningsWindow(WindowEvent event) {
macWarningsWindowVisible.set(false);
aggregatedMacWarnings.clear();
}
/* Convenience */
public Collection<Vault> getDirectories() {

View File

@@ -8,31 +8,49 @@
******************************************************************************/
package org.cryptomator.ui.controllers;
import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
import java.util.concurrent.atomic.AtomicBoolean;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Platform;
import javafx.collections.ListChangeListener;
import javafx.collections.WeakListChangeListener;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart.Data;
import javafx.scene.chart.XYChart.Series;
import javafx.scene.control.Label;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import javafx.util.Duration;
import org.cryptomator.crypto.CryptorIOSampling;
import org.cryptomator.ui.MainModule.ControllerFactory;
import org.cryptomator.ui.model.Vault;
import org.cryptomator.ui.util.ActiveWindowStyleSupport;
import org.cryptomator.ui.util.mount.CommandFailedException;
import com.google.inject.Inject;
public class UnlockedController implements Initializable {
private static final int IO_SAMPLING_STEPS = 100;
private static final double IO_SAMPLING_INTERVAL = 0.25;
private final ControllerFactory controllerFactory;
private final ListChangeListener<String> macWarningsListener = this::macWarningsDidChange;
private final ListChangeListener<String> weakMacWarningsListener = new WeakListChangeListener<>(macWarningsListener);
private final AtomicBoolean macWarningsWindowVisible = new AtomicBoolean();
private LockListener listener;
private Vault vault;
private Timeline ioAnimation;
@@ -48,6 +66,11 @@ public class UnlockedController implements Initializable {
private ResourceBundle rb;
@Inject
public UnlockedController(ControllerFactory controllerFactory) {
this.controllerFactory = controllerFactory;
}
@Override
public void initialize(URL url, ResourceBundle rb) {
this.rb = rb;
@@ -61,6 +84,7 @@ public class UnlockedController implements Initializable {
messageLabel.setText(rb.getString("unlocked.label.unmountFailed"));
return;
}
vault.getNamesOfResourcesWithInvalidMac().removeListener(weakMacWarningsListener);
vault.stopServer();
vault.setUnlocked(false);
if (listener != null) {
@@ -68,6 +92,46 @@ public class UnlockedController implements Initializable {
}
}
// ****************************************
// MAC Auth Warnings
// ****************************************
private void macWarningsDidChange(ListChangeListener.Change<? extends String> change) {
if (change.getList().size() > 0) {
Platform.runLater(this::showMacWarningsWindow);
}
}
private void showMacWarningsWindow() {
if (macWarningsWindowVisible.getAndSet(true) == false) {
try {
final FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/mac_warnings.fxml"), rb);
loader.setControllerFactory(controllerFactory);
final Parent root = loader.load();
final Stage stage = new Stage();
stage.setTitle(String.format(rb.getString("macWarnings.windowTitle"), vault.getName()));
stage.setScene(new Scene(root));
stage.sizeToScene();
stage.setResizable(false);
stage.setOnHidden(this::onHideMacWarningsWindow);
ActiveWindowStyleSupport.startObservingFocus(stage);
final MacWarningsController ctrl = loader.getController();
ctrl.setVault(vault);
ctrl.setStage(stage);
stage.show();
} catch (IOException e) {
throw new IllegalStateException("Failed to load fxml file.", e);
}
}
}
private void onHideMacWarningsWindow(WindowEvent event) {
macWarningsWindowVisible.set(false);
}
// ****************************************
// IO Graph
// ****************************************
@@ -128,11 +192,12 @@ public class UnlockedController implements Initializable {
return vault;
}
public void setVault(Vault directory) {
this.vault = directory;
public void setVault(Vault vault) {
this.vault = vault;
vault.getNamesOfResourcesWithInvalidMac().addListener(weakMacWarningsListener);
if (directory.getCryptor() instanceof CryptorIOSampling) {
startIoSampling((CryptorIOSampling) directory.getCryptor());
if (vault.getCryptor() instanceof CryptorIOSampling) {
startIoSampling((CryptorIOSampling) vault.getCryptor());
} else {
ioGraph.setVisible(false);
}

View File

@@ -6,12 +6,14 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.text.Normalizer;
import java.text.Normalizer.Form;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableSet;
import javafx.collections.ObservableList;
import javax.security.auth.DestroyFailedException;
@@ -43,7 +45,8 @@ public class Vault implements Serializable {
private final WebDavMounter mounter;
private final DeferredCloser closer;
private final ObjectProperty<Boolean> unlocked = new SimpleObjectProperty<Boolean>(this, "unlocked", Boolean.FALSE);
private final ObservableSet<String> namesOfResourcesWithInvalidMac = FXThreads.observableSetOnMainThread(FXCollections.observableSet());
private final ObservableList<String> namesOfResourcesWithInvalidMac = FXThreads.observableListOnMainThread(FXCollections.observableArrayList());
private final Set<String> whitelistedResourcesWithInvalidMac = new HashSet<>();
private String mountName;
private DeferredClosable<ServletLifeCycleAdapter> webDavServlet = DeferredClosable.empty();
@@ -77,11 +80,12 @@ public class Vault implements Serializable {
public synchronized boolean startServer() {
namesOfResourcesWithInvalidMac.clear();
whitelistedResourcesWithInvalidMac.clear();
Optional<ServletLifeCycleAdapter> o = webDavServlet.get();
if (o.isPresent() && o.get().isRunning()) {
return false;
}
ServletLifeCycleAdapter servlet = server.createServlet(path, cryptor, namesOfResourcesWithInvalidMac, mountName);
ServletLifeCycleAdapter servlet = server.createServlet(path, cryptor, namesOfResourcesWithInvalidMac, whitelistedResourcesWithInvalidMac, mountName);
if (servlet.start()) {
webDavServlet = closer.closeLater(servlet);
return true;
@@ -102,6 +106,7 @@ public class Vault implements Serializable {
LOG.error("Destruction of cryptor throw an exception.", e);
}
setUnlocked(false);
whitelistedResourcesWithInvalidMac.clear();
namesOfResourcesWithInvalidMac.clear();
}
@@ -160,10 +165,14 @@ public class Vault implements Serializable {
return mountName;
}
public ObservableSet<String> getNamesOfResourcesWithInvalidMac() {
public ObservableList<String> getNamesOfResourcesWithInvalidMac() {
return namesOfResourcesWithInvalidMac;
}
public Set<String> getWhitelistedResourcesWithInvalidMac() {
return whitelistedResourcesWithInvalidMac;
}
/**
* Tries to form a similar string using the regular latin alphabet.
*

View File

@@ -15,6 +15,7 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import javafx.application.Platform;
import javafx.collections.ObservableList;
import javafx.collections.ObservableSet;
/**
@@ -53,8 +54,7 @@ public final class FXThreads {
};
/**
* Waits for the given task to complete and notifies the given successCallback. If an exception occurs, the callback will never be
* called. If you are interested in the exception, use
* Waits for the given task to complete and notifies the given successCallback. If an exception occurs, the callback will never be called. If you are interested in the exception, use
* {@link #runOnMainThreadWhenFinished(ExecutorService, Future, CallbackWhenTaskFinished, CallbackWhenTaskFailed)} instead.
*
* <pre>
@@ -74,8 +74,7 @@ public final class FXThreads {
}
/**
* Waits for the given task to complete and notifies the given successCallback. If an exception occurs, the callback will never be
* called. If you are interested in the exception, use
* Waits for the given task to complete and notifies the given successCallback. If an exception occurs, the callback will never be called. If you are interested in the exception, use
* {@link #runOnMainThreadWhenFinished(ExecutorService, Future, CallbackWhenTaskFinished, CallbackWhenTaskFailed)} instead.
*
* <pre>
@@ -123,4 +122,8 @@ public final class FXThreads {
return new ObservableSetOnMainThread<E>(set);
}
public static <E> ObservableList<E> observableListOnMainThread(ObservableList<E> list) {
return new ObservableListOnMainThread<E>(list);
}
}

View File

@@ -0,0 +1,272 @@
package org.cryptomator.ui.util;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.collections.ListChangeListener;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
class ObservableListOnMainThread<E> implements ObservableList<E> {
private final ObservableList<E> list;
private final Collection<InvalidationListener> invalidationListeners;
private final Collection<ListChangeListener<? super E>> listChangeListeners;
public ObservableListOnMainThread(ObservableList<E> list) {
this.list = list;
this.invalidationListeners = new HashSet<>();
this.listChangeListeners = new HashSet<>();
this.list.addListener(this::invalidated);
this.list.addListener(this::onChanged);
}
@Override
public int size() {
return list.size();
}
@Override
public boolean isEmpty() {
return list.isEmpty();
}
@Override
public boolean contains(Object o) {
return list.contains(o);
}
@Override
public Iterator<E> iterator() {
return list.iterator();
}
@Override
public Object[] toArray() {
return list.toArray();
}
@Override
public <T> T[] toArray(T[] a) {
return list.toArray(a);
}
@Override
public boolean add(E e) {
return list.add(e);
}
@Override
public boolean remove(Object o) {
return list.remove(o);
}
@Override
public boolean containsAll(Collection<?> c) {
return list.containsAll(c);
}
@Override
public boolean addAll(Collection<? extends E> c) {
return list.addAll(c);
}
@Override
public boolean addAll(int index, Collection<? extends E> c) {
return list.addAll(index, c);
}
@Override
public boolean removeAll(Collection<?> c) {
return list.removeAll(c);
}
@Override
public boolean retainAll(Collection<?> c) {
return list.retainAll(c);
}
@Override
public void clear() {
list.clear();
}
@Override
public E get(int index) {
return list.get(index);
}
@Override
public E set(int index, E element) {
return list.set(index, element);
}
@Override
public void add(int index, E element) {
list.add(index, element);
}
@Override
public E remove(int index) {
return list.remove(index);
}
@Override
public int indexOf(Object o) {
return list.indexOf(o);
}
@Override
public int lastIndexOf(Object o) {
return list.lastIndexOf(o);
}
@Override
public ListIterator<E> listIterator() {
return list.listIterator();
}
@Override
public ListIterator<E> listIterator(int index) {
return list.listIterator(index);
}
@Override
public List<E> subList(int fromIndex, int toIndex) {
return list.subList(fromIndex, toIndex);
}
@Override
public boolean addAll(@SuppressWarnings("unchecked") E... elements) {
return list.addAll(elements);
}
@Override
public boolean setAll(@SuppressWarnings("unchecked") E... elements) {
return list.addAll(elements);
}
@Override
public boolean setAll(Collection<? extends E> col) {
return list.setAll(col);
}
@Override
public boolean removeAll(@SuppressWarnings("unchecked") E... elements) {
return list.removeAll(elements);
}
@Override
public boolean retainAll(@SuppressWarnings("unchecked") E... elements) {
return list.retainAll(elements);
}
@Override
public void remove(int from, int to) {
list.remove(from, to);
}
private void invalidated(Observable observable) {
Platform.runLater(() -> {
for (InvalidationListener listener : invalidationListeners) {
listener.invalidated(this);
}
});
}
@Override
public void addListener(InvalidationListener listener) {
invalidationListeners.add(listener);
}
@Override
public void removeListener(InvalidationListener listener) {
invalidationListeners.remove(listener);
}
private void onChanged(Change<? extends E> change) {
final Change<? extends E> c = new ListChange(change);
Platform.runLater(() -> {
for (ListChangeListener<? super E> listener : listChangeListeners) {
listener.onChanged(c);
}
});
}
@Override
public void addListener(ListChangeListener<? super E> listener) {
listChangeListeners.add(listener);
}
@Override
public void removeListener(ListChangeListener<? super E> listener) {
listChangeListeners.add(listener);
}
private class ListChange extends ListChangeListener.Change<E> {
private final Change<? extends E> originalChange;
public ListChange(Change<? extends E> change) {
super(ObservableListOnMainThread.this);
this.originalChange = change;
}
@Override
public boolean wasAdded() {
return originalChange.wasAdded();
}
@Override
public boolean wasRemoved() {
return originalChange.wasRemoved();
}
@Override
public boolean next() {
return originalChange.next();
}
@Override
public void reset() {
originalChange.reset();
}
@Override
public int getFrom() {
return originalChange.getFrom();
}
@Override
public int getTo() {
return originalChange.getTo();
}
@Override
@SuppressWarnings("unchecked")
public List<E> getRemoved() {
return (List<E>) originalChange.getRemoved();
}
@Override
protected int[] getPermutation() {
if (originalChange.wasPermutated()) {
int[] permutations = new int[originalChange.getTo() - originalChange.getFrom()];
for (int i = 0; i < permutations.length; i++) {
permutations[i] = originalChange.getPermutation(i);
}
return permutations;
} else {
return new int[0];
}
}
}
}

View File

@@ -1,44 +0,0 @@
/*******************************************************************************
* Copyright (c) 2014 cryptomator.org
* This file is licensed under the terms of the MIT license.
* See the LICENSE.txt file for more info.
*
* Contributors:
* Sebastian Stenzel - initial implementation
******************************************************************************/
package org.cryptomator.ui.util;
import java.util.Collection;
import javafx.collections.ObservableSet;
import javafx.collections.SetChangeListener;
/**
* From the moment on, this aggregator is added as an observer to one or many {@link ObservableSet}s, change-events will be passed through
* to the given aggregation.
*/
public class ObservableSetAggregator<E> implements SetChangeListener<E> {
private final Collection<E> aggregation;
/**
* @param aggregation Set to which elements from observed subsets shall be added.
*/
public ObservableSetAggregator(final Collection<E> aggregation) {
this.aggregation = aggregation;
}
@Override
public void onChanged(Change<? extends E> change) {
if (change.getSet() == aggregation) {
// break cycle if aggregator observes aggregation
return;
}
if (change.wasAdded()) {
aggregation.add(change.getElementAdded());
} else if (change.wasRemoved()) {
aggregation.remove(change.getElementRemoved());
}
}
}

View File

@@ -56,7 +56,7 @@ unlocked.label.unmountFailed=Ejecting drive failed.
unlocked.ioGraph.yAxis.label=Throughput (MiB/s)
# mac_warnings.fxml
macWarnings.windowTitle=Danger - MAC authentication failed
macWarnings.windowTitle=Danger - Corrupted file in %s
macWarnings.message=Cryptomator detected potentially malicious corruptions in the following files:
macWarnings.moreInformationButton=Learn more
macWarnings.dismissButton=I promise to be careful