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 {
|
||||
pack project(':LauncherAPI')
|
||||
pack project(':LauncherModernCore')
|
||||
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: 'org.fusesource.jansi', name: 'jansi', version: rootProject['verJansi']
|
||||
|
|
|
@ -3,15 +3,12 @@
|
|||
import com.google.gson.reflect.TypeToken;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
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.launchserver.HttpRequester;
|
||||
import pro.gravit.utils.helper.SecurityHelper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
|
|
@ -3,11 +3,10 @@
|
|||
import me.tongfei.progressbar.ProgressBar;
|
||||
import me.tongfei.progressbar.ProgressBarBuilder;
|
||||
import me.tongfei.progressbar.ProgressBarStyle;
|
||||
import pro.gravit.launcher.AsyncDownloader;
|
||||
import pro.gravit.launcher.Launcher;
|
||||
import pro.gravit.launcher.modern.Downloader;
|
||||
import pro.gravit.launcher.profiles.ClientProfile;
|
||||
import pro.gravit.launchserver.LaunchServer;
|
||||
import pro.gravit.utils.Downloader;
|
||||
import pro.gravit.utils.command.CommandException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -45,9 +44,9 @@ protected boolean showApplyDialog(String text) throws IOException {
|
|||
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;
|
||||
for (AsyncDownloader.SizedFile file : list) {
|
||||
for (Downloader.SizedFile file : list) {
|
||||
total += file.size;
|
||||
}
|
||||
long totalFiles = list.size();
|
||||
|
|
|
@ -3,12 +3,11 @@
|
|||
import com.google.gson.JsonObject;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import pro.gravit.launcher.AsyncDownloader;
|
||||
import pro.gravit.launcher.Launcher;
|
||||
import pro.gravit.launcher.modern.Downloader;
|
||||
import pro.gravit.launchserver.HttpRequester;
|
||||
import pro.gravit.launchserver.LaunchServer;
|
||||
import pro.gravit.launchserver.command.Command;
|
||||
import pro.gravit.utils.Downloader;
|
||||
import pro.gravit.utils.helper.IOHelper;
|
||||
|
||||
import java.io.Writer;
|
||||
|
@ -85,7 +84,7 @@ public void invoke(String... args) throws Exception {
|
|||
logger.info("Copy {} into {}", indexPath, targetPath);
|
||||
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()) {
|
||||
var value = e.getValue().getAsJsonObject();
|
||||
var hash = value.get("hash").getAsString();
|
||||
|
@ -101,7 +100,7 @@ public void invoke(String... args) throws Exception {
|
|||
continue;
|
||||
}
|
||||
}
|
||||
toDownload.add(new AsyncDownloader.SizedFile(hash, path, size));
|
||||
toDownload.add(new Downloader.SizedFile(hash, path, size));
|
||||
}
|
||||
logger.info("Download {} files", toDownload.size());
|
||||
Downloader downloader = downloadWithProgressBar(dirName, toDownload, RESOURCES_DOWNLOAD_URL, assetDir);
|
||||
|
|
|
@ -3,20 +3,27 @@
|
|||
import com.google.gson.JsonElement;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import pro.gravit.launcher.HTTPRequest;
|
||||
import pro.gravit.utils.HttpDownloader;
|
||||
import pro.gravit.launcher.Launcher;
|
||||
import pro.gravit.utils.helper.IOHelper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
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.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
public class MirrorManager {
|
||||
protected final ArrayList<Mirror> list = new ArrayList<>();
|
||||
private transient final Logger logger = LogManager.getLogger();
|
||||
private transient final HttpClient client = HttpClient.newBuilder().build();
|
||||
private Mirror defaultMirror;
|
||||
|
||||
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);
|
||||
logger.debug("Try download {}", url.toString());
|
||||
try {
|
||||
HttpDownloader.downloadZip(url, path);
|
||||
downloadZip(url, path);
|
||||
} catch (IOException e) {
|
||||
logger.error("Download {} failed({}: {})", url.toString(), e.getClass().getName(), e.getMessage());
|
||||
return false;
|
||||
|
@ -82,8 +89,12 @@ public JsonElement jsonRequest(Mirror mirror, JsonElement request, String method
|
|||
if (!mirror.enabled) return null;
|
||||
URL url = mirror.getURL(mask, args);
|
||||
try {
|
||||
return HTTPRequest.jsonRequest(request, method, url);
|
||||
} catch (IOException e) {
|
||||
var response = client.send(HttpRequest.newBuilder()
|
||||
.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());
|
||||
return null;
|
||||
}
|
||||
|
@ -101,6 +112,22 @@ public JsonElement jsonRequest(JsonElement request, String method, String mask,
|
|||
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 {
|
||||
final String baseUrl;
|
||||
boolean enabled;
|
||||
|
|
|
@ -13,8 +13,8 @@
|
|||
version = "12"
|
||||
modules = ['javafx.controls', 'javafx.fxml']
|
||||
}
|
||||
sourceCompatibility = '1.8'
|
||||
targetCompatibility = '1.8'
|
||||
sourceCompatibility = '17'
|
||||
targetCompatibility = '17'
|
||||
|
||||
configurations {
|
||||
bundle
|
||||
|
@ -49,6 +49,7 @@
|
|||
|
||||
dependencies {
|
||||
pack project(':LauncherAPI')
|
||||
pack project(':LauncherModernCore')
|
||||
pack project(':LauncherClient')
|
||||
pack project(':LauncherStart')
|
||||
bundle group: 'com.github.oshi', name: 'oshi-core', version: rootProject['verOshiCore']
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package pro.gravit.launcher.utils;
|
||||
|
||||
import pro.gravit.launcher.AsyncDownloader;
|
||||
import pro.gravit.launcher.LauncherEngine;
|
||||
import pro.gravit.launcher.LauncherInject;
|
||||
import pro.gravit.launcher.request.update.LauncherRequest;
|
||||
|
@ -23,6 +22,8 @@
|
|||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static pro.gravit.launcher.modern.Downloader.makeSSLSocketFactory;
|
||||
|
||||
public class LauncherUpdater {
|
||||
@LauncherInject("launcher.certificatePinning")
|
||||
private static boolean isCertificatePinning;
|
||||
|
@ -49,7 +50,7 @@ public static Path prepareUpdate(URL url) throws Exception {
|
|||
if (isCertificatePinning) {
|
||||
HttpsURLConnection connection1 = (HttpsURLConnection) connection;
|
||||
try {
|
||||
connection1.setSSLSocketFactory(AsyncDownloader.makeSSLSocketFactory());
|
||||
connection1.setSSLSocketFactory(makeSSLSocketFactory());
|
||||
} catch (NoSuchAlgorithmException | CertificateException | KeyStoreException | KeyManagementException 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.utils.helper.IOHelper;
|
||||
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.URISyntaxException;
|
||||
import java.net.http.HttpClient;
|
||||
|
@ -13,6 +16,11 @@
|
|||
import java.nio.ByteBuffer;
|
||||
import java.nio.file.Path;
|
||||
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.List;
|
||||
import java.util.Queue;
|
||||
|
@ -26,6 +34,8 @@ public class Downloader {
|
|||
private static boolean isCertificatePinning;
|
||||
@LauncherInject("launcher.noHttp2")
|
||||
private static boolean isNoHttp2;
|
||||
private static volatile SSLSocketFactory sslSocketFactory;
|
||||
private static volatile SSLContext sslContext;
|
||||
protected final HttpClient client;
|
||||
protected final ExecutorService executor;
|
||||
protected final Queue<DownloadTask> tasks = new ConcurrentLinkedDeque<>();
|
||||
|
@ -35,7 +45,50 @@ protected Downloader(HttpClient client, ExecutorService 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;
|
||||
LogHelper.info("Download with Java 11+ HttpClient");
|
||||
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);
|
||||
if (closeExecutor) {
|
||||
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;
|
||||
}
|
||||
|
@ -61,7 +119,7 @@ public static Downloader newDownloader(ExecutorService executor) {
|
|||
.executor(executor);
|
||||
if (isCertificatePinning) {
|
||||
try {
|
||||
builder.sslContext(AsyncDownloader.makeSSLContext());
|
||||
builder.sslContext(makeSSLContext());
|
||||
} catch (Exception e) {
|
||||
throw new SecurityException(e);
|
||||
}
|
||||
|
@ -88,11 +146,23 @@ public CompletableFuture<Void> getFuture() {
|
|||
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 baseUri = new URI(baseURL);
|
||||
URI baseUri = baseURL == null ? null : new URI(baseURL);
|
||||
Collections.shuffle(files);
|
||||
Queue<AsyncDownloader.SizedFile> queue = new ConcurrentLinkedDeque<>(files);
|
||||
Queue<SizedFile> queue = new ConcurrentLinkedDeque<>(files);
|
||||
CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
AtomicInteger currentThreads = new AtomicInteger(threads);
|
||||
ConsumerObject consumerObject = new ConsumerObject();
|
||||
|
@ -100,7 +170,7 @@ public CompletableFuture<Void> downloadFiles(List<AsyncDownloader.SizedFile> fil
|
|||
if (callback != null && e != null) {
|
||||
callback.onComplete(e.body());
|
||||
}
|
||||
AsyncDownloader.SizedFile file = queue.poll();
|
||||
SizedFile file = queue.poll();
|
||||
if (file == null) {
|
||||
if (currentThreads.decrementAndGet() == 0)
|
||||
future.complete(null);
|
||||
|
@ -124,7 +194,7 @@ public CompletableFuture<Void> downloadFiles(List<AsyncDownloader.SizedFile> fil
|
|||
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));
|
||||
ProgressTrackingBodyHandler<Path> bodyHandler = makeBodyHandler(targetDir.resolve(file.filePath), callback);
|
||||
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 {
|
||||
String scheme = baseUri.getScheme();
|
||||
String host = baseUri.getHost();
|
||||
int port = baseUri.getPort();
|
||||
if (port != -1)
|
||||
host = host + ":" + port;
|
||||
String path = baseUri.getPath();
|
||||
URI uri;
|
||||
if(baseUri != null) {
|
||||
String scheme = baseUri.getScheme();
|
||||
String host = baseUri.getHost();
|
||||
int port = baseUri.getPort();
|
||||
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()
|
||||
.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")
|
||||
.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'
|
||||
|
||||
include 'Launcher'
|
||||
include 'LauncherModernCore'
|
||||
include 'LauncherCore'
|
||||
include 'LauncherAPI'
|
||||
include 'LauncherClient'
|
||||
|
|
Loading…
Reference in a new issue