Skip to content

Commit 3c59a10

Browse files
Merge pull request #44 from cryptomator/feature/new-mount-api
implement new mount api
2 parents ebea135 + d3a6e8c commit 3c59a10

26 files changed

+736
-797
lines changed

pom.xml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
1919

2020
<!-- dependencies -->
21+
<integrations-api.version>1.2.0-beta1</integrations-api.version>
2122
<webdavservlet.version>1.2.2</webdavservlet.version>
2223
<dagger.version>2.41</dagger.version>
2324
<jetty.version>10.0.12</jetty.version>
@@ -58,6 +59,13 @@
5859
<version>${webdavservlet.version}</version>
5960
</dependency>
6061

62+
<!-- Mount API -->
63+
<dependency>
64+
<groupId>org.cryptomator</groupId>
65+
<artifactId>integrations-api</artifactId>
66+
<version>${integrations-api.version}</version>
67+
</dependency>
68+
6169
<!-- Jetty -->
6270
<dependency>
6371
<groupId>org.eclipse.jetty</groupId>
@@ -77,6 +85,14 @@
7785
<version>${guava.version}</version>
7886
</dependency>
7987

88+
<!-- Correctness -->
89+
<dependency>
90+
<groupId>org.jetbrains</groupId>
91+
<artifactId>annotations</artifactId>
92+
<version>23.0.0</version>
93+
<scope>provided</scope>
94+
</dependency>
95+
8096
<!-- Logging -->
8197
<dependency>
8298
<groupId>org.slf4j</groupId>

src/main/java/module-info.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
1+
import org.cryptomator.frontend.webdav.mount.FallbackMounter;
2+
import org.cryptomator.frontend.webdav.mount.LinuxGioMounter;
3+
import org.cryptomator.frontend.webdav.mount.MacAppleScriptMounter;
4+
import org.cryptomator.frontend.webdav.mount.WindowsMounter;
5+
import org.cryptomator.integrations.mount.MountService;
6+
17
module org.cryptomator.frontend.webdav {
28
requires org.cryptomator.frontend.webdav.servlet;
9+
requires org.cryptomator.integrations.api;
310
requires org.eclipse.jetty.server;
411
requires org.eclipse.jetty.servlet;
512
requires com.google.common;
613
requires org.slf4j;
14+
requires static org.jetbrains.annotations;
715

8-
exports org.cryptomator.frontend.webdav;
9-
exports org.cryptomator.frontend.webdav.mount;
10-
exports org.cryptomator.frontend.webdav.servlet;
16+
provides MountService with MacAppleScriptMounter, FallbackMounter, WindowsMounter, LinuxGioMounter;
1117
}

src/main/java/org/cryptomator/frontend/webdav/WebDavServer.java

