diff --git a/api/build.gradle b/api/build.gradle new file mode 100644 index 0000000..2cfab9a --- /dev/null +++ b/api/build.gradle @@ -0,0 +1,15 @@ +plugins { + id "java" +} + +dependencies { + +} + +java { + // leave it on java 17 to be compatible with older versions and we dont really need 21 there anyway + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + toolchain.languageVersion.set(JavaLanguageVersion.of(17)) + withSourcesJar() +} diff --git a/api/src/main/java/de/rafael/modflared/api/ModflaredApi.java b/api/src/main/java/de/rafael/modflared/api/ModflaredApi.java new file mode 100644 index 0000000..70019d7 --- /dev/null +++ b/api/src/main/java/de/rafael/modflared/api/ModflaredApi.java @@ -0,0 +1,7 @@ +package de.rafael.modflared.api; + +import de.rafael.modflared.api.tunnel.IModflaredApiTunnel; + +public class ModflaredApi { + public static IModflaredApiTunnel IAPITUNNEL = null; +} diff --git a/api/src/main/java/de/rafael/modflared/api/tunnel/IModflaredApiTunnel.java b/api/src/main/java/de/rafael/modflared/api/tunnel/IModflaredApiTunnel.java new file mode 100644 index 0000000..b150515 --- /dev/null +++ b/api/src/main/java/de/rafael/modflared/api/tunnel/IModflaredApiTunnel.java @@ -0,0 +1,21 @@ +package de.rafael.modflared.api.tunnel; + +@SuppressWarnings("unused") +public interface IModflaredApiTunnel { + /** + * Returns true if this is a modflared tunnel. + * */ + default boolean isModflaredTunnel(String host, int port) { + return false; + } + + /** + * Add a tunnel dependency. + * */ + void addTunnelDependency(String host, int port, String id); + + /** + * Remove a tunnel dependency. + * */ + void removeTunnelDependency(String host, int port, String id); +} diff --git a/build.gradle b/build.gradle index 2942a6d..200de0f 100644 --- a/build.gradle +++ b/build.gradle @@ -46,11 +46,6 @@ allprojects { // for more information about repositories. } - tasks.withType(JavaCompile) { - options.encoding = "UTF-8" - options.release = 21 - } - java { withSourcesJar() } diff --git a/common/build.gradle b/common/build.gradle index 7a6b8f0..2cb4f56 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -10,6 +10,7 @@ dependencies { // We depend on fabric loader here to use the fabric @Environment annotations and get the mixin dependencies // Do NOT use other classes from fabric loader modImplementation "net.fabricmc:fabric-loader:${rootProject.fabric_loader_version}" + compileOnly(project(":api")) } publishing { diff --git a/common/src/main/java/de/rafael/modflared/Modflared.java b/common/src/main/java/de/rafael/modflared/Modflared.java index 3de06e6..0808e54 100644 --- a/common/src/main/java/de/rafael/modflared/Modflared.java +++ b/common/src/main/java/de/rafael/modflared/Modflared.java @@ -2,6 +2,7 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import de.rafael.modflared.api.ModflaredApi; import de.rafael.modflared.platform.LoaderPlatform; import de.rafael.modflared.tunnel.manager.TunnelManager; import org.slf4j.Logger; @@ -24,9 +25,10 @@ public class Modflared { public static void init() { TUNNEL_MANAGER.prepareBinary(); TUNNEL_MANAGER.loadForcedTunnels(); + ModflaredApi.IAPITUNNEL = TUNNEL_MANAGER; Runtime.getRuntime().addShutdownHook(new Thread(() -> { - TUNNEL_MANAGER.closeTunnels(); + TUNNEL_MANAGER.forceCloseTunnels(); EXECUTOR.shutdownNow(); })); } diff --git a/common/src/main/java/de/rafael/modflared/mixin/ClientConnectionMixin.java b/common/src/main/java/de/rafael/modflared/mixin/ClientConnectionMixin.java index 8ecae03..c2f122c 100644 --- a/common/src/main/java/de/rafael/modflared/mixin/ClientConnectionMixin.java +++ b/common/src/main/java/de/rafael/modflared/mixin/ClientConnectionMixin.java @@ -27,7 +27,7 @@ private static ChannelFuture connect(@NotNull InetSocketAddress address, boolean public void disconnect(Text disconnectReason, CallbackInfo callbackInfo) { synchronized(this) { if(this.modflared$runningTunnel != null) { - Modflared.TUNNEL_MANAGER.closeTunnel(this.modflared$runningTunnel); + Modflared.TUNNEL_MANAGER.closeTunnel(this.modflared$runningTunnel, "modflared", false); this.modflared$runningTunnel = null; } } diff --git a/common/src/main/java/de/rafael/modflared/tunnel/RunningTunnel.java b/common/src/main/java/de/rafael/modflared/tunnel/RunningTunnel.java index 4d10d5b..e40a01f 100644 --- a/common/src/main/java/de/rafael/modflared/tunnel/RunningTunnel.java +++ b/common/src/main/java/de/rafael/modflared/tunnel/RunningTunnel.java @@ -17,6 +17,8 @@ import java.io.*; import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.zip.CRC32; diff --git a/common/src/main/java/de/rafael/modflared/tunnel/manager/TunnelManager.java b/common/src/main/java/de/rafael/modflared/tunnel/manager/TunnelManager.java index efffb20..783a2f1 100644 --- a/common/src/main/java/de/rafael/modflared/tunnel/manager/TunnelManager.java +++ b/common/src/main/java/de/rafael/modflared/tunnel/manager/TunnelManager.java @@ -5,6 +5,7 @@ import com.google.gson.JsonParser; import de.rafael.modflared.Modflared; import de.rafael.modflared.ModflaredPlatform; +import de.rafael.modflared.api.tunnel.IModflaredApiTunnel; import de.rafael.modflared.binary.Cloudflared; import de.rafael.modflared.interfaces.mixin.IClientConnection; import de.rafael.modflared.tunnel.RunningTunnel; @@ -27,12 +28,10 @@ import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.UnknownHostException; -import java.util.ArrayList; -import java.util.List; -import java.util.Properties; +import java.util.*; import java.util.concurrent.atomic.AtomicReference; -public class TunnelManager { +public class TunnelManager implements IModflaredApiTunnel { public static final File BASE_FOLDER = ModflaredPlatform.getGameDir().resolve("modflared/").toFile(); public static final File DATA_FOLDER = new File(BASE_FOLDER, "bin/"); @@ -43,7 +42,7 @@ public class TunnelManager { private final AtomicReference cloudflared = new AtomicReference<>(); private final List forcedTunnels = new ArrayList<>(); - private final List runningTunnels = new ArrayList<>(); + private final Map> runningTunnels = new HashMap<>(); public RunningTunnel createTunnel(String host) { var binary = this.cloudflared.get(); @@ -51,21 +50,32 @@ public RunningTunnel createTunnel(String host) { Modflared.LOGGER.info("Starting tunnel to {}", host); var process = binary.createTunnel(RunningTunnel.Access.localWithRandomPort(host)); if (process == null) return null; - this.runningTunnels.add(process); + this.runningTunnels.put(process, new ArrayList<>(Collections.singleton("modflared"))); return process; } else { return null; } } - public void closeTunnel(@NotNull RunningTunnel runningTunnel) { - Modflared.LOGGER.info("Stopping tunnel to {}", runningTunnel.access().tunnelAddress()); + public void closeTunnel(@NotNull RunningTunnel runningTunnel, String dependencyId, boolean force) { + if (force) { + Modflared.LOGGER.info("Stopping tunnel to {}", runningTunnel.access().tunnelAddress()); + } else { + List dependencies = runningTunnels.get(runningTunnel); + dependencies.remove(dependencyId); + if (dependencies.isEmpty()) { + Modflared.LOGGER.info("Closing tunnel to {} as no more dependencies are using it", runningTunnel.access().tunnelAddress()); + } else { + runningTunnels.put(runningTunnel, dependencies); + return; + } + } this.runningTunnels.remove(runningTunnel); runningTunnel.closeTunnel(); } - public void closeTunnels() { - for (RunningTunnel runningTunnel : this.runningTunnels) { + public void forceCloseTunnels() { + for (RunningTunnel runningTunnel : this.runningTunnels.keySet()) { runningTunnel.closeTunnel(); } } @@ -230,4 +240,37 @@ public static void displayErrorToast() { MinecraftClient.getInstance().getToastManager().add(new SystemToast(SystemToast.Type.PERIODIC_NOTIFICATION, Text.translatable("gui.toast.title.error"), Text.translatable("gui.toast.body.error"))); } + @Override + public boolean isModflaredTunnel(String host, int port) { + return getRunningTunnel(host, port) != null; + } + + @Override + public void addTunnelDependency(String host, int port, String id) { + RunningTunnel tunnel = getRunningTunnel(host, port); + if (tunnel != null) { + this.runningTunnels.computeIfAbsent(tunnel, k -> new ArrayList<>()).add(id); + } else { + Modflared.LOGGER.warn("No running tunnel found for {}:{}", host, port); + } + } + + @Override + public void removeTunnelDependency(String host, int port, String id) { + RunningTunnel tunnel = getRunningTunnel(host, port); + if (tunnel != null) { + closeTunnel(tunnel, id, false); + } else { + Modflared.LOGGER.warn("No running tunnel found for {}:{}", host, port); + } + } + + @Nullable + private RunningTunnel getRunningTunnel(String host, int port) { + return this.runningTunnels.keySet().stream() + .filter(tunnel -> tunnel.access().tunnelAddress().getHostString().equalsIgnoreCase(host) && + tunnel.access().tunnelAddress().getPort() == port) + .findFirst() + .orElse(null); + } } diff --git a/settings.gradle b/settings.gradle index cd8d03b..e831647 100644 --- a/settings.gradle +++ b/settings.gradle @@ -7,6 +7,7 @@ pluginManagement { } } +include("api") include("common") include("fabric") include("neoforge")