mirror of
https://github.com/GravitLauncher/Launcher
synced 2024-12-22 16:41:46 +03:00
[FEATURE] Remove deprecated http methods, fix download
This commit is contained in:
parent
474d557e3f
commit
fe7ae41f65
16 changed files with 229 additions and 506 deletions
|
@ -75,6 +75,7 @@
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
pack project(':LauncherAPI')
|
pack project(':LauncherAPI')
|
||||||
|
pack project(':LauncherModernCore')
|
||||||
bundle group: 'me.tongfei', name: 'progressbar', version: '0.9.2'
|
bundle group: 'me.tongfei', name: 'progressbar', version: '0.9.2'
|
||||||
bundle group: 'com.github.Marcono1234', name: 'gson-record-type-adapter-factory', version: 'v0.2.0'
|
bundle group: 'com.github.Marcono1234', name: 'gson-record-type-adapter-factory', version: 'v0.2.0'
|
||||||
bundle group: 'org.fusesource.jansi', name: 'jansi', version: rootProject['verJansi']
|
bundle group: 'org.fusesource.jansi', name: 'jansi', version: rootProject['verJansi']
|
||||||
|
|
|
@ -3,15 +3,12 @@
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import pro.gravit.launcher.HTTPRequest;
|
|
||||||
import pro.gravit.launcher.Launcher;
|
|
||||||
import pro.gravit.launcher.profiles.Texture;
|
import pro.gravit.launcher.profiles.Texture;
|
||||||
import pro.gravit.launchserver.HttpRequester;
|
import pro.gravit.launchserver.HttpRequester;
|
||||||
import pro.gravit.utils.helper.SecurityHelper;
|
import pro.gravit.utils.helper.SecurityHelper;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
import java.net.URL;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
|
@ -3,11 +3,10 @@
|
||||||
import me.tongfei.progressbar.ProgressBar;
|
import me.tongfei.progressbar.ProgressBar;
|
||||||
import me.tongfei.progressbar.ProgressBarBuilder;
|
import me.tongfei.progressbar.ProgressBarBuilder;
|
||||||
import me.tongfei.progressbar.ProgressBarStyle;
|
import me.tongfei.progressbar.ProgressBarStyle;
|
||||||
import pro.gravit.launcher.AsyncDownloader;
|
|
||||||
import pro.gravit.launcher.Launcher;
|
import pro.gravit.launcher.Launcher;
|
||||||
|
import pro.gravit.launcher.modern.Downloader;
|
||||||
import pro.gravit.launcher.profiles.ClientProfile;
|
import pro.gravit.launcher.profiles.ClientProfile;
|
||||||
import pro.gravit.launchserver.LaunchServer;
|
import pro.gravit.launchserver.LaunchServer;
|
||||||
import pro.gravit.utils.Downloader;
|
|
||||||
import pro.gravit.utils.command.CommandException;
|
import pro.gravit.utils.command.CommandException;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -45,9 +44,9 @@ protected boolean showApplyDialog(String text) throws IOException {
|
||||||
return response.equals("y");
|
return response.equals("y");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Downloader downloadWithProgressBar(String taskName, List<AsyncDownloader.SizedFile> list, String baseUrl, Path targetDir) throws Exception {
|
protected Downloader downloadWithProgressBar(String taskName, List<Downloader.SizedFile> list, String baseUrl, Path targetDir) throws Exception {
|
||||||
long total = 0;
|
long total = 0;
|
||||||
for (AsyncDownloader.SizedFile file : list) {
|
for (Downloader.SizedFile file : list) {
|
||||||
total += file.size;
|
total += file.size;
|
||||||
}
|
}
|
||||||
long totalFiles = list.size();
|
long totalFiles = list.size();
|
||||||
|
|
|
@ -3,12 +3,11 @@
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import pro.gravit.launcher.AsyncDownloader;
|
|
||||||
import pro.gravit.launcher.Launcher;
|
import pro.gravit.launcher.Launcher;
|
||||||
|
import pro.gravit.launcher.modern.Downloader;
|
||||||
import pro.gravit.launchserver.HttpRequester;
|
import pro.gravit.launchserver.HttpRequester;
|
||||||
import pro.gravit.launchserver.LaunchServer;
|
import pro.gravit.launchserver.LaunchServer;
|
||||||
import pro.gravit.launchserver.command.Command;
|
import pro.gravit.launchserver.command.Command;
|
||||||
import pro.gravit.utils.Downloader;
|
|
||||||
import pro.gravit.utils.helper.IOHelper;
|
import pro.gravit.utils.helper.IOHelper;
|
||||||
|
|
||||||
import java.io.Writer;
|
import java.io.Writer;
|
||||||
|
@ -85,7 +84,7 @@ public void invoke(String... args) throws Exception {
|
||||||
logger.info("Copy {} into {}", indexPath, targetPath);
|
logger.info("Copy {} into {}", indexPath, targetPath);
|
||||||
Files.copy(indexPath, targetPath, StandardCopyOption.REPLACE_EXISTING);
|
Files.copy(indexPath, targetPath, StandardCopyOption.REPLACE_EXISTING);
|
||||||
}
|
}
|
||||||
List<AsyncDownloader.SizedFile> toDownload = new ArrayList<>(128);
|
List<Downloader.SizedFile> toDownload = new ArrayList<>(128);
|
||||||
for (var e : objects.entrySet()) {
|
for (var e : objects.entrySet()) {
|
||||||
var value = e.getValue().getAsJsonObject();
|
var value = e.getValue().getAsJsonObject();
|
||||||
var hash = value.get("hash").getAsString();
|
var hash = value.get("hash").getAsString();
|
||||||
|
@ -101,7 +100,7 @@ public void invoke(String... args) throws Exception {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
toDownload.add(new AsyncDownloader.SizedFile(hash, path, size));
|
toDownload.add(new Downloader.SizedFile(hash, path, size));
|
||||||
}
|
}
|
||||||
logger.info("Download {} files", toDownload.size());
|
logger.info("Download {} files", toDownload.size());
|
||||||
Downloader downloader = downloadWithProgressBar(dirName, toDownload, RESOURCES_DOWNLOAD_URL, assetDir);
|
Downloader downloader = downloadWithProgressBar(dirName, toDownload, RESOURCES_DOWNLOAD_URL, assetDir);
|
||||||
|
|
|
@ -3,20 +3,27 @@
|
||||||
import com.google.gson.JsonElement;
|
import com.google.gson.JsonElement;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import pro.gravit.launcher.HTTPRequest;
|
import pro.gravit.launcher.Launcher;
|
||||||
import pro.gravit.utils.HttpDownloader;
|
|
||||||
import pro.gravit.utils.helper.IOHelper;
|
import pro.gravit.utils.helper.IOHelper;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.net.http.HttpClient;
|
||||||
|
import java.net.http.HttpRequest;
|
||||||
|
import java.net.http.HttpResponse;
|
||||||
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipInputStream;
|
||||||
|
|
||||||
public class MirrorManager {
|
public class MirrorManager {
|
||||||
protected final ArrayList<Mirror> list = new ArrayList<>();
|
protected final ArrayList<Mirror> list = new ArrayList<>();
|
||||||
private transient final Logger logger = LogManager.getLogger();
|
private transient final Logger logger = LogManager.getLogger();
|
||||||
|
private transient final HttpClient client = HttpClient.newBuilder().build();
|
||||||
private Mirror defaultMirror;
|
private Mirror defaultMirror;
|
||||||
|
|
||||||
public void addMirror(String mirror) {
|
public void addMirror(String mirror) {
|
||||||
|
@ -58,7 +65,7 @@ public boolean downloadZip(Mirror mirror, Path path, String mask, Object... args
|
||||||
URL url = mirror.getURL(mask, args);
|
URL url = mirror.getURL(mask, args);
|
||||||
logger.debug("Try download {}", url.toString());
|
logger.debug("Try download {}", url.toString());
|
||||||
try {
|
try {
|
||||||
HttpDownloader.downloadZip(url, path);
|
downloadZip(url, path);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.error("Download {} failed({}: {})", url.toString(), e.getClass().getName(), e.getMessage());
|
logger.error("Download {} failed({}: {})", url.toString(), e.getClass().getName(), e.getMessage());
|
||||||
return false;
|
return false;
|
||||||
|
@ -82,8 +89,12 @@ public JsonElement jsonRequest(Mirror mirror, JsonElement request, String method
|
||||||
if (!mirror.enabled) return null;
|
if (!mirror.enabled) return null;
|
||||||
URL url = mirror.getURL(mask, args);
|
URL url = mirror.getURL(mask, args);
|
||||||
try {
|
try {
|
||||||
return HTTPRequest.jsonRequest(request, method, url);
|
var response = client.send(HttpRequest.newBuilder()
|
||||||
} catch (IOException e) {
|
.method(method, request == null ? HttpRequest.BodyPublishers.noBody() : HttpRequest.BodyPublishers.ofString(Launcher.gsonManager.gson.toJson(request)))
|
||||||
|
.uri(url.toURI())
|
||||||
|
.build(), HttpResponse.BodyHandlers.ofString());
|
||||||
|
return Launcher.gsonManager.gson.fromJson(response.body(), JsonElement.class);
|
||||||
|
} catch (IOException | URISyntaxException | InterruptedException e) {
|
||||||
logger.error("JsonRequest {} failed({}: {})", url.toString(), e.getClass().getName(), e.getMessage());
|
logger.error("JsonRequest {} failed({}: {})", url.toString(), e.getClass().getName(), e.getMessage());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -101,6 +112,22 @@ public JsonElement jsonRequest(JsonElement request, String method, String mask,
|
||||||
throw new IOException("Error jsonRequest. All mirrors return error");
|
throw new IOException("Error jsonRequest. All mirrors return error");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void downloadZip(URL url, Path dir) throws IOException {
|
||||||
|
try (ZipInputStream input = IOHelper.newZipInput(url)) {
|
||||||
|
Files.createDirectory(dir);
|
||||||
|
for (ZipEntry entry = input.getNextEntry(); entry != null; entry = input.getNextEntry()) {
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
Files.createDirectory(dir.resolve(IOHelper.toPath(entry.getName())));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Unpack entry
|
||||||
|
String name = entry.getName();
|
||||||
|
logger.debug("Downloading file: '{}'", name);
|
||||||
|
IOHelper.transfer(input, dir.resolve(IOHelper.toPath(name)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static class Mirror {
|
public static class Mirror {
|
||||||
final String baseUrl;
|
final String baseUrl;
|
||||||
boolean enabled;
|
boolean enabled;
|
||||||
|
|
|
@ -13,8 +13,8 @@
|
||||||
version = "12"
|
version = "12"
|
||||||
modules = ['javafx.controls', 'javafx.fxml']
|
modules = ['javafx.controls', 'javafx.fxml']
|
||||||
}
|
}
|
||||||
sourceCompatibility = '1.8'
|
sourceCompatibility = '17'
|
||||||
targetCompatibility = '1.8'
|
targetCompatibility = '17'
|
||||||
|
|
||||||
configurations {
|
configurations {
|
||||||
bundle
|
bundle
|
||||||
|
@ -49,6 +49,7 @@
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
pack project(':LauncherAPI')
|
pack project(':LauncherAPI')
|
||||||
|
pack project(':LauncherModernCore')
|
||||||
pack project(':LauncherClient')
|
pack project(':LauncherClient')
|
||||||
pack project(':LauncherStart')
|
pack project(':LauncherStart')
|
||||||
bundle group: 'com.github.oshi', name: 'oshi-core', version: rootProject['verOshiCore']
|
bundle group: 'com.github.oshi', name: 'oshi-core', version: rootProject['verOshiCore']
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package pro.gravit.launcher.utils;
|
package pro.gravit.launcher.utils;
|
||||||
|
|
||||||
import pro.gravit.launcher.AsyncDownloader;
|
|
||||||
import pro.gravit.launcher.LauncherEngine;
|
import pro.gravit.launcher.LauncherEngine;
|
||||||
import pro.gravit.launcher.LauncherInject;
|
import pro.gravit.launcher.LauncherInject;
|
||||||
import pro.gravit.launcher.request.update.LauncherRequest;
|
import pro.gravit.launcher.request.update.LauncherRequest;
|
||||||
|
@ -23,6 +22,8 @@
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import static pro.gravit.launcher.modern.Downloader.makeSSLSocketFactory;
|
||||||
|
|
||||||
public class LauncherUpdater {
|
public class LauncherUpdater {
|
||||||
@LauncherInject("launcher.certificatePinning")
|
@LauncherInject("launcher.certificatePinning")
|
||||||
private static boolean isCertificatePinning;
|
private static boolean isCertificatePinning;
|
||||||
|
@ -49,7 +50,7 @@ public static Path prepareUpdate(URL url) throws Exception {
|
||||||
if (isCertificatePinning) {
|
if (isCertificatePinning) {
|
||||||
HttpsURLConnection connection1 = (HttpsURLConnection) connection;
|
HttpsURLConnection connection1 = (HttpsURLConnection) connection;
|
||||||
try {
|
try {
|
||||||
connection1.setSSLSocketFactory(AsyncDownloader.makeSSLSocketFactory());
|
connection1.setSSLSocketFactory(makeSSLSocketFactory());
|
||||||
} catch (NoSuchAlgorithmException | CertificateException | KeyStoreException | KeyManagementException e) {
|
} catch (NoSuchAlgorithmException | CertificateException | KeyStoreException | KeyManagementException e) {
|
||||||
throw new IOException(e);
|
throw new IOException(e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,213 +0,0 @@
|
||||||
package pro.gravit.launcher;
|
|
||||||
|
|
||||||
import pro.gravit.utils.helper.IOHelper;
|
|
||||||
|
|
||||||
import javax.net.ssl.HttpsURLConnection;
|
|
||||||
import javax.net.ssl.SSLContext;
|
|
||||||
import javax.net.ssl.SSLSocketFactory;
|
|
||||||
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.security.KeyManagementException;
|
|
||||||
import java.security.KeyStoreException;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.security.cert.CertificateException;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
import java.util.concurrent.CompletionException;
|
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
|
|
||||||
public class AsyncDownloader {
|
|
||||||
public static final Callback IGNORE = (ignored) -> {
|
|
||||||
};
|
|
||||||
@LauncherInject("launcher.certificatePinning")
|
|
||||||
private static boolean isCertificatePinning;
|
|
||||||
private static volatile SSLSocketFactory sslSocketFactory;
|
|
||||||
private static volatile SSLContext sslContext;
|
|
||||||
public final Callback callback;
|
|
||||||
public volatile boolean isClosed;
|
|
||||||
|
|
||||||
public AsyncDownloader(Callback callback) {
|
|
||||||
this.callback = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AsyncDownloader() {
|
|
||||||
callback = IGNORE;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static SSLSocketFactory makeSSLSocketFactory() throws NoSuchAlgorithmException, CertificateException, KeyStoreException, IOException, KeyManagementException {
|
|
||||||
if (sslSocketFactory != null) return sslSocketFactory;
|
|
||||||
SSLContext sslContext = makeSSLContext();
|
|
||||||
sslSocketFactory = sslContext.getSocketFactory();
|
|
||||||
return sslSocketFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static SSLContext makeSSLContext() throws NoSuchAlgorithmException, CertificateException, KeyStoreException, IOException, KeyManagementException {
|
|
||||||
if (sslContext != null) return sslContext;
|
|
||||||
SSLContext sslContext = SSLContext.getInstance("TLS");
|
|
||||||
sslContext.init(null, CertificatePinningTrustManager.getTrustManager().getTrustManagers(), new SecureRandom());
|
|
||||||
return sslContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void downloadFile(URL url, Path target, long size) throws IOException {
|
|
||||||
if (isClosed) throw new IOException("Download interrupted");
|
|
||||||
URLConnection connection = url.openConnection();
|
|
||||||
if (isCertificatePinning) {
|
|
||||||
HttpsURLConnection connection1 = (HttpsURLConnection) connection;
|
|
||||||
try {
|
|
||||||
connection1.setSSLSocketFactory(makeSSLSocketFactory());
|
|
||||||
} catch (NoSuchAlgorithmException | CertificateException | KeyStoreException | KeyManagementException e) {
|
|
||||||
throw new IOException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try (InputStream input = connection.getInputStream()) {
|
|
||||||
transfer(input, target, size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void downloadFile(URL url, Path target) throws IOException {
|
|
||||||
URLConnection connection = url.openConnection();
|
|
||||||
if (isCertificatePinning) {
|
|
||||||
HttpsURLConnection connection1 = (HttpsURLConnection) connection;
|
|
||||||
try {
|
|
||||||
connection1.setSSLSocketFactory(makeSSLSocketFactory());
|
|
||||||
} catch (NoSuchAlgorithmException | CertificateException | KeyStoreException | KeyManagementException e) {
|
|
||||||
throw new IOException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try (InputStream input = connection.getInputStream()) {
|
|
||||||
IOHelper.transfer(input, target);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void downloadListInOneThread(List<SizedFile> 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.urlPath, "", "").toURL();
|
|
||||||
downloadFile(url, targetDir.resolve(currentFile.filePath), currentFile.size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void downloadListInOneThreadSimple(List<SizedFile> files, String baseURL, Path targetDir) throws IOException {
|
|
||||||
|
|
||||||
for (AsyncDownloader.SizedFile currentFile : files) {
|
|
||||||
downloadFile(new URL(baseURL + currentFile.urlPath), targetDir.resolve(currentFile.filePath), currentFile.size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<List<SizedFile>> sortFiles(List<SizedFile> files, int threads) {
|
|
||||||
files.sort(Comparator.comparingLong((f) -> -f.size));
|
|
||||||
List<List<SizedFile>> result = new ArrayList<>();
|
|
||||||
for (int i = 0; i < threads; ++i) result.add(new LinkedList<>());
|
|
||||||
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 < threads; ++i)
|
|
||||||
if (sizes[i] < min) {
|
|
||||||
min = sizes[i];
|
|
||||||
minIndex = i;
|
|
||||||
}
|
|
||||||
result.get(minIndex).add(file);
|
|
||||||
sizes[minIndex] += file.size;
|
|
||||||
}
|
|
||||||
for (List<AsyncDownloader.SizedFile> list : result) {
|
|
||||||
Collections.shuffle(list);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
public CompletableFuture[] runDownloadList(List<List<SizedFile>> files, String baseURL, Path targetDir, Executor executor) {
|
|
||||||
int threads = files.size();
|
|
||||||
CompletableFuture[] futures = new CompletableFuture[threads];
|
|
||||||
for (int i = 0; i < threads; ++i) {
|
|
||||||
List<SizedFile> currentTasks = files.get(i);
|
|
||||||
futures[i] = CompletableFuture.runAsync(() -> {
|
|
||||||
try {
|
|
||||||
downloadListInOneThread(currentTasks, baseURL, targetDir);
|
|
||||||
} catch (URISyntaxException | IOException e) {
|
|
||||||
throw new CompletionException(e);
|
|
||||||
}
|
|
||||||
}, executor);
|
|
||||||
}
|
|
||||||
return futures;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
public CompletableFuture[] runDownloadListSimple(List<List<SizedFile>> files, String baseURL, Path targetDir, Executor executor) {
|
|
||||||
int threads = files.size();
|
|
||||||
CompletableFuture[] futures = new CompletableFuture[threads];
|
|
||||||
for (int i = 0; i < threads; ++i) {
|
|
||||||
List<SizedFile> currentTasks = files.get(i);
|
|
||||||
futures[i] = CompletableFuture.runAsync(() -> {
|
|
||||||
try {
|
|
||||||
downloadListInOneThreadSimple(currentTasks, baseURL, targetDir);
|
|
||||||
} catch (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) {
|
|
||||||
if (isClosed) throw new IOException("Download interrupted");
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@FunctionalInterface
|
|
||||||
public interface Callback {
|
|
||||||
void update(long diff);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class SizedFile {
|
|
||||||
public final String urlPath, filePath;
|
|
||||||
public final long size;
|
|
||||||
|
|
||||||
public SizedFile(String path, long size) {
|
|
||||||
this.urlPath = path;
|
|
||||||
this.filePath = path;
|
|
||||||
this.size = size;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SizedFile(String urlPath, String filePath, long size) {
|
|
||||||
this.urlPath = urlPath;
|
|
||||||
this.filePath = filePath;
|
|
||||||
this.size = size;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,57 +0,0 @@
|
||||||
package pro.gravit.launcher;
|
|
||||||
|
|
||||||
import com.google.gson.JsonElement;
|
|
||||||
import com.google.gson.JsonParser;
|
|
||||||
import pro.gravit.utils.helper.LogHelper;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.io.OutputStreamWriter;
|
|
||||||
import java.net.HttpURLConnection;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
|
|
||||||
public final class HTTPRequest {
|
|
||||||
private static final int TIMEOUT = 10000;
|
|
||||||
|
|
||||||
private HTTPRequest() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public static JsonElement jsonRequest(JsonElement request, URL url) throws IOException {
|
|
||||||
return jsonRequest(request, "POST", url);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static JsonElement jsonRequest(JsonElement request, String method, URL url) throws IOException {
|
|
||||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
|
||||||
connection.setDoInput(true);
|
|
||||||
if (request != null) connection.setDoOutput(true);
|
|
||||||
connection.setRequestMethod(method);
|
|
||||||
if (request != null) connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
|
|
||||||
if (request != null) connection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.85 Safari/537.36");
|
|
||||||
connection.setRequestProperty("Accept", "application/json");
|
|
||||||
if (TIMEOUT > 0)
|
|
||||||
connection.setConnectTimeout(TIMEOUT);
|
|
||||||
if (request != null)
|
|
||||||
try (OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream(), StandardCharsets.UTF_8)) {
|
|
||||||
writer.write(request.toString());
|
|
||||||
writer.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
InputStreamReader reader;
|
|
||||||
int statusCode = connection.getResponseCode();
|
|
||||||
|
|
||||||
if (200 <= statusCode && statusCode < 300)
|
|
||||||
reader = new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8);
|
|
||||||
else
|
|
||||||
reader = new InputStreamReader(connection.getErrorStream(), StandardCharsets.UTF_8);
|
|
||||||
try {
|
|
||||||
return JsonParser.parseReader(reader);
|
|
||||||
} catch (Exception e) {
|
|
||||||
if (200 > statusCode || statusCode > 300) {
|
|
||||||
LogHelper.error("JsonRequest failed. Server response code %d", statusCode);
|
|
||||||
throw new IOException(e);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,62 +0,0 @@
|
||||||
package pro.gravit.utils;
|
|
||||||
|
|
||||||
import pro.gravit.launcher.AsyncDownloader;
|
|
||||||
import pro.gravit.utils.helper.LogHelper;
|
|
||||||
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
|
|
||||||
public class Downloader {
|
|
||||||
private final CompletableFuture<Void> future;
|
|
||||||
private final AsyncDownloader asyncDownloader;
|
|
||||||
private Downloader(CompletableFuture<Void> future, AsyncDownloader downloader) {
|
|
||||||
this.future = future;
|
|
||||||
this.asyncDownloader = downloader;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Downloader downloadList(List<AsyncDownloader.SizedFile> files, String baseURL, Path targetDir, DownloadCallback callback, ExecutorService executor, int threads) {
|
|
||||||
final boolean closeExecutor;
|
|
||||||
LogHelper.info("Download with legacy mode");
|
|
||||||
if (executor == null) {
|
|
||||||
executor = Executors.newWorkStealingPool(4);
|
|
||||||
closeExecutor = true;
|
|
||||||
} else {
|
|
||||||
closeExecutor = false;
|
|
||||||
}
|
|
||||||
AsyncDownloader asyncDownloader = new AsyncDownloader((diff) -> {
|
|
||||||
if (callback != null) {
|
|
||||||
callback.apply(diff);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
List<List<AsyncDownloader.SizedFile>> list = asyncDownloader.sortFiles(files, threads);
|
|
||||||
CompletableFuture<Void> future = CompletableFuture.allOf(asyncDownloader.runDownloadList(list, baseURL, targetDir, executor));
|
|
||||||
|
|
||||||
ExecutorService finalExecutor = executor;
|
|
||||||
return new Downloader(future.thenAccept(e -> {
|
|
||||||
if (closeExecutor) {
|
|
||||||
finalExecutor.shutdownNow();
|
|
||||||
}
|
|
||||||
}), asyncDownloader);
|
|
||||||
}
|
|
||||||
|
|
||||||
public CompletableFuture<Void> getFuture() {
|
|
||||||
return future;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void cancel() {
|
|
||||||
this.asyncDownloader.isClosed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isCanceled() {
|
|
||||||
return this.asyncDownloader.isClosed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface DownloadCallback {
|
|
||||||
void apply(long fullDiff);
|
|
||||||
|
|
||||||
void onComplete(Path path);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,76 +0,0 @@
|
||||||
package pro.gravit.utils;
|
|
||||||
|
|
||||||
import pro.gravit.utils.helper.IOHelper;
|
|
||||||
import pro.gravit.utils.helper.LogHelper;
|
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
import java.util.zip.ZipEntry;
|
|
||||||
import java.util.zip.ZipInputStream;
|
|
||||||
|
|
||||||
public final class HttpDownloader {
|
|
||||||
public static final int INTERVAL = 500;
|
|
||||||
public final AtomicInteger writed;
|
|
||||||
public final Thread thread;
|
|
||||||
private volatile String filename;
|
|
||||||
|
|
||||||
public HttpDownloader(URL url, Path file) {
|
|
||||||
writed = new AtomicInteger(0);
|
|
||||||
filename = null;
|
|
||||||
thread = new Thread(() -> {
|
|
||||||
try {
|
|
||||||
filename = IOHelper.getFileName(file);
|
|
||||||
downloadFile(url, file, writed::set);
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
thread.setDaemon(true);
|
|
||||||
thread.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void downloadFile(URL url, Path file, Consumer<Integer> chanheTrack) throws IOException {
|
|
||||||
try (BufferedInputStream in = new BufferedInputStream(url.openStream()); OutputStream fout = IOHelper.newOutput(file, false)) {
|
|
||||||
|
|
||||||
final byte[] data = new byte[IOHelper.BUFFER_SIZE];
|
|
||||||
int count;
|
|
||||||
long timestamp = System.currentTimeMillis();
|
|
||||||
int writed_local = 0;
|
|
||||||
while ((count = in.read(data, 0, IOHelper.BUFFER_SIZE)) != -1) {
|
|
||||||
fout.write(data, 0, count);
|
|
||||||
writed_local += count;
|
|
||||||
if (System.currentTimeMillis() - timestamp > INTERVAL) {
|
|
||||||
chanheTrack.accept(writed_local);
|
|
||||||
LogHelper.debug("Downloaded %d", writed_local);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
chanheTrack.accept(writed_local);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void downloadZip(URL url, Path dir) throws IOException {
|
|
||||||
try (ZipInputStream input = IOHelper.newZipInput(url)) {
|
|
||||||
Files.createDirectory(dir);
|
|
||||||
for (ZipEntry entry = input.getNextEntry(); entry != null; entry = input.getNextEntry()) {
|
|
||||||
if (entry.isDirectory()) {
|
|
||||||
Files.createDirectory(dir.resolve(IOHelper.toPath(entry.getName())));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// Unpack entry
|
|
||||||
String name = entry.getName();
|
|
||||||
LogHelper.subInfo("Downloading file: '%s'", name);
|
|
||||||
IOHelper.transfer(input, dir.resolve(IOHelper.toPath(name)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getFilename() {
|
|
||||||
return filename;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,60 +0,0 @@
|
||||||
package pro.gravit.launcher;
|
|
||||||
|
|
||||||
import com.google.gson.JsonElement;
|
|
||||||
import com.google.gson.JsonParser;
|
|
||||||
import pro.gravit.utils.helper.IOHelper;
|
|
||||||
import pro.gravit.utils.helper.LogHelper;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.net.http.HttpClient;
|
|
||||||
import java.net.http.HttpRequest;
|
|
||||||
import java.net.http.HttpResponse;
|
|
||||||
import java.time.Duration;
|
|
||||||
|
|
||||||
public final class HTTPRequest {
|
|
||||||
private static final int TIMEOUT = 10000;
|
|
||||||
|
|
||||||
private HTTPRequest() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public static JsonElement jsonRequest(JsonElement request, URL url) throws IOException {
|
|
||||||
return jsonRequest(request, "POST", url);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static JsonElement jsonRequest(JsonElement request, String method, URL url) throws IOException {
|
|
||||||
HttpClient client = HttpClient.newBuilder()
|
|
||||||
.build();
|
|
||||||
HttpRequest.BodyPublisher publisher;
|
|
||||||
if (request != null) {
|
|
||||||
publisher = HttpRequest.BodyPublishers.ofString(request.toString());
|
|
||||||
} else {
|
|
||||||
publisher = HttpRequest.BodyPublishers.noBody();
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
HttpRequest request1 = HttpRequest.newBuilder()
|
|
||||||
.method(method, publisher)
|
|
||||||
.uri(url.toURI())
|
|
||||||
.header("Content-Type", "application/json; charset=UTF-8")
|
|
||||||
.header("Accept", "application/json")
|
|
||||||
.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.85 Safari/537.36")
|
|
||||||
.timeout(Duration.ofMillis(TIMEOUT))
|
|
||||||
.build();
|
|
||||||
HttpResponse<InputStream> response = client.send(request1, HttpResponse.BodyHandlers.ofInputStream());
|
|
||||||
int statusCode = response.statusCode();
|
|
||||||
try {
|
|
||||||
return JsonParser.parseReader(IOHelper.newReader(response.body()));
|
|
||||||
} catch (Exception e) {
|
|
||||||
if (200 > statusCode || statusCode > 300) {
|
|
||||||
LogHelper.error("JsonRequest failed. Server response code %d", statusCode);
|
|
||||||
throw new IOException(e);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} catch (URISyntaxException | InterruptedException e) {
|
|
||||||
throw new IOException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
72
LauncherModernCore/build.gradle
Normal file
72
LauncherModernCore/build.gradle
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
sourceCompatibility = '17'
|
||||||
|
targetCompatibility = '17'
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
api project(':LauncherCore')
|
||||||
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
useJUnitPlatform()
|
||||||
|
testLogging {
|
||||||
|
events "passed", "skipped", "failed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jar {
|
||||||
|
archiveClassifier.set('clean')
|
||||||
|
manifest.attributes("Multi-Release": "true")
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register('sourcesJar', Jar) {
|
||||||
|
from sourceSets.main.allJava
|
||||||
|
archiveClassifier.set('sources')
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register('javadocJar', Jar) {
|
||||||
|
from javadoc
|
||||||
|
archiveClassifier.set('javadoc')
|
||||||
|
}
|
||||||
|
|
||||||
|
publishing {
|
||||||
|
publications {
|
||||||
|
launchermoderncore(MavenPublication) {
|
||||||
|
artifactId = 'launcher-modern-core'
|
||||||
|
artifact(jar) {
|
||||||
|
classifier ""
|
||||||
|
}
|
||||||
|
artifact sourcesJar
|
||||||
|
artifact javadocJar
|
||||||
|
pom {
|
||||||
|
name = 'GravitLauncher Core Utils with Java 17+'
|
||||||
|
description = 'GravitLauncher Core Utils'
|
||||||
|
url = 'https://gravitlauncher.com'
|
||||||
|
licenses {
|
||||||
|
license {
|
||||||
|
name = 'GNU General Public License, Version 3.0'
|
||||||
|
url = 'https://www.gnu.org/licenses/gpl-3.0.html'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
developers {
|
||||||
|
developer {
|
||||||
|
id = 'gravita'
|
||||||
|
name = 'Gravita'
|
||||||
|
email = 'gravita@gravit.pro'
|
||||||
|
}
|
||||||
|
developer {
|
||||||
|
id = 'zaxar163'
|
||||||
|
name = 'Zaxar163'
|
||||||
|
email = 'zahar.vcherachny@yandex.ru'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scm {
|
||||||
|
connection = 'scm:git:https://github.com/GravitLauncher/Launcher.git'
|
||||||
|
developerConnection = 'scm:git:ssh://git@github.com:GravitLauncher/Launcher.git'
|
||||||
|
url = 'https://gravitlauncher.com/'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
signing {
|
||||||
|
sign publishing.publications.launchermoderncore
|
||||||
|
}
|
|
@ -1,10 +1,13 @@
|
||||||
package pro.gravit.utils;
|
package pro.gravit.launcher.modern;
|
||||||
|
|
||||||
import pro.gravit.launcher.AsyncDownloader;
|
import pro.gravit.launcher.CertificatePinningTrustManager;
|
||||||
import pro.gravit.launcher.LauncherInject;
|
import pro.gravit.launcher.LauncherInject;
|
||||||
import pro.gravit.utils.helper.IOHelper;
|
import pro.gravit.utils.helper.IOHelper;
|
||||||
import pro.gravit.utils.helper.LogHelper;
|
import pro.gravit.utils.helper.LogHelper;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.SSLSocketFactory;
|
||||||
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.net.http.HttpClient;
|
import java.net.http.HttpClient;
|
||||||
|
@ -13,6 +16,11 @@
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.StandardOpenOption;
|
import java.nio.file.StandardOpenOption;
|
||||||
|
import java.security.KeyManagementException;
|
||||||
|
import java.security.KeyStoreException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
|
@ -26,6 +34,8 @@ public class Downloader {
|
||||||
private static boolean isCertificatePinning;
|
private static boolean isCertificatePinning;
|
||||||
@LauncherInject("launcher.noHttp2")
|
@LauncherInject("launcher.noHttp2")
|
||||||
private static boolean isNoHttp2;
|
private static boolean isNoHttp2;
|
||||||
|
private static volatile SSLSocketFactory sslSocketFactory;
|
||||||
|
private static volatile SSLContext sslContext;
|
||||||
protected final HttpClient client;
|
protected final HttpClient client;
|
||||||
protected final ExecutorService executor;
|
protected final ExecutorService executor;
|
||||||
protected final Queue<DownloadTask> tasks = new ConcurrentLinkedDeque<>();
|
protected final Queue<DownloadTask> tasks = new ConcurrentLinkedDeque<>();
|
||||||
|
@ -35,7 +45,50 @@ protected Downloader(HttpClient client, ExecutorService executor) {
|
||||||
this.executor = executor;
|
this.executor = executor;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Downloader downloadList(List<AsyncDownloader.SizedFile> files, String baseURL, Path targetDir, DownloadCallback callback, ExecutorService executor, int threads) throws Exception {
|
public static ThreadFactory getDaemonThreadFactory(String name) {
|
||||||
|
return (task) -> {
|
||||||
|
Thread thread = new Thread(task);
|
||||||
|
thread.setName(name);
|
||||||
|
thread.setDaemon(true);
|
||||||
|
return thread;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SSLSocketFactory makeSSLSocketFactory() throws NoSuchAlgorithmException, CertificateException, KeyStoreException, IOException, KeyManagementException {
|
||||||
|
if (sslSocketFactory != null) return sslSocketFactory;
|
||||||
|
SSLContext sslContext = makeSSLContext();
|
||||||
|
sslSocketFactory = sslContext.getSocketFactory();
|
||||||
|
return sslSocketFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SSLContext makeSSLContext() throws NoSuchAlgorithmException, CertificateException, KeyStoreException, IOException, KeyManagementException {
|
||||||
|
if (sslContext != null) return sslContext;
|
||||||
|
SSLContext sslContext = SSLContext.getInstance("TLS");
|
||||||
|
sslContext.init(null, CertificatePinningTrustManager.getTrustManager().getTrustManagers(), new SecureRandom());
|
||||||
|
return sslContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Downloader downloadFile(URI uri, Path path, ExecutorService executor) {
|
||||||
|
boolean closeExecutor = false;
|
||||||
|
if (executor == null) {
|
||||||
|
executor = Executors.newSingleThreadExecutor(getDaemonThreadFactory("Downloader"));
|
||||||
|
closeExecutor = true;
|
||||||
|
}
|
||||||
|
Downloader downloader = newDownloader(executor);
|
||||||
|
downloader.future = downloader.downloadFile(uri, path);
|
||||||
|
if (closeExecutor) {
|
||||||
|
ExecutorService finalExecutor = executor;
|
||||||
|
downloader.future = downloader.future.thenAccept((e) -> {
|
||||||
|
finalExecutor.shutdownNow();
|
||||||
|
}).exceptionallyCompose((ex) -> {
|
||||||
|
finalExecutor.shutdownNow();
|
||||||
|
return CompletableFuture.failedFuture(ex);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return downloader;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Downloader downloadList(List<SizedFile> files, String baseURL, Path targetDir, DownloadCallback callback, ExecutorService executor, int threads) throws Exception {
|
||||||
boolean closeExecutor = false;
|
boolean closeExecutor = false;
|
||||||
LogHelper.info("Download with Java 11+ HttpClient");
|
LogHelper.info("Download with Java 11+ HttpClient");
|
||||||
if (executor == null) {
|
if (executor == null) {
|
||||||
|
@ -46,7 +99,12 @@ public static Downloader downloadList(List<AsyncDownloader.SizedFile> files, Str
|
||||||
downloader.future = downloader.downloadFiles(files, baseURL, targetDir, callback, executor, threads);
|
downloader.future = downloader.downloadFiles(files, baseURL, targetDir, callback, executor, threads);
|
||||||
if (closeExecutor) {
|
if (closeExecutor) {
|
||||||
ExecutorService finalExecutor = executor;
|
ExecutorService finalExecutor = executor;
|
||||||
downloader.future = downloader.future.thenAccept(e -> finalExecutor.shutdownNow());
|
downloader.future = downloader.future.thenAccept((e) -> {
|
||||||
|
finalExecutor.shutdownNow();
|
||||||
|
}).exceptionallyCompose((ex) -> {
|
||||||
|
finalExecutor.shutdownNow();
|
||||||
|
return CompletableFuture.failedFuture(ex);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return downloader;
|
return downloader;
|
||||||
}
|
}
|
||||||
|
@ -61,7 +119,7 @@ public static Downloader newDownloader(ExecutorService executor) {
|
||||||
.executor(executor);
|
.executor(executor);
|
||||||
if (isCertificatePinning) {
|
if (isCertificatePinning) {
|
||||||
try {
|
try {
|
||||||
builder.sslContext(AsyncDownloader.makeSSLContext());
|
builder.sslContext(makeSSLContext());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new SecurityException(e);
|
throw new SecurityException(e);
|
||||||
}
|
}
|
||||||
|
@ -88,11 +146,23 @@ public CompletableFuture<Void> getFuture() {
|
||||||
return future;
|
return future;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompletableFuture<Void> downloadFiles(List<AsyncDownloader.SizedFile> files, String baseURL, Path targetDir, DownloadCallback callback, ExecutorService executor, int threads) throws Exception {
|
public CompletableFuture<Void> downloadFile(URI uri, Path path) {
|
||||||
|
return client.sendAsync(HttpRequest.newBuilder()
|
||||||
|
.GET()
|
||||||
|
.uri(uri)
|
||||||
|
.build(), HttpResponse.BodyHandlers.ofFile(path, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING)).thenCompose((t) -> {
|
||||||
|
if(t.statusCode() < 200 || t.statusCode() >= 400) {
|
||||||
|
return CompletableFuture.failedFuture(new IOException(String.format("Failed to download %s: code %d", uri.toString(), t.statusCode())));
|
||||||
|
}
|
||||||
|
return CompletableFuture.completedFuture(null);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompletableFuture<Void> downloadFiles(List<SizedFile> files, String baseURL, Path targetDir, DownloadCallback callback, ExecutorService executor, int threads) throws Exception {
|
||||||
// URI scheme
|
// URI scheme
|
||||||
URI baseUri = new URI(baseURL);
|
URI baseUri = baseURL == null ? null : new URI(baseURL);
|
||||||
Collections.shuffle(files);
|
Collections.shuffle(files);
|
||||||
Queue<AsyncDownloader.SizedFile> queue = new ConcurrentLinkedDeque<>(files);
|
Queue<SizedFile> queue = new ConcurrentLinkedDeque<>(files);
|
||||||
CompletableFuture<Void> future = new CompletableFuture<>();
|
CompletableFuture<Void> future = new CompletableFuture<>();
|
||||||
AtomicInteger currentThreads = new AtomicInteger(threads);
|
AtomicInteger currentThreads = new AtomicInteger(threads);
|
||||||
ConsumerObject consumerObject = new ConsumerObject();
|
ConsumerObject consumerObject = new ConsumerObject();
|
||||||
|
@ -100,7 +170,7 @@ public CompletableFuture<Void> downloadFiles(List<AsyncDownloader.SizedFile> fil
|
||||||
if (callback != null && e != null) {
|
if (callback != null && e != null) {
|
||||||
callback.onComplete(e.body());
|
callback.onComplete(e.body());
|
||||||
}
|
}
|
||||||
AsyncDownloader.SizedFile file = queue.poll();
|
SizedFile file = queue.poll();
|
||||||
if (file == null) {
|
if (file == null) {
|
||||||
if (currentThreads.decrementAndGet() == 0)
|
if (currentThreads.decrementAndGet() == 0)
|
||||||
future.complete(null);
|
future.complete(null);
|
||||||
|
@ -124,7 +194,7 @@ public CompletableFuture<Void> downloadFiles(List<AsyncDownloader.SizedFile> fil
|
||||||
return future;
|
return future;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected DownloadTask sendAsync(AsyncDownloader.SizedFile file, URI baseUri, Path targetDir, DownloadCallback callback) throws Exception {
|
protected DownloadTask sendAsync(SizedFile file, URI baseUri, Path targetDir, DownloadCallback callback) throws Exception {
|
||||||
IOHelper.createParentDirs(targetDir.resolve(file.filePath));
|
IOHelper.createParentDirs(targetDir.resolve(file.filePath));
|
||||||
ProgressTrackingBodyHandler<Path> bodyHandler = makeBodyHandler(targetDir.resolve(file.filePath), callback);
|
ProgressTrackingBodyHandler<Path> bodyHandler = makeBodyHandler(targetDir.resolve(file.filePath), callback);
|
||||||
CompletableFuture<HttpResponse<Path>> future = client.sendAsync(makeHttpRequest(baseUri, file.urlPath), bodyHandler);
|
CompletableFuture<HttpResponse<Path>> future = client.sendAsync(makeHttpRequest(baseUri, file.urlPath), bodyHandler);
|
||||||
|
@ -139,15 +209,21 @@ protected DownloadTask sendAsync(AsyncDownloader.SizedFile file, URI baseUri, Pa
|
||||||
}
|
}
|
||||||
|
|
||||||
protected HttpRequest makeHttpRequest(URI baseUri, String filePath) throws URISyntaxException {
|
protected HttpRequest makeHttpRequest(URI baseUri, String filePath) throws URISyntaxException {
|
||||||
String scheme = baseUri.getScheme();
|
URI uri;
|
||||||
String host = baseUri.getHost();
|
if(baseUri != null) {
|
||||||
int port = baseUri.getPort();
|
String scheme = baseUri.getScheme();
|
||||||
if (port != -1)
|
String host = baseUri.getHost();
|
||||||
host = host + ":" + port;
|
int port = baseUri.getPort();
|
||||||
String path = baseUri.getPath();
|
if (port != -1)
|
||||||
|
host = host + ":" + port;
|
||||||
|
String path = baseUri.getPath();
|
||||||
|
uri = new URI(scheme, host, path + filePath, "", "");
|
||||||
|
} else {
|
||||||
|
uri = new URI(filePath);
|
||||||
|
}
|
||||||
return HttpRequest.newBuilder()
|
return HttpRequest.newBuilder()
|
||||||
.GET()
|
.GET()
|
||||||
.uri(new URI(scheme, host, path + filePath, "", ""))
|
.uri(uri)
|
||||||
.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.85 Safari/537.36")
|
.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.85 Safari/537.36")
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
@ -262,4 +338,21 @@ public void cancel() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class SizedFile {
|
||||||
|
public final String urlPath, filePath;
|
||||||
|
public final long size;
|
||||||
|
|
||||||
|
public SizedFile(String path, long size) {
|
||||||
|
this.urlPath = path;
|
||||||
|
this.filePath = path;
|
||||||
|
this.size = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SizedFile(String urlPath, String filePath, long size) {
|
||||||
|
this.urlPath = urlPath;
|
||||||
|
this.filePath = filePath;
|
||||||
|
this.size = size;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
2
modules
2
modules
|
@ -1 +1 @@
|
||||||
Subproject commit 1229e6c912a187f8f5cd239934faa0586113207e
|
Subproject commit f6bb09363dc2a1de0f3528b5c9d870e18ca9680e
|
|
@ -1,6 +1,7 @@
|
||||||
rootProject.name = 'GravitLauncher'
|
rootProject.name = 'GravitLauncher'
|
||||||
|
|
||||||
include 'Launcher'
|
include 'Launcher'
|
||||||
|
include 'LauncherModernCore'
|
||||||
include 'LauncherCore'
|
include 'LauncherCore'
|
||||||
include 'LauncherAPI'
|
include 'LauncherAPI'
|
||||||
include 'LauncherClient'
|
include 'LauncherClient'
|
||||||
|
|
Loading…
Reference in a new issue