Lines changed: 3 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
/**
2424
* The WebDAV server, that WebDAV servlets can be added to using {@link #createWebDavServlet(Path, String)}.
2525
*
26-
* An instance of this class can be obtained via {@link #create()}.
26+
* An instance of this class can be obtained via {@link #create(InetSocketAddress)}.
2727
*/
2828
public class WebDavServer {
2929

@@ -41,44 +41,8 @@ public class WebDavServer {
4141
this.servletCollectionCtx = servletCollectionCtx;
4242
}
4343

44-
public static WebDavServer create() {
45-
return WebDavServerFactory.createWebDavServer();
46-
}
47-
48-
/**
49-
* Reconfigures the server socket to listen on the specified bindAddr and port.
50-
*
51-
* @param bindAddr Hostname or IP address, the WebDAV server's network interface should bind to. Use <code>0.0.0.0</code> to listen to all interfaces.
52-
* @param port TCP port or <code>0</code> to use an auto-assigned port.
53-
* @throws ServerLifecycleException If any exception occurs during socket reconfiguration (e.g. port not available).
54-
*/
55-
public void bind(String bindAddr, int port) {
56-
this.bind(InetSocketAddress.createUnresolved(bindAddr, port));
57-
}
58-
59-
/**
60-
* Reconfigures the server socket to listen on the specified bindAddr and port.
61-
*
62-
* @param socketBindAddress Socket address and port of the server. Use <code>0.0.0.0:0</code> to listen on all interfaces and auto-assign a port.
63-
* @throws ServerLifecycleException If any exception occurs during socket reconfiguration (e.g. port not available).
64-
*/
65-
public void bind(InetSocketAddress socketBindAddress) {
66-
try {
67-
localConnector.stop();
68-
LOG.info("Binding server socket to {}:{}", socketBindAddress.getHostString(), socketBindAddress.getPort());
69-
localConnector.setHost(socketBindAddress.getHostString());
70-
localConnector.setPort(socketBindAddress.getPort());
71-
localConnector.start();
72-
} catch (Exception e) {
73-
throw new ServerLifecycleException("Failed to restart socket.", e);
74-
}
75-
}
76-
77-
/**
78-
* @return <code>true</code> if the server is currently running.
79-
*/
80-
public boolean isRunning() {
81-
return server.isRunning();
44+
public static WebDavServer create(InetSocketAddress bindAddr) {
45+
return WebDavServerFactory.createWebDavServer(bindAddr);
8246
}
8347

8448
/**

src/main/java/org/cryptomator/frontend/webdav/WebDavServerFactory.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import org.eclipse.jetty.servlet.ServletHolder;
1717
import org.eclipse.jetty.util.thread.ExecutorThreadPool;
1818

19+
import java.net.InetSocketAddress;
1920
import java.util.HashSet;
2021
import java.util.concurrent.BlockingQueue;
2122
import java.util.concurrent.LinkedBlockingQueue;
@@ -66,10 +67,12 @@ private static Server createServer(ExecutorThreadPool threadPool, ContextHandler
6667
return server;
6768
}
6869

69-
private static ServerConnector createServerConnector(Server server) {
70+
private static ServerConnector createServerConnector(Server server, InetSocketAddress bindAddr) {
7071
HttpConfiguration config = new HttpConfiguration();
7172
config.setUriCompliance(UriCompliance.from("0,AMBIGUOUS_PATH_SEPARATOR,AMBIGUOUS_PATH_ENCODING"));
7273
ServerConnector connector = new ServerConnector(server, new HttpConnectionFactory(config));
74+
connector.setHost(bindAddr.getHostString());
75+
connector.setPort(bindAddr.getPort());
7376
server.setConnectors(new Connector[]{connector});
7477
return connector;
7578
}
@@ -87,15 +90,15 @@ private static ServletContextHandler createDefaultServletContext(DefaultServlet
8790
return servletContext;
8891
}
8992

90-
public static WebDavServer createWebDavServer() {
93+
public static WebDavServer createWebDavServer(InetSocketAddress bindAddr) {
9194
var contextPaths = new HashSet<String>();
9295
var executorService = createThreadPoolExecutor();
9396
var threadPool = createThreadPool(executorService);
9497
var defaultServlet = new DefaultServlet(contextPaths);
9598
var defaultServletCtx = createDefaultServletContext(defaultServlet);
9699
var servletCollectionCtx = createContextHandlerCollection(defaultServletCtx);
97100
var server = createServer(threadPool, servletCollectionCtx);
98-
var serverConnector = createServerConnector(server);
101+
var serverConnector = createServerConnector(server, bindAddr);
99102
return new WebDavServer(server, executorService, serverConnector, servletCollectionCtx);
100103
}
101104

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package org.cryptomator.frontend.webdav;
2+
3+
import java.io.IOException;
4+
5+
public interface WebDavServerHandle extends AutoCloseable {
6+
7+
WebDavServer server();
8+
9+
@Override
10+
void close() throws IOException;
11+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package org.cryptomator.frontend.webdav;
2+
3+
import java.io.IOException;
4+
import java.net.InetAddress;
5+
import java.net.InetSocketAddress;
6+
import java.util.concurrent.ConcurrentHashMap;
7+
import java.util.concurrent.ConcurrentMap;
8+
import java.util.concurrent.atomic.AtomicInteger;
9+
10+
public class WebDavServerManager {
11+
12+
private static final ConcurrentMap<Integer, ReferenceCountingHandle> RUNNING_SERVERS = new ConcurrentHashMap<>();
13+
14+
private WebDavServerManager() {
15+
}
16+
17+
public static WebDavServerHandle getOrCreateServer(int port) throws ServerLifecycleException {
18+
return RUNNING_SERVERS.compute(port, (p, handle) -> {
19+
if (handle == null) {
20+
var server = tryCreate(p);
21+
return new ReferenceCountingHandle(port, server, new AtomicInteger(1));
22+
} else {
23+
handle.counter.incrementAndGet();
24+
return handle;
25+
}
26+
});
27+
}
28+
29+
private static WebDavServer tryCreate(int port) throws ServerLifecycleException {
30+
var bindAddr = new InetSocketAddress(InetAddress.getLoopbackAddress(), port);
31+
var server = WebDavServerFactory.createWebDavServer(bindAddr);
32+
server.start();
33+
return server;
34+
}
35+
36+
private record ReferenceCountingHandle(int port, WebDavServer server, AtomicInteger counter) implements WebDavServerHandle {
37+
38+
@Override
39+
public void close() throws IOException {
40+
if (counter.decrementAndGet() == 0) {
41+
RUNNING_SERVERS.remove(port, server);
42+
try {
43+
server.terminate();
44+
} catch (ServerLifecycleException e) {
45+
throw new IOException(e);
46+
}
47+
}
48+
}
49+
50+
}
51+
52+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package org.cryptomator.frontend.webdav.mount;
2+
3+
import org.cryptomator.frontend.webdav.WebDavServerHandle;
4+
import org.cryptomator.frontend.webdav.servlet.WebDavServletController;
5+
import org.cryptomator.integrations.mount.Mount;
6+
import org.cryptomator.integrations.mount.UnmountFailedException;
7+
8+
import java.io.IOException;
9+
10+
public abstract class AbstractMount implements Mount {
11+
12+
protected final WebDavServerHandle serverHandle;
13+
protected final WebDavServletController servlet;
14+
15+
public AbstractMount(WebDavServerHandle serverHandle, WebDavServletController servlet) {
16+
this.serverHandle = serverHandle;
17+
this.servlet = servlet;
18+
}
19+
20+
@Override
21+
public void unmount() throws UnmountFailedException {
22+
servlet.stop();
23+
}
24+
25+
@Override
26+
public void close() throws UnmountFailedException, IOException {
27+
Mount.super.close();
28+
serverHandle.close();
29+
}
30+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package org.cryptomator.frontend.webdav.mount;
2+
3+
import org.cryptomator.frontend.webdav.ServerLifecycleException;
4+
import org.cryptomator.frontend.webdav.WebDavServerHandle;
5+
import org.cryptomator.frontend.webdav.WebDavServerManager;
6+
import org.cryptomator.frontend.webdav.servlet.WebDavServletController;
7+
import org.cryptomator.integrations.mount.Mount;
8+
import org.cryptomator.integrations.mount.MountBuilder;
9+
import org.cryptomator.integrations.mount.MountFailedException;
10+
import org.jetbrains.annotations.Range;
11+
import org.slf4j.Logger;
12+
import org.slf4j.LoggerFactory;
13+
14+
import java.io.IOException;
15+
import java.net.MalformedURLException;
16+
import java.net.URI;
17+
import java.net.URL;
18+
import java.nio.file.Path;
19+
20+
public abstract class AbstractMountBuilder implements MountBuilder {
21+
22+
private static final Logger LOG = LoggerFactory.getLogger(AbstractMountBuilder.class);
23+
24+
protected final Path vfsRoot;
25+
protected String contextPath;
26+
protected int port;
27+
28+
public AbstractMountBuilder(Path vfsRoot) {
29+
this.vfsRoot = vfsRoot;
30+
}
31+
32+
@Override
33+
public MountBuilder setLoopbackPort(@Range(from = 0L, to = 32767L) int port) {
34+
this.port = port;
35+
return this;
36+
}
37+
38+
@Override
39+
public MountBuilder setVolumeId(String volumeId) {
40+
try {
41+
new URL("http", "localhost", 80, volumeId);
42+
} catch (MalformedURLException e) {
43+
throw new IllegalArgumentException("Volume id needs to satisfy url path component restrictions", e);
44+
}
45+
this.contextPath = volumeId;
46+
return this;
47+
}
48+
49+
50+
@Override
51+
public final Mount mount() throws MountFailedException {
52+
WebDavServerHandle serverHandle;
53+
try {
54+
serverHandle = WebDavServerManager.getOrCreateServer(port);
55+
} catch (ServerLifecycleException e) {
56+
throw new MountFailedException("Failed to start server", e);
57+
}
58+
59+
boolean success = false;
60+
try {
61+
WebDavServletController servlet;
62+
try {
63+
servlet = serverHandle.server().createWebDavServlet(vfsRoot, contextPath);
64+
servlet.start();
65+
} catch (ServerLifecycleException e) {
66+
throw new MountFailedException("Failed to create WebDAV servlet", e);
67+
}
68+
69+
var uri = servlet.getServletRootUri();
70+
LOG.info("Mounting {}...", uri);
71+
72+
var mount = this.mount(serverHandle, servlet, uri);
73+
success = true;
74+
return mount;
75+
} finally {
76+
if (!success) {
77+
try {
78+
serverHandle.close();
79+
} catch (IOException e) {
80+
LOG.warn("Terminating server caused I/O error", e);
81+
}
82+
}
83+
}
84+
}
85+
86+
protected abstract Mount mount(WebDavServerHandle serverHandle, WebDavServletController servlet, URI uri) throws MountFailedException;
87+
}

0 commit comments

Comments
 (0)