diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/AsyncDownloader.java b/LauncherCore/src/main/java/pro/gravit/launcher/AsyncDownloader.java new file mode 100644 index 00000000..a9707b2c --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/AsyncDownloader.java @@ -0,0 +1,132 @@ +package pro.gravit.launcher; + +import pro.gravit.utils.helper.IOHelper; + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLConnection; +import java.nio.file.Path; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.Executor; + +public class AsyncDownloader { + public AsyncDownloader(Callback callback) { + this.callback = callback; + } + + public AsyncDownloader() { + callback = (ignored) -> {}; + } + + @FunctionalInterface + public interface Callback + { + void update(long diff); + } + public final Callback callback; + public static class SizedFile + { + public final String path; + public final long size; + + public SizedFile(String path, long size) { + this.path = path; + this.size = size; + } + } + public void downloadFile(URL url, Path target, long size) throws IOException + { + URLConnection connection = url.openConnection(); + try(InputStream input = connection.getInputStream()) + { + transfer(input, target, size); + } + } + public void downloadFile(URL url, Path target) throws IOException + { + URLConnection connection = url.openConnection(); + try(InputStream input = connection.getInputStream()) + { + IOHelper.transfer(input, target); + } + } + public void downloadListInOneThread(List files, String baseURL, Path targetDir) throws URISyntaxException, IOException { + URI baseUri = new URI(baseURL); + String scheme = baseUri.getScheme(); + String host = baseUri.getHost(); + int port = baseUri.getPort(); + if (port != -1) + host = host + ":" + port; + String path = baseUri.getPath(); + for(AsyncDownloader.SizedFile currentFile : files) + { + URL url = new URI(scheme,host,path + currentFile.path, "", "").toURL(); + downloadFile(url, targetDir.resolve(currentFile.path), currentFile.size); + } + } + public List> sortFiles(List files, int threads) + { + files.sort(Comparator.comparingLong((f) -> -f.size)); + List> result = new ArrayList<>(); + for(int i=0;i()); + long[] sizes = new long[threads]; + Arrays.fill(sizes, 0); + for(SizedFile file : files) + { + long min = Long.MAX_VALUE; + int minIndex = 0; + for(int i=0;i> files, String baseURL, Path targetDir, Executor executor) { + int threads = files.size(); + CompletableFuture[] futures = new CompletableFuture[threads]; + for(int i=0;i currentTasks = files.get(i); + futures[i] = CompletableFuture.runAsync(() -> { + try { + downloadListInOneThread(currentTasks, baseURL, targetDir); + } catch (URISyntaxException | IOException e) { + throw new CompletionException(e); + } + }, executor); + } + return futures; + } + + public void transfer(InputStream input, Path file, long size) throws IOException { + try (OutputStream fileOutput = IOHelper.newOutput(file)) { + long downloaded = 0L; + + // Download with digest update + byte[] bytes = IOHelper.newBuffer(); + while (downloaded < size) { + int remaining = (int) Math.min(size - downloaded, bytes.length); + int length = input.read(bytes, 0, remaining); + if (length < 0) + throw new EOFException(String.format("%d bytes remaining", size - downloaded)); + + // Update file + fileOutput.write(bytes, 0, length); + + // Update state + downloaded += length; + //totalDownloaded += length; + callback.update(length); + } + } + } +}