mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-07-28 19:12:38 +00:00
Prevents starting a second instance of the GUI and forwards
main-method-arguments to the running instance. Command line arguments are treated by showing the corresponding folder in the GUI. If an argument is a folder, it is shown directly. If an argument is a .masterkey.json file, the parent directory is shown. If an argument does not exist, but the folder can be created, the newly created folder is shown. It was necessary to move the main function away from the MainApplication class because running the main method of a class, which extends the javafx Application class, will start a non-daemon thread. This prevents the VM from exiting naturally. OSX needs its own mechanism, which is implemented in OS-specific code. It is vital that the required handler is added in the main thread of the application, not the Java FX thread, which is a bit awkward to implement. Since it is possible to open .cryptomator packages on OSX, this extension is now hidden in the folder list.
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
package org.cryptomator.ui.util;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.concurrent.ConcurrentSkipListMap;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class ListenerRegistryTest {
|
||||
/**
|
||||
* This test looks at how concurrent modifications affect the iterator of a
|
||||
* {@link ConcurrentSkipListMap}. It shows that concurrent modifications
|
||||
* work just fine, however the state of the iterator including the next
|
||||
* value are advanced during retrieval of a value, so it's not possible to
|
||||
* remove the next value.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testConcurrentSkipListMap() throws Exception {
|
||||
ConcurrentSkipListMap<Integer, Integer> map = new ConcurrentSkipListMap<>();
|
||||
|
||||
map.put(1, 1);
|
||||
map.put(2, 2);
|
||||
map.put(3, 3);
|
||||
map.put(4, 4);
|
||||
map.put(5, 5);
|
||||
|
||||
final Iterator<Integer> iterator = map.values().iterator();
|
||||
|
||||
assertTrue(iterator.hasNext());
|
||||
assertEquals((Integer) 1, iterator.next());
|
||||
map.remove(2);
|
||||
assertTrue(iterator.hasNext());
|
||||
// iterator returns 2 anyway.
|
||||
assertEquals((Integer) 2, iterator.next());
|
||||
assertTrue(iterator.hasNext());
|
||||
map.remove(4);
|
||||
assertEquals((Integer) 3, iterator.next());
|
||||
assertTrue(iterator.hasNext());
|
||||
// this time we removed 4 before retrieving 3, so it is skipped.
|
||||
assertEquals((Integer) 5, iterator.next());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
package org.cryptomator.ui.util;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentSkipListSet;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ForkJoinTask;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.cryptomator.ui.util.SingleInstanceManager.LocalInstance;
|
||||
import org.cryptomator.ui.util.SingleInstanceManager.MessageListener;
|
||||
import org.cryptomator.ui.util.SingleInstanceManager.RemoteInstance;
|
||||
import org.junit.Test;
|
||||
|
||||
public class SingleInstanceManagerTest {
|
||||
@Test(timeout = 1000)
|
||||
public void testTryFillTimeout() throws Exception {
|
||||
try (final ServerSocket socket = new ServerSocket(0)) {
|
||||
// we need to asynchronously accept the connection
|
||||
final ForkJoinTask<?> forked = ForkJoinTask.adapt(() -> {
|
||||
try {
|
||||
socket.setSoTimeout(1000);
|
||||
|
||||
socket.accept();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}).fork();
|
||||
|
||||
try (SocketChannel channel = SocketChannel.open()) {
|
||||
channel.configureBlocking(false);
|
||||
channel.connect(new InetSocketAddress(InetAddress.getLoopbackAddress(), socket.getLocalPort()));
|
||||
TimeoutTask.attempt(t -> channel.finishConnect(), 100, 1);
|
||||
final ByteBuffer buffer = ByteBuffer.allocate(1);
|
||||
SingleInstanceManager.tryFill(channel, buffer, 100);
|
||||
assertTrue(buffer.hasRemaining());
|
||||
}
|
||||
|
||||
forked.join();
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = 1000)
|
||||
public void testTryFill() throws Exception {
|
||||
try (final ServerSocket socket = new ServerSocket(0)) {
|
||||
// we need to asynchronously accept the connection
|
||||
final ForkJoinTask<?> forked = ForkJoinTask.adapt(() -> {
|
||||
try {
|
||||
socket.setSoTimeout(1000);
|
||||
|
||||
socket.accept().getOutputStream().write(1);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}).fork();
|
||||
|
||||
try (SocketChannel channel = SocketChannel.open()) {
|
||||
channel.configureBlocking(false);
|
||||
channel.connect(new InetSocketAddress(InetAddress.getLoopbackAddress(), socket.getLocalPort()));
|
||||
TimeoutTask.attempt(t -> channel.finishConnect(), 100, 1);
|
||||
final ByteBuffer buffer = ByteBuffer.allocate(1);
|
||||
SingleInstanceManager.tryFill(channel, buffer, 100);
|
||||
assertFalse(buffer.hasRemaining());
|
||||
}
|
||||
|
||||
forked.join();
|
||||
}
|
||||
}
|
||||
|
||||
String appKey = "APPKEY";
|
||||
|
||||
@Test
|
||||
public void testOneMessage() throws Exception {
|
||||
ExecutorService exec = Executors.newCachedThreadPool();
|
||||
|
||||
try {
|
||||
final LocalInstance server = SingleInstanceManager.startLocalInstance(appKey, exec);
|
||||
final Optional<RemoteInstance> r = SingleInstanceManager.getRemoteInstance(appKey);
|
||||
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
final MessageListener listener = spy(new MessageListener() {
|
||||
@Override
|
||||
public void handleMessage(String message) {
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
server.registerListener(listener);
|
||||
|
||||
assertTrue(r.isPresent());
|
||||
|
||||
String message = "Is this thing on?";
|
||||
assertTrue(r.get().sendMessage(message, 100));
|
||||
System.out.println("wrote message");
|
||||
|
||||
latch.await(10, TimeUnit.SECONDS);
|
||||
|
||||
verify(listener).handleMessage(message);
|
||||
} finally {
|
||||
exec.shutdownNow();
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = 60000)
|
||||
public void testALotOfMessages() throws Exception {
|
||||
final int connectors = 256;
|
||||
final int messagesPerConnector = 256;
|
||||
|
||||
ExecutorService exec = Executors.newSingleThreadExecutor();
|
||||
ExecutorService exec2 = Executors.newFixedThreadPool(16);
|
||||
|
||||
try (final LocalInstance server = SingleInstanceManager.startLocalInstance(appKey, exec)) {
|
||||
|
||||
Set<String> sentMessages = new ConcurrentSkipListSet<>();
|
||||
Set<String> receivedMessages = new HashSet<>();
|
||||
|
||||
CountDownLatch sendLatch = new CountDownLatch(connectors);
|
||||
CountDownLatch receiveLatch = new CountDownLatch(connectors * messagesPerConnector);
|
||||
|
||||
server.registerListener(message -> {
|
||||
receivedMessages.add(message);
|
||||
receiveLatch.countDown();
|
||||
});
|
||||
|
||||
Set<RemoteInstance> instances = Collections.synchronizedSet(new HashSet<>());
|
||||
|
||||
for (int i = 0; i < connectors; i++) {
|
||||
exec2.submit(() -> {
|
||||
try {
|
||||
final Optional<RemoteInstance> r = SingleInstanceManager.getRemoteInstance(appKey);
|
||||
assertTrue(r.isPresent());
|
||||
instances.add(r.get());
|
||||
|
||||
for (int j = 0; j < messagesPerConnector; j++) {
|
||||
exec2.submit(() -> {
|
||||
try {
|
||||
for (;;) {
|
||||
final String message = UUID.randomUUID().toString();
|
||||
if (!sentMessages.add(message)) {
|
||||
continue;
|
||||
}
|
||||
r.get().sendMessage(message, 100);
|
||||
break;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
sendLatch.countDown();
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
assertTrue(sendLatch.await(1, TimeUnit.MINUTES));
|
||||
|
||||
exec2.shutdown();
|
||||
assertTrue(exec2.awaitTermination(1, TimeUnit.MINUTES));
|
||||
|
||||
assertTrue(receiveLatch.await(1, TimeUnit.MINUTES));
|
||||
|
||||
assertEquals(sentMessages, receivedMessages);
|
||||
|
||||
for (RemoteInstance remoteInstance : instances) {
|
||||
try {
|
||||
remoteInstance.close();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
exec.shutdownNow();
|
||||
exec2.shutdownNow();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user