diff --git a/java17/src/main/java/io/papermc/paperclip/DownloadContext.java b/java17/src/main/java/io/papermc/paperclip/DownloadContext.java index b3405cd..26ce99c 100644 --- a/java17/src/main/java/io/papermc/paperclip/DownloadContext.java +++ b/java17/src/main/java/io/papermc/paperclip/DownloadContext.java @@ -4,6 +4,7 @@ import java.net.MalformedURLException; import java.net.URI; import java.net.URL; +import java.net.URLConnection; import java.nio.channels.Channels; import java.nio.channels.FileChannel; import java.nio.channels.ReadableByteChannel; @@ -16,6 +17,10 @@ record DownloadContext(byte[] hash, URL url, String fileName) { + private static final int CONNECT_TIMEOUT_MILLIS = 30_000; + private static final int READ_TIMEOUT_MILLIS = 30_000; + private static final int MAX_ATTEMPTS = 3; + public Path getOutputFile(final Path outputDir) { final Path cacheDir = outputDir.resolve("cache"); return cacheDir.resolve(this.fileName); @@ -51,19 +56,40 @@ public void download(final Path outputDir) throws IOException { System.out.println("Downloading " + this.fileName); + for (int attempt = 1; ; attempt++) { + try { + this.downloadTo(outputFile); + } catch (final IOException e) { + if (attempt >= MAX_ATTEMPTS) { + System.err.println("Failed to download " + this.fileName); + e.printStackTrace(); + System.exit(1); + } + System.err.println("Failed to download " + this.fileName + " (attempt " + attempt + " of " + MAX_ATTEMPTS + "): " + e); + continue; + } + + // A connection dropped mid-body reads as end of stream, so a truncated file only shows up here + if (Util.isFileValid(outputFile, this.hash)) { + return; + } + if (attempt >= MAX_ATTEMPTS) { + throw new IllegalStateException("Hash check failed for downloaded file " + this.fileName); + } + System.err.println("Hash check failed for downloaded file " + this.fileName + " (attempt " + attempt + " of " + MAX_ATTEMPTS + ")"); + } + } + + private void downloadTo(final Path outputFile) throws IOException { + final URLConnection connection = this.url.openConnection(); + connection.setConnectTimeout(CONNECT_TIMEOUT_MILLIS); + connection.setReadTimeout(READ_TIMEOUT_MILLIS); + try ( - final ReadableByteChannel source = Channels.newChannel(this.url.openStream()); + final ReadableByteChannel source = Channels.newChannel(connection.getInputStream()); final FileChannel fileChannel = FileChannel.open(outputFile, CREATE, WRITE, TRUNCATE_EXISTING) ) { fileChannel.transferFrom(source, 0, Long.MAX_VALUE); - } catch (final IOException e) { - System.err.println("Failed to download " + this.fileName); - e.printStackTrace(); - System.exit(1); - } - - if (!Util.isFileValid(outputFile, this.hash)) { - throw new IllegalStateException("Hash check failed for downloaded file " + this.fileName); } } }