mirror of
https://github.com/HMCL-dev/HMCL.git
synced 2025-09-12 13:26:53 -04:00
FetchTask 启用透明 GZip 压缩 (#4219)
This commit is contained in:
parent
bbc546e2a0
commit
b1d8edc72a
@ -21,26 +21,24 @@ import org.jackhuang.hmcl.event.Event;
|
||||
import org.jackhuang.hmcl.event.EventBus;
|
||||
import org.jackhuang.hmcl.util.CacheRepository;
|
||||
import org.jackhuang.hmcl.util.DigestUtils;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.ToStringBuilder;
|
||||
import org.jackhuang.hmcl.util.io.ContentEncoding;
|
||||
import org.jackhuang.hmcl.util.io.IOUtils;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jackhuang.hmcl.util.io.ResponseCodeException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.*;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.threadPool;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
@ -107,7 +105,7 @@ public abstract class FetchTask<T> extends Task<T> {
|
||||
break download;
|
||||
}
|
||||
|
||||
List<String> redirects = null;
|
||||
List<URI> redirects = null;
|
||||
String bmclapiHash = null;
|
||||
try {
|
||||
beforeDownload(uri);
|
||||
@ -117,6 +115,7 @@ public abstract class FetchTask<T> extends Task<T> {
|
||||
|
||||
if (conn instanceof HttpURLConnection) {
|
||||
var httpConnection = (HttpURLConnection) conn;
|
||||
httpConnection.setRequestProperty("Accept-Encoding", "gzip");
|
||||
|
||||
if (checkETag) repository.injectConnection(httpConnection);
|
||||
Map<String, List<String>> requestProperties = httpConnection.getRequestProperties();
|
||||
@ -143,25 +142,25 @@ public abstract class FetchTask<T> extends Task<T> {
|
||||
throw new IOException("Too much redirects");
|
||||
}
|
||||
|
||||
URL prevUrl = httpConnection.getURL();
|
||||
String location = httpConnection.getHeaderField("Location");
|
||||
|
||||
httpConnection.disconnect();
|
||||
if (location == null || location.isBlank()) {
|
||||
if (StringUtils.isBlank(location))
|
||||
throw new IOException("Redirected to an empty location");
|
||||
}
|
||||
|
||||
URL target = new URL(prevUrl, NetworkUtils.encodeLocation(location));
|
||||
redirects.add(target.toString());
|
||||
URI target = NetworkUtils.toURI(httpConnection.getURL())
|
||||
.resolve(NetworkUtils.toURI(location));
|
||||
redirects.add(target);
|
||||
|
||||
HttpURLConnection redirected = (HttpURLConnection) target.openConnection();
|
||||
if (!NetworkUtils.isHttpUri(target))
|
||||
throw new IOException("Redirected to not http URI: " + target);
|
||||
|
||||
HttpURLConnection redirected = NetworkUtils.createHttpConnection(target);
|
||||
redirected.setUseCaches(checkETag);
|
||||
redirected.setConnectTimeout(NetworkUtils.TIME_OUT);
|
||||
redirected.setReadTimeout(NetworkUtils.TIME_OUT);
|
||||
redirected.setInstanceFollowRedirects(false);
|
||||
requestProperties
|
||||
.forEach((key, value) -> value.forEach(element ->
|
||||
redirected.addRequestProperty(key, element)));
|
||||
.forEach((key, values) ->
|
||||
values.forEach(element ->
|
||||
redirected.addRequestProperty(key, element)));
|
||||
httpConnection = redirected;
|
||||
} else {
|
||||
break;
|
||||
@ -192,36 +191,36 @@ public abstract class FetchTask<T> extends Task<T> {
|
||||
}
|
||||
}
|
||||
|
||||
long contentLength = conn.getContentLength();
|
||||
try (Context context = getContext(conn, checkETag, bmclapiHash);
|
||||
InputStream stream = conn.getInputStream()) {
|
||||
int lastDownloaded = 0, downloaded = 0;
|
||||
long contentLength = conn.getContentLengthLong();
|
||||
var encoding = ContentEncoding.fromConnection(conn);
|
||||
try (var context = getContext(conn, checkETag, bmclapiHash);
|
||||
var counter = new CounterInputStream(conn.getInputStream());
|
||||
var input = encoding.wrap(counter)) {
|
||||
long lastDownloaded = 0L;
|
||||
byte[] buffer = new byte[IOUtils.DEFAULT_BUFFER_SIZE];
|
||||
while (true) {
|
||||
if (isCancelled()) break;
|
||||
|
||||
int len = stream.read(buffer);
|
||||
int len = input.read(buffer);
|
||||
if (len == -1) break;
|
||||
|
||||
context.write(buffer, 0, len);
|
||||
|
||||
downloaded += len;
|
||||
|
||||
if (contentLength >= 0) {
|
||||
// Update progress information per second
|
||||
updateProgress(downloaded, contentLength);
|
||||
updateProgress(counter.downloaded, contentLength);
|
||||
}
|
||||
|
||||
updateDownloadSpeed(downloaded - lastDownloaded);
|
||||
lastDownloaded = downloaded;
|
||||
updateDownloadSpeed(counter.downloaded - lastDownloaded);
|
||||
lastDownloaded = counter.downloaded;
|
||||
}
|
||||
|
||||
if (isCancelled()) break download;
|
||||
|
||||
updateDownloadSpeed(downloaded - lastDownloaded);
|
||||
updateDownloadSpeed(counter.downloaded - lastDownloaded);
|
||||
|
||||
if (contentLength >= 0 && downloaded != contentLength)
|
||||
throw new IOException("Unexpected file size: " + downloaded + ", expected: " + contentLength);
|
||||
if (contentLength >= 0 && counter.downloaded != contentLength)
|
||||
throw new IOException("Unexpected file size: " + counter.downloaded + ", expected: " + contentLength);
|
||||
|
||||
context.withResult(true);
|
||||
}
|
||||
@ -246,7 +245,7 @@ public abstract class FetchTask<T> extends Task<T> {
|
||||
}
|
||||
|
||||
private static final Timer timer = new Timer("DownloadSpeedRecorder", true);
|
||||
private static final AtomicInteger downloadSpeed = new AtomicInteger(0);
|
||||
private static final AtomicLong downloadSpeed = new AtomicLong(0L);
|
||||
public static final EventBus speedEvent = new EventBus();
|
||||
|
||||
static {
|
||||
@ -258,14 +257,38 @@ public abstract class FetchTask<T> extends Task<T> {
|
||||
}, 0, 1000);
|
||||
}
|
||||
|
||||
private static void updateDownloadSpeed(int speed) {
|
||||
private static void updateDownloadSpeed(long speed) {
|
||||
downloadSpeed.addAndGet(speed);
|
||||
}
|
||||
|
||||
public static class SpeedEvent extends Event {
|
||||
private final int speed;
|
||||
private static final class CounterInputStream extends FilterInputStream {
|
||||
long downloaded;
|
||||
|
||||
public SpeedEvent(Object source, int speed) {
|
||||
CounterInputStream(InputStream in) {
|
||||
super(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
int b = in.read();
|
||||
if (b >= 0)
|
||||
downloaded++;
|
||||
return b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
int n = in.read(b, off, len);
|
||||
if (n >= 0)
|
||||
downloaded += n;
|
||||
return n;
|
||||
}
|
||||
}
|
||||
|
||||
public static class SpeedEvent extends Event {
|
||||
private final long speed;
|
||||
|
||||
public SpeedEvent(Object source, long speed) {
|
||||
super(source);
|
||||
|
||||
this.speed = speed;
|
||||
@ -276,7 +299,7 @@ public abstract class FetchTask<T> extends Task<T> {
|
||||
*
|
||||
* @return download speed
|
||||
*/
|
||||
public int getSpeed() {
|
||||
public long getSpeed() {
|
||||
return speed;
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,7 @@ import java.util.zip.GZIPInputStream;
|
||||
* @author Glavo
|
||||
*/
|
||||
public enum ContentEncoding {
|
||||
NONE {
|
||||
IDENTITY {
|
||||
@Override
|
||||
public InputStream wrap(InputStream inputStream) {
|
||||
return inputStream;
|
||||
@ -43,8 +43,8 @@ public enum ContentEncoding {
|
||||
|
||||
public static @NotNull ContentEncoding fromConnection(URLConnection connection) throws IOException {
|
||||
String encoding = connection.getContentEncoding();
|
||||
if (encoding == null || encoding.isEmpty()) {
|
||||
return NONE;
|
||||
if (encoding == null || encoding.isEmpty() || "identity".equals(encoding)) {
|
||||
return IDENTITY;
|
||||
} else if ("gzip".equalsIgnoreCase(encoding)) {
|
||||
return GZIP;
|
||||
} else {
|
||||
|
Loading…
x
Reference in New Issue
Block a user