diff --git a/build.gradle b/build.gradle index 3480c7fa..8dada2b7 100644 --- a/build.gradle +++ b/build.gradle @@ -88,7 +88,6 @@ dependencies { implementation ('org.ow2.asm:asm-commons:9.3') implementation ('org.ow2.asm:asm-tree:9.3') implementation ('org.ow2.asm:asm-util:9.3') - implementation ('com.github.mizosoft.methanol:methanol:1.7.0') implementation ('me.tongfei:progressbar:0.9.0') // game handling utils diff --git a/src/main/java/net/fabricmc/loom/task/AbstractRunTask.java b/src/main/java/net/fabricmc/loom/task/AbstractRunTask.java index 0f669a0a..88767e70 100644 --- a/src/main/java/net/fabricmc/loom/task/AbstractRunTask.java +++ b/src/main/java/net/fabricmc/loom/task/AbstractRunTask.java @@ -101,6 +101,7 @@ public abstract class AbstractRunTask extends JavaExec { if (canUseArgFile()) { final String content = "-classpath\n" + this.classpath.getFiles().stream() .map(File::getAbsolutePath) + .map(AbstractRunTask::quoteArg) .collect(Collectors.joining(System.getProperty("path.separator"))); try { @@ -120,6 +121,50 @@ public abstract class AbstractRunTask extends JavaExec { return args; } + // Based off https://github.com/JetBrains/intellij-community/blob/295dd68385a458bdfde638152e36d19bed18b666/platform/util/src/com/intellij/execution/CommandLineWrapperUtil.java#L87 + private static String quoteArg(String arg) { + final String specials = " #'\"\n\r\t\f"; + + if (!containsAnyChar(arg, specials)) { + return arg; + } + + final StringBuilder sb = new StringBuilder(arg.length() * 2); + + for (int i = 0; i < arg.length(); i++) { + char c = arg.charAt(i); + + switch (c) { + case ' ', '#', '\'' -> sb.append('"').append(c).append('"'); + case '"' -> sb.append("\"\\\"\""); + case '\n' -> sb.append("\"\\n\""); + case '\r' -> sb.append("\"\\r\""); + case '\t' -> sb.append("\"\\t\""); + case '\f' -> sb.append("\"\\f\""); + default -> sb.append(c); + } + } + + return sb.toString(); + } + + // https://github.com/JetBrains/intellij-community/blob/295dd68385a458bdfde638152e36d19bed18b666/platform/util/base/src/com/intellij/openapi/util/text/Strings.java#L100-L118 + public static boolean containsAnyChar(final @NotNull String value, final @NotNull String chars) { + return chars.length() > value.length() + ? containsAnyChar(value, chars, 0, value.length()) + : containsAnyChar(chars, value, 0, chars.length()); + } + + public static boolean containsAnyChar(final @NotNull String value, final @NotNull String chars, final int start, final int end) { + for (int i = start; i < end; i++) { + if (chars.indexOf(value.charAt(i)) >= 0) { + return true; + } + } + + return false; + } + @Override public @NotNull JavaExec setClasspath(@NotNull FileCollection classpath) { this.classpath.setFrom(classpath); diff --git a/src/main/java/net/fabricmc/loom/util/download/Download.java b/src/main/java/net/fabricmc/loom/util/download/Download.java index b0a2dd6e..aa5da5b4 100644 --- a/src/main/java/net/fabricmc/loom/util/download/Download.java +++ b/src/main/java/net/fabricmc/loom/util/download/Download.java @@ -26,6 +26,7 @@ package net.fabricmc.loom.util.download; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.io.UncheckedIOException; import java.net.HttpURLConnection; import java.net.ProxySelector; @@ -45,10 +46,10 @@ import java.time.Duration; import java.time.Instant; import java.util.Locale; import java.util.Optional; -import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.IntConsumer; import java.util.zip.GZIPInputStream; -import com.github.mizosoft.methanol.ProgressTracker; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -106,21 +107,10 @@ public class Download { } private HttpResponse send(HttpRequest httpRequest, HttpResponse.BodyHandler bodyHandler) throws DownloadException { - final ProgressTracker tracker = ProgressTracker.create(); - final AtomicBoolean started = new AtomicBoolean(false); + progressListener.onStart(); try { - return getHttpClient().send(httpRequest, tracker.tracking(bodyHandler, progress -> { - if (started.compareAndSet(false, true)) { - progressListener.onStart(); - } - - progressListener.onProgress(progress.totalBytesTransferred(), progress.contentLength()); - - if (progress.done()) { - progressListener.onEnd(true); - } - })); + return getHttpClient().send(httpRequest, bodyHandler); } catch (IOException | InterruptedException e) { throw error(e, "Failed to download (%s)", url); } @@ -139,6 +129,8 @@ public class Download { return new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); } catch (IOException e) { throw error(e, "Failed to decode download output"); + } finally { + progressListener.onEnd(); } } @@ -155,6 +147,8 @@ public class Download { } catch (Throwable throwable) { tryCleanup(output); throw error(throwable, "Failed to download (%s) to (%s)", url, output); + } finally { + progressListener.onEnd(); } } @@ -190,8 +184,17 @@ public class Download { } if (success) { - try (InputStream inputStream = decodeOutput(response)) { - Files.write(output, inputStream.readAllBytes()); + final long length = Long.parseLong(response.headers().firstValue("Content-Length").orElse("-1")); + AtomicLong totalBytes = new AtomicLong(0); + + try (OutputStream outputStream = Files.newOutputStream(output)) { + copyWithCallback(decodeOutput(response), outputStream, value -> { + if (length < 0) { + return; + } + + progressListener.onProgress(totalBytes.addAndGet(value), length); + }); } catch (IOException e) { tryCleanup(output); throw error(e, "Failed to decode and write download output"); @@ -230,6 +233,16 @@ public class Download { } } + private void copyWithCallback(InputStream is, OutputStream os, IntConsumer consumer) throws IOException { + byte[] buffer = new byte[1024]; + int length; + + while ((length = is.read(buffer)) > 0) { + os.write(buffer, 0, length); + consumer.accept(length); + } + } + private InputStream decodeOutput(HttpResponse response) throws IOException { final String encoding = response.headers().firstValue("Content-Encoding").orElse(""); diff --git a/src/main/java/net/fabricmc/loom/util/download/DownloadBuilder.java b/src/main/java/net/fabricmc/loom/util/download/DownloadBuilder.java index 95d46623..15242936 100644 --- a/src/main/java/net/fabricmc/loom/util/download/DownloadBuilder.java +++ b/src/main/java/net/fabricmc/loom/util/download/DownloadBuilder.java @@ -114,22 +114,21 @@ public class DownloadBuilder { } public String downloadString(Path cache) throws DownloadException { - withRetries(() -> { + return withRetries(() -> { build().downloadPath(cache); - return null; - }); - try { - return Files.readString(cache, StandardCharsets.UTF_8); - } catch (IOException e) { try { - Files.delete(cache); - } catch (IOException ex) { - // Ignored - } + return Files.readString(cache, StandardCharsets.UTF_8); + } catch (IOException e) { + try { + Files.deleteIfExists(cache); + } catch (IOException ex) { + // Ignored + } - throw new DownloadException("Failed to download and read string", e); - } + throw new DownloadException("Failed to download and read string", e); + } + }); } private T withRetries(DownloadSupplier supplier) throws DownloadException { diff --git a/src/main/java/net/fabricmc/loom/util/download/DownloadProgressListener.java b/src/main/java/net/fabricmc/loom/util/download/DownloadProgressListener.java index 982db3aa..93589f19 100644 --- a/src/main/java/net/fabricmc/loom/util/download/DownloadProgressListener.java +++ b/src/main/java/net/fabricmc/loom/util/download/DownloadProgressListener.java @@ -29,7 +29,7 @@ public interface DownloadProgressListener { void onProgress(long bytesTransferred, long contentLength); - void onEnd(boolean success); + void onEnd(); DownloadProgressListener NONE = new DownloadProgressListener() { @Override @@ -41,7 +41,7 @@ public interface DownloadProgressListener { } @Override - public void onEnd(boolean success) { + public void onEnd() { } }; } diff --git a/src/main/java/net/fabricmc/loom/util/download/GradleDownloadProgressListener.java b/src/main/java/net/fabricmc/loom/util/download/GradleDownloadProgressListener.java index 0bd3e54f..a4b70e04 100644 --- a/src/main/java/net/fabricmc/loom/util/download/GradleDownloadProgressListener.java +++ b/src/main/java/net/fabricmc/loom/util/download/GradleDownloadProgressListener.java @@ -54,7 +54,7 @@ public class GradleDownloadProgressListener implements DownloadProgressListener } @Override - public void onEnd(boolean success) { + public void onEnd() { Objects.requireNonNull(progressLogger); progressLogger.completed(); progressLogger = null; diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/download/DownloadFileTest.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/download/DownloadFileTest.groovy index 8815a146..d6675629 100644 --- a/src/test/groovy/net/fabricmc/loom/test/unit/download/DownloadFileTest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/unit/download/DownloadFileTest.groovy @@ -218,7 +218,7 @@ class DownloadFileTest extends DownloadTest { } @Override - void onEnd(boolean success) { + void onEnd() { ended = true } }) @@ -250,7 +250,7 @@ class DownloadFileTest extends DownloadTest { } @Override - void onEnd(boolean success) { + void onEnd() { ended = true } }) diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/download/DownloadStringTest.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/download/DownloadStringTest.groovy index 66f58dd6..d9ccdd3b 100644 --- a/src/test/groovy/net/fabricmc/loom/test/unit/download/DownloadStringTest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/unit/download/DownloadStringTest.groovy @@ -95,4 +95,18 @@ class DownloadStringTest extends DownloadTest { then: result == "Hello World 3" } + + def "String: File cache"() { + setup: + server.get("/downloadString2") { + it.result("Hello World!") + } + + when: + def output = new File(File.createTempDir(), "file.txt").toPath() + def result = Download.create("$PATH/downloadString2").downloadString(output) + + then: + result == "Hello World!" + } }