[FEATURE] Remove deprecated http methods, fix download

This commit is contained in:
Gravita 2023-10-29 21:18:47 +07:00
parent 474d557e3f
commit fe7ae41f65
16 changed files with 229 additions and 506 deletions

View file

@ -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']

View file

@ -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;

View file

@ -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();

View file

@ -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);

View file

@ -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;

View file

@ -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']

View file

@ -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);
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}
}

View 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
}

View file

@ -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;
}
}
}

@ -1 +1 @@
Subproject commit 1229e6c912a187f8f5cd239934faa0586113207e
Subproject commit f6bb09363dc2a1de0f3528b5c9d870e18ca9680e

View file

@ -1,6 +1,7 @@
rootProject.name = 'GravitLauncher'
include 'Launcher'
include 'LauncherModernCore'
include 'LauncherCore'
include 'LauncherAPI'
include 'LauncherClient'