mirror of
https://github.com/HMCL-dev/HMCL.git
synced 2025-09-17 15:57:18 -04:00
fix: support downloading files from urls whose protocols are not http.
This commit is contained in:
parent
63e2fdf56d
commit
1bba1b427c
@ -49,8 +49,8 @@ public final class FabricInstallTask extends Task<Version> {
|
|||||||
this.version = version;
|
this.version = version;
|
||||||
this.remote = remoteVersion;
|
this.remote = remoteVersion;
|
||||||
|
|
||||||
launchMetaTask = new GetTask(dependencyManager.getDownloadProvider().injectURLsWithCandidates(remoteVersion.getUrls()))
|
launchMetaTask = new GetTask(dependencyManager.getDownloadProvider().injectURLsWithCandidates(remoteVersion.getUrls()));
|
||||||
.setCacheRepository(dependencyManager.getCacheRepository());
|
launchMetaTask.setCacheRepository(dependencyManager.getCacheRepository());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -72,9 +72,9 @@ public final class ForgeInstallTask extends Task<Version> {
|
|||||||
|
|
||||||
dependent = new FileDownloadTask(
|
dependent = new FileDownloadTask(
|
||||||
dependencyManager.getDownloadProvider().injectURLsWithCandidates(remote.getUrls()),
|
dependencyManager.getDownloadProvider().injectURLsWithCandidates(remote.getUrls()),
|
||||||
installer.toFile(), null)
|
installer.toFile(), null);
|
||||||
.setCacheRepository(dependencyManager.getCacheRepository())
|
dependent.setCacheRepository(dependencyManager.getCacheRepository());
|
||||||
.setCaching(true);
|
dependent.setCaching(true);
|
||||||
dependent.addIntegrityCheckHandler(FileDownloadTask.ZIP_INTEGRITY_CHECK_HANDLER);
|
dependent.addIntegrityCheckHandler(FileDownloadTask.ZIP_INTEGRITY_CHECK_HANDLER);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,11 +113,11 @@ public final class GameAssetDownloadTask extends Task<Void> {
|
|||||||
|
|
||||||
FileDownloadTask task = new FileDownloadTask(urls, file, new FileDownloadTask.IntegrityCheck("SHA-1", assetObject.getHash()));
|
FileDownloadTask task = new FileDownloadTask(urls, file, new FileDownloadTask.IntegrityCheck("SHA-1", assetObject.getHash()));
|
||||||
task.setName(assetObject.getHash());
|
task.setName(assetObject.getHash());
|
||||||
dependencies.add(task
|
task.setCandidate(dependencyManager.getCacheRepository().getCommonDirectory()
|
||||||
.setCacheRepository(dependencyManager.getCacheRepository())
|
.resolve("assets").resolve("objects").resolve(assetObject.getLocation()));
|
||||||
.setCaching(true)
|
task.setCacheRepository(dependencyManager.getCacheRepository());
|
||||||
.setCandidate(dependencyManager.getCacheRepository().getCommonDirectory()
|
task.setCaching(true);
|
||||||
.resolve("assets").resolve("objects").resolve(assetObject.getLocation())).withCounter());
|
dependencies.add(task.withCounter());
|
||||||
} else {
|
} else {
|
||||||
dependencyManager.getCacheRepository().tryCacheFile(file.toPath(), CacheRepository.SHA1, assetObject.getHash());
|
dependencyManager.getCacheRepository().tryCacheFile(file.toPath(), CacheRepository.SHA1, assetObject.getHash());
|
||||||
}
|
}
|
||||||
|
@ -63,10 +63,12 @@ public final class GameAssetIndexDownloadTask extends Task<Void> {
|
|||||||
|
|
||||||
// We should not check the hash code of asset index file since this file is not consistent
|
// We should not check the hash code of asset index file since this file is not consistent
|
||||||
// And Mojang will modify this file anytime. So assetIndex.hash might be outdated.
|
// And Mojang will modify this file anytime. So assetIndex.hash might be outdated.
|
||||||
dependencies.add(new FileDownloadTask(
|
FileDownloadTask task = new FileDownloadTask(
|
||||||
dependencyManager.getDownloadProvider().injectURLWithCandidates(assetIndexInfo.getUrl()),
|
dependencyManager.getDownloadProvider().injectURLWithCandidates(assetIndexInfo.getUrl()),
|
||||||
assetIndexFile
|
assetIndexFile
|
||||||
).setCacheRepository(dependencyManager.getCacheRepository()));
|
);
|
||||||
|
task.setCacheRepository(dependencyManager.getCacheRepository());
|
||||||
|
dependencies.add(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -59,9 +59,9 @@ public final class GameDownloadTask extends Task<Void> {
|
|||||||
FileDownloadTask task = new FileDownloadTask(
|
FileDownloadTask task = new FileDownloadTask(
|
||||||
dependencyManager.getDownloadProvider().injectURLWithCandidates(version.getDownloadInfo().getUrl()),
|
dependencyManager.getDownloadProvider().injectURLWithCandidates(version.getDownloadInfo().getUrl()),
|
||||||
jar,
|
jar,
|
||||||
IntegrityCheck.of(CacheRepository.SHA1, version.getDownloadInfo().getSha1()))
|
IntegrityCheck.of(CacheRepository.SHA1, version.getDownloadInfo().getSha1()));
|
||||||
.setCaching(true)
|
task.setCaching(true);
|
||||||
.setCacheRepository(dependencyManager.getCacheRepository());
|
task.setCacheRepository(dependencyManager.getCacheRepository());
|
||||||
|
|
||||||
if (gameVersion != null)
|
if (gameVersion != null)
|
||||||
task.setCandidate(dependencyManager.getCacheRepository().getCommonDirectory().resolve("jars").resolve(gameVersion + ".jar"));
|
task.setCandidate(dependencyManager.getCacheRepository().getCommonDirectory().resolve("jars").resolve(gameVersion + ".jar"));
|
||||||
|
@ -129,16 +129,16 @@ public class LibraryDownloadTask extends Task<Void> {
|
|||||||
URL packXz = NetworkUtils.toURL(dependencyManager.getDownloadProvider().injectURL(url) + ".pack.xz");
|
URL packXz = NetworkUtils.toURL(dependencyManager.getDownloadProvider().injectURL(url) + ".pack.xz");
|
||||||
if (NetworkUtils.urlExists(packXz)) {
|
if (NetworkUtils.urlExists(packXz)) {
|
||||||
List<URL> urls = dependencyManager.getDownloadProvider().injectURLWithCandidates(url + ".pack.xz");
|
List<URL> urls = dependencyManager.getDownloadProvider().injectURLWithCandidates(url + ".pack.xz");
|
||||||
task = new FileDownloadTask(urls, xzFile, null)
|
task = new FileDownloadTask(urls, xzFile, null);
|
||||||
.setCacheRepository(cacheRepository)
|
task.setCacheRepository(cacheRepository);
|
||||||
.setCaching(true);
|
task.setCaching(true);
|
||||||
xz = true;
|
xz = true;
|
||||||
} else {
|
} else {
|
||||||
List<URL> urls = dependencyManager.getDownloadProvider().injectURLWithCandidates(url);
|
List<URL> urls = dependencyManager.getDownloadProvider().injectURLWithCandidates(url);
|
||||||
task = new FileDownloadTask(urls, jar,
|
task = new FileDownloadTask(urls, jar,
|
||||||
library.getDownload().getSha1() != null ? new IntegrityCheck("SHA-1", library.getDownload().getSha1()) : null)
|
library.getDownload().getSha1() != null ? new IntegrityCheck("SHA-1", library.getDownload().getSha1()) : null);
|
||||||
.setCacheRepository(cacheRepository)
|
task.setCacheRepository(cacheRepository);
|
||||||
.setCaching(true);
|
task.setCaching(true);
|
||||||
task.addIntegrityCheckHandler(FileDownloadTask.ZIP_INTEGRITY_CHECK_HANDLER);
|
task.addIntegrityCheckHandler(FileDownloadTask.ZIP_INTEGRITY_CHECK_HANDLER);
|
||||||
xz = false;
|
xz = false;
|
||||||
}
|
}
|
||||||
|
@ -92,11 +92,12 @@ public final class OptiFineInstallTask extends Task<Version> {
|
|||||||
dest = Files.createTempFile("optifine-installer", ".jar");
|
dest = Files.createTempFile("optifine-installer", ".jar");
|
||||||
|
|
||||||
if (installer == null) {
|
if (installer == null) {
|
||||||
dependents.add(new FileDownloadTask(
|
FileDownloadTask task = new FileDownloadTask(
|
||||||
dependencyManager.getDownloadProvider().injectURLsWithCandidates(remote.getUrls()),
|
dependencyManager.getDownloadProvider().injectURLsWithCandidates(remote.getUrls()),
|
||||||
dest.toFile(), null)
|
dest.toFile(), null);
|
||||||
.setCacheRepository(dependencyManager.getCacheRepository())
|
task.setCacheRepository(dependencyManager.getCacheRepository());
|
||||||
.setCaching(true));
|
task.setCaching(true);
|
||||||
|
dependents.add(task);
|
||||||
} else {
|
} else {
|
||||||
FileUtils.copyFile(installer, dest);
|
FileUtils.copyFile(installer, dest);
|
||||||
}
|
}
|
||||||
|
@ -149,9 +149,10 @@ public final class CurseCompletionTask extends Task<Void> {
|
|||||||
for (CurseManifestFile file : newManifest.getFiles())
|
for (CurseManifestFile file : newManifest.getFiles())
|
||||||
if (StringUtils.isNotBlank(file.getFileName())) {
|
if (StringUtils.isNotBlank(file.getFileName())) {
|
||||||
if (!modManager.hasSimpleMod(file.getFileName())) {
|
if (!modManager.hasSimpleMod(file.getFileName())) {
|
||||||
dependencies.add(new FileDownloadTask(file.getUrl(), modManager.getSimpleModPath(file.getFileName()).toFile())
|
FileDownloadTask task = new FileDownloadTask(file.getUrl(), modManager.getSimpleModPath(file.getFileName()).toFile());
|
||||||
.setCacheRepository(dependency.getCacheRepository())
|
task.setCacheRepository(dependency.getCacheRepository());
|
||||||
.setCaching(true).withCounter());
|
task.setCaching(true);
|
||||||
|
dependencies.add(task.withCounter());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
228
HMCLCore/src/main/java/org/jackhuang/hmcl/task/FetchTask.java
Normal file
228
HMCLCore/src/main/java/org/jackhuang/hmcl/task/FetchTask.java
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
/*
|
||||||
|
* Hello Minecraft! Launcher
|
||||||
|
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package org.jackhuang.hmcl.task;
|
||||||
|
|
||||||
|
import org.jackhuang.hmcl.event.Event;
|
||||||
|
import org.jackhuang.hmcl.event.EventBus;
|
||||||
|
import org.jackhuang.hmcl.util.CacheRepository;
|
||||||
|
import org.jackhuang.hmcl.util.Logging;
|
||||||
|
import org.jackhuang.hmcl.util.ToStringBuilder;
|
||||||
|
import org.jackhuang.hmcl.util.io.IOUtils;
|
||||||
|
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||||
|
import org.jackhuang.hmcl.util.io.ResponseCodeException;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLConnection;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
public abstract class FetchTask<T> extends Task<T> {
|
||||||
|
protected final List<URL> urls;
|
||||||
|
protected final int retry;
|
||||||
|
protected boolean caching;
|
||||||
|
protected CacheRepository repository = CacheRepository.getInstance();
|
||||||
|
|
||||||
|
public FetchTask(List<URL> urls, int retry) {
|
||||||
|
if (urls == null || urls.isEmpty())
|
||||||
|
throw new IllegalArgumentException("At least one URL is required");
|
||||||
|
|
||||||
|
this.urls = new ArrayList<>(urls);
|
||||||
|
this.retry = retry;
|
||||||
|
|
||||||
|
setExecutor(Schedulers.io());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCaching(boolean caching) {
|
||||||
|
this.caching = caching;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCacheRepository(CacheRepository repository) {
|
||||||
|
this.repository = repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void beforeDownload(URL url) throws IOException {}
|
||||||
|
|
||||||
|
protected abstract void useCachedResult(Path cachedFile) throws IOException;
|
||||||
|
|
||||||
|
protected abstract EnumCheckETag shouldCheckETag();
|
||||||
|
|
||||||
|
protected abstract Context getContext(URLConnection conn, boolean checkETag) throws IOException;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute() throws Exception {
|
||||||
|
Exception exception = null;
|
||||||
|
URL failedURL = null;
|
||||||
|
boolean checkETag;
|
||||||
|
switch (shouldCheckETag()) {
|
||||||
|
case CHECK_E_TAG: checkETag = true; break;
|
||||||
|
case NOT_CHECK_E_TAG: checkETag = false; break;
|
||||||
|
default: return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int repeat = 0;
|
||||||
|
download: for (URL url : urls) {
|
||||||
|
for (int retryTime = 0; retryTime < retry; retryTime++) {
|
||||||
|
if (isCancelled()) {
|
||||||
|
break download;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
beforeDownload(url);
|
||||||
|
|
||||||
|
updateProgress(0);
|
||||||
|
|
||||||
|
URLConnection conn = NetworkUtils.createConnection(url);
|
||||||
|
if (checkETag) repository.injectConnection(conn);
|
||||||
|
|
||||||
|
if (conn instanceof HttpURLConnection) {
|
||||||
|
conn = NetworkUtils.resolveConnection((HttpURLConnection) conn);
|
||||||
|
int responseCode = ((HttpURLConnection) conn).getResponseCode();
|
||||||
|
|
||||||
|
if (responseCode == HttpURLConnection.HTTP_NOT_MODIFIED) {
|
||||||
|
// Handle cache
|
||||||
|
try {
|
||||||
|
Path cache = repository.getCachedRemoteFile(conn);
|
||||||
|
useCachedResult(cache);
|
||||||
|
return;
|
||||||
|
} catch (IOException e) {
|
||||||
|
Logging.LOG.log(Level.WARNING, "Unable to use cached file, redownload " + url, e);
|
||||||
|
repository.removeRemoteEntry(conn);
|
||||||
|
// Now we must reconnect the server since 304 may result in empty content,
|
||||||
|
// if we want to redownload the file, we must reconnect the server without etag settings.
|
||||||
|
retryTime--;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else if (responseCode / 100 == 4) {
|
||||||
|
break; // we will not try this URL again
|
||||||
|
} else if (responseCode / 100 != 2) {
|
||||||
|
throw new ResponseCodeException(url, responseCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
long contentLength = conn.getContentLength();
|
||||||
|
try (Context context = getContext(conn, checkETag); InputStream stream = conn.getInputStream()) {
|
||||||
|
int lastDownloaded = 0, downloaded = 0;
|
||||||
|
byte[] buffer = new byte[IOUtils.DEFAULT_BUFFER_SIZE];
|
||||||
|
while (true) {
|
||||||
|
if (isCancelled()) break;
|
||||||
|
|
||||||
|
int len = stream.read(buffer);
|
||||||
|
if (len == -1) break;
|
||||||
|
|
||||||
|
context.write(buffer, 0, len);
|
||||||
|
|
||||||
|
downloaded += len;
|
||||||
|
|
||||||
|
if (contentLength >= 0) {
|
||||||
|
// Update progress information per second
|
||||||
|
updateProgress(downloaded, contentLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateDownloadSpeed(downloaded - lastDownloaded);
|
||||||
|
lastDownloaded = downloaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateDownloadSpeed(downloaded - lastDownloaded);
|
||||||
|
|
||||||
|
if (contentLength >= 0 && downloaded != contentLength)
|
||||||
|
throw new IOException("Unexpected file size: " + downloaded + ", expected: " + contentLength);
|
||||||
|
|
||||||
|
if (isCancelled()) break download;
|
||||||
|
|
||||||
|
context.withResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
} catch (IOException ex) {
|
||||||
|
failedURL = url;
|
||||||
|
exception = ex;
|
||||||
|
Logging.LOG.log(Level.WARNING, "Failed to download " + url + ", repeat times: " + (++repeat), ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exception != null)
|
||||||
|
throw new DownloadException(failedURL, exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Timer timer = new Timer("DownloadSpeedRecorder", true);
|
||||||
|
private static final AtomicInteger downloadSpeed = new AtomicInteger(0);
|
||||||
|
public static final EventBus speedEvent = new EventBus();
|
||||||
|
|
||||||
|
static {
|
||||||
|
timer.schedule(new TimerTask() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
speedEvent.channel(SpeedEvent.class).fireEvent(new SpeedEvent(speedEvent, downloadSpeed.getAndSet(0)));
|
||||||
|
}
|
||||||
|
}, 0, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void updateDownloadSpeed(int speed) {
|
||||||
|
downloadSpeed.addAndGet(speed);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class SpeedEvent extends Event {
|
||||||
|
private final int speed;
|
||||||
|
|
||||||
|
public SpeedEvent(Object source, int speed) {
|
||||||
|
super(source);
|
||||||
|
|
||||||
|
this.speed = speed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download speed in byte/sec.
|
||||||
|
* @return download speed
|
||||||
|
*/
|
||||||
|
public int getSpeed() {
|
||||||
|
return speed;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return new ToStringBuilder(this).append("speed", speed).toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static abstract class Context implements Closeable {
|
||||||
|
private boolean success;
|
||||||
|
|
||||||
|
public abstract void write(byte[] buffer, int offset, int len) throws IOException;
|
||||||
|
|
||||||
|
public final void withResult(boolean success) {
|
||||||
|
this.success = success;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean isSuccess() {
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected enum EnumCheckETag {
|
||||||
|
CHECK_E_TAG,
|
||||||
|
NOT_CHECK_E_TAG,
|
||||||
|
CACHED
|
||||||
|
}
|
||||||
|
}
|
@ -17,26 +17,23 @@
|
|||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.task;
|
package org.jackhuang.hmcl.task;
|
||||||
|
|
||||||
import org.jackhuang.hmcl.event.Event;
|
|
||||||
import org.jackhuang.hmcl.event.EventBus;
|
|
||||||
import org.jackhuang.hmcl.util.CacheRepository;
|
|
||||||
import org.jackhuang.hmcl.util.Logging;
|
import org.jackhuang.hmcl.util.Logging;
|
||||||
import org.jackhuang.hmcl.util.ToStringBuilder;
|
import org.jackhuang.hmcl.util.io.ChecksumMismatchException;
|
||||||
import org.jackhuang.hmcl.util.io.*;
|
import org.jackhuang.hmcl.util.io.CompressingUtils;
|
||||||
|
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.RandomAccessFile;
|
import java.io.RandomAccessFile;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.net.HttpURLConnection;
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.net.URLConnection;
|
||||||
import java.nio.file.FileSystem;
|
import java.nio.file.FileSystem;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
|
||||||
import static java.util.Objects.requireNonNull;
|
import static java.util.Objects.requireNonNull;
|
||||||
@ -47,7 +44,7 @@ import static org.jackhuang.hmcl.util.DigestUtils.getDigest;
|
|||||||
*
|
*
|
||||||
* @author huangyuhui
|
* @author huangyuhui
|
||||||
*/
|
*/
|
||||||
public class FileDownloadTask extends Task<Void> {
|
public class FileDownloadTask extends FetchTask<Void> {
|
||||||
|
|
||||||
public static class IntegrityCheck {
|
public static class IntegrityCheck {
|
||||||
private String algorithm;
|
private String algorithm;
|
||||||
@ -83,13 +80,9 @@ public class FileDownloadTask extends Task<Void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final List<URL> urls;
|
|
||||||
private final File file;
|
private final File file;
|
||||||
private final IntegrityCheck integrityCheck;
|
private final IntegrityCheck integrityCheck;
|
||||||
private final int retry;
|
|
||||||
private Path candidate;
|
private Path candidate;
|
||||||
private boolean caching;
|
|
||||||
private CacheRepository repository = CacheRepository.getInstance();
|
|
||||||
private RandomAccessFile rFile;
|
private RandomAccessFile rFile;
|
||||||
private InputStream stream;
|
private InputStream stream;
|
||||||
private final ArrayList<IntegrityCheckHandler> integrityCheckHandlers = new ArrayList<>();
|
private final ArrayList<IntegrityCheckHandler> integrityCheckHandlers = new ArrayList<>();
|
||||||
@ -148,35 +141,11 @@ public class FileDownloadTask extends Task<Void> {
|
|||||||
* @param retry the times for retrying if downloading fails.
|
* @param retry the times for retrying if downloading fails.
|
||||||
*/
|
*/
|
||||||
public FileDownloadTask(List<URL> urls, File file, IntegrityCheck integrityCheck, int retry) {
|
public FileDownloadTask(List<URL> urls, File file, IntegrityCheck integrityCheck, int retry) {
|
||||||
if (urls == null || urls.isEmpty())
|
super(urls, retry);
|
||||||
throw new IllegalArgumentException("At least one URL is required");
|
|
||||||
|
|
||||||
this.urls = new ArrayList<>(urls);
|
|
||||||
this.file = file;
|
this.file = file;
|
||||||
this.integrityCheck = integrityCheck;
|
this.integrityCheck = integrityCheck;
|
||||||
this.retry = retry;
|
|
||||||
|
|
||||||
setName(file.getName());
|
setName(file.getName());
|
||||||
setExecutor(Schedulers.io());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void closeFiles() {
|
|
||||||
if (rFile != null)
|
|
||||||
try {
|
|
||||||
rFile.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
Logging.LOG.log(Level.WARNING, "Failed to close file: " + rFile, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
rFile = null;
|
|
||||||
|
|
||||||
if (stream != null)
|
|
||||||
try {
|
|
||||||
stream.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
Logging.LOG.log(Level.WARNING, "Failed to close stream", e);
|
|
||||||
}
|
|
||||||
stream = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public File getFile() {
|
public File getFile() {
|
||||||
@ -188,130 +157,71 @@ public class FileDownloadTask extends Task<Void> {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public FileDownloadTask setCaching(boolean caching) {
|
|
||||||
this.caching = caching;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public FileDownloadTask setCacheRepository(CacheRepository repository) {
|
|
||||||
this.repository = repository;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addIntegrityCheckHandler(IntegrityCheckHandler handler) {
|
public void addIntegrityCheckHandler(IntegrityCheckHandler handler) {
|
||||||
integrityCheckHandlers.add(Objects.requireNonNull(handler));
|
integrityCheckHandlers.add(Objects.requireNonNull(handler));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute() throws Exception {
|
protected EnumCheckETag shouldCheckETag() {
|
||||||
boolean checkETag;
|
|
||||||
// Check cache
|
// Check cache
|
||||||
if (integrityCheck != null && caching) {
|
if (integrityCheck != null && caching) {
|
||||||
checkETag = false;
|
|
||||||
Optional<Path> cache = repository.checkExistentFile(candidate, integrityCheck.getAlgorithm(), integrityCheck.getChecksum());
|
Optional<Path> cache = repository.checkExistentFile(candidate, integrityCheck.getAlgorithm(), integrityCheck.getChecksum());
|
||||||
if (cache.isPresent()) {
|
if (cache.isPresent()) {
|
||||||
try {
|
try {
|
||||||
FileUtils.copyFile(cache.get().toFile(), file);
|
FileUtils.copyFile(cache.get().toFile(), file);
|
||||||
Logging.LOG.log(Level.FINER, "Successfully verified file " + file + " from " + urls.get(0));
|
Logging.LOG.log(Level.FINER, "Successfully verified file " + file + " from " + urls.get(0));
|
||||||
return;
|
return EnumCheckETag.CACHED;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Logging.LOG.log(Level.WARNING, "Failed to copy cache files", e);
|
Logging.LOG.log(Level.WARNING, "Failed to copy cache files", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return EnumCheckETag.NOT_CHECK_E_TAG;
|
||||||
} else {
|
} else {
|
||||||
checkETag = true;
|
return EnumCheckETag.CHECK_E_TAG;
|
||||||
}
|
}
|
||||||
|
|
||||||
Exception exception = null;
|
|
||||||
URL failedURL = null;
|
|
||||||
|
|
||||||
int repeat = 0;
|
|
||||||
download: for (URL url : urls) {
|
|
||||||
for (int retryTime = 0; retryTime < retry; retryTime++) {
|
|
||||||
if (isCancelled()) {
|
|
||||||
break download;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void beforeDownload(URL url) {
|
||||||
Logging.LOG.log(Level.FINER, "Downloading " + url + " to " + file);
|
Logging.LOG.log(Level.FINER, "Downloading " + url + " to " + file);
|
||||||
Path temp = null;
|
}
|
||||||
|
|
||||||
try {
|
@Override
|
||||||
updateProgress(0);
|
protected void useCachedResult(Path cache) throws IOException {
|
||||||
|
|
||||||
HttpURLConnection con = NetworkUtils.createConnection(url);
|
|
||||||
if (checkETag) repository.injectConnection(con);
|
|
||||||
con = NetworkUtils.resolveConnection(con);
|
|
||||||
|
|
||||||
if (con.getResponseCode() == HttpURLConnection.HTTP_NOT_MODIFIED) {
|
|
||||||
// Handle cache
|
|
||||||
try {
|
|
||||||
Path cache = repository.getCachedRemoteFile(con);
|
|
||||||
FileUtils.copyFile(cache.toFile(), file);
|
FileUtils.copyFile(cache.toFile(), file);
|
||||||
return;
|
|
||||||
} catch (IOException e) {
|
|
||||||
Logging.LOG.log(Level.WARNING, "Unable to use cached file, redownload it", e);
|
|
||||||
repository.removeRemoteEntry(con);
|
|
||||||
// Now we must reconnect the server since 304 may result in empty content,
|
|
||||||
// if we want to redownload the file, we must reconnect the server without etag settings.
|
|
||||||
retryTime--;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
} else if (con.getResponseCode() / 100 == 4) {
|
|
||||||
break; // we will not try this URL again
|
|
||||||
} else if (con.getResponseCode() / 100 != 2) {
|
|
||||||
throw new ResponseCodeException(url, con.getResponseCode());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int contentLength = con.getContentLength();
|
@Override
|
||||||
if (contentLength < 0)
|
protected Context getContext(URLConnection conn, boolean checkETag) throws IOException {
|
||||||
throw new IOException("The content length is invalid.");
|
Path temp = Files.createTempFile(null, null);
|
||||||
|
RandomAccessFile rFile = new RandomAccessFile(temp.toFile(), "rw");
|
||||||
if (!FileUtils.makeDirectory(file.getAbsoluteFile().getParentFile()))
|
|
||||||
throw new IOException("Could not make directory " + file.getAbsoluteFile().getParent());
|
|
||||||
|
|
||||||
temp = Files.createTempFile(null, null);
|
|
||||||
rFile = new RandomAccessFile(temp.toFile(), "rw");
|
|
||||||
|
|
||||||
MessageDigest digest = integrityCheck == null ? null : integrityCheck.createDigest();
|
MessageDigest digest = integrityCheck == null ? null : integrityCheck.createDigest();
|
||||||
|
|
||||||
stream = con.getInputStream();
|
return new Context() {
|
||||||
int lastDownloaded = 0, downloaded = 0;
|
@Override
|
||||||
byte[] buffer = new byte[IOUtils.DEFAULT_BUFFER_SIZE];
|
public void write(byte[] buffer, int offset, int len) throws IOException {
|
||||||
while (true) {
|
|
||||||
if (isCancelled()) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
int read = stream.read(buffer);
|
|
||||||
if (read == -1)
|
|
||||||
break;
|
|
||||||
|
|
||||||
if (digest != null) {
|
if (digest != null) {
|
||||||
digest.update(buffer, 0, read);
|
digest.update(buffer, offset, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write buffer to file.
|
rFile.write(buffer, offset, len);
|
||||||
rFile.write(buffer, 0, read);
|
|
||||||
downloaded += read;
|
|
||||||
|
|
||||||
// Update progress information per second
|
|
||||||
updateProgress(downloaded, contentLength);
|
|
||||||
|
|
||||||
updateDownloadSpeed(downloaded - lastDownloaded);
|
|
||||||
lastDownloaded = downloaded;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateDownloadSpeed(downloaded - lastDownloaded);
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
try {
|
||||||
|
rFile.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
Logging.LOG.log(Level.WARNING, "Failed to close file: " + rFile, e);
|
||||||
|
}
|
||||||
|
|
||||||
closeFiles();
|
if (!isSuccess()) {
|
||||||
|
try {
|
||||||
if (downloaded != contentLength)
|
Files.delete(temp);
|
||||||
throw new IOException("Unexpected file size: " + downloaded + ", expected: " + contentLength);
|
} catch (IOException e) {
|
||||||
|
Logging.LOG.log(Level.WARNING, "Failed to delete file: " + rFile, e);
|
||||||
// Restore temp file to original name.
|
}
|
||||||
if (isCancelled()) {
|
return;
|
||||||
temp.toFile().delete();
|
|
||||||
break download;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (IntegrityCheckHandler handler : integrityCheckHandlers) {
|
for (IntegrityCheckHandler handler : integrityCheckHandlers) {
|
||||||
@ -321,6 +231,7 @@ public class FileDownloadTask extends Task<Void> {
|
|||||||
Files.deleteIfExists(file.toPath());
|
Files.deleteIfExists(file.toPath());
|
||||||
if (!FileUtils.makeDirectory(file.getAbsoluteFile().getParentFile()))
|
if (!FileUtils.makeDirectory(file.getAbsoluteFile().getParentFile()))
|
||||||
throw new IOException("Unable to make parent directory " + file);
|
throw new IOException("Unable to make parent directory " + file);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
FileUtils.moveFile(temp.toFile(), file);
|
FileUtils.moveFile(temp.toFile(), file);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@ -341,64 +252,10 @@ public class FileDownloadTask extends Task<Void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (checkETag) {
|
if (checkETag) {
|
||||||
repository.cacheRemoteFile(file.toPath(), con);
|
repository.cacheRemoteFile(file.toPath(), conn);
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
} catch (IOException e) {
|
|
||||||
if (temp != null)
|
|
||||||
temp.toFile().delete();
|
|
||||||
failedURL = url;
|
|
||||||
exception = e;
|
|
||||||
Logging.LOG.log(Level.WARNING, "Failed to download " + url + ", repeat times: " + (++repeat), e);
|
|
||||||
} finally {
|
|
||||||
closeFiles();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
if (exception != null)
|
|
||||||
throw new DownloadException(failedURL, exception);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final Timer timer = new Timer("DownloadSpeedRecorder", true);
|
|
||||||
private static final AtomicInteger downloadSpeed = new AtomicInteger(0);
|
|
||||||
public static final EventBus speedEvent = new EventBus();
|
|
||||||
|
|
||||||
static {
|
|
||||||
timer.schedule(new TimerTask() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
speedEvent.channel(SpeedEvent.class).fireEvent(new SpeedEvent(speedEvent, downloadSpeed.getAndSet(0)));
|
|
||||||
}
|
|
||||||
}, 0, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void updateDownloadSpeed(int speed) {
|
|
||||||
downloadSpeed.addAndGet(speed);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class SpeedEvent extends Event {
|
|
||||||
private final int speed;
|
|
||||||
|
|
||||||
public SpeedEvent(Object source, int speed) {
|
|
||||||
super(source);
|
|
||||||
|
|
||||||
this.speed = speed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Download speed in byte/sec.
|
|
||||||
* @return download speed
|
|
||||||
*/
|
|
||||||
public int getSpeed() {
|
|
||||||
return speed;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return new ToStringBuilder(this).append("speed", speed).toString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IntegrityCheckHandler {
|
public interface IntegrityCheckHandler {
|
||||||
|
@ -17,23 +17,15 @@
|
|||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.task;
|
package org.jackhuang.hmcl.task;
|
||||||
|
|
||||||
import org.jackhuang.hmcl.util.CacheRepository;
|
|
||||||
import org.jackhuang.hmcl.util.Logging;
|
|
||||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||||
import org.jackhuang.hmcl.util.io.IOUtils;
|
|
||||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.net.HttpURLConnection;
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.net.URLConnection;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
|
|
||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
|
||||||
@ -41,12 +33,9 @@ import static java.nio.charset.StandardCharsets.UTF_8;
|
|||||||
*
|
*
|
||||||
* @author huangyuhui
|
* @author huangyuhui
|
||||||
*/
|
*/
|
||||||
public final class GetTask extends Task<String> {
|
public final class GetTask extends FetchTask<String> {
|
||||||
|
|
||||||
private final List<URL> urls;
|
|
||||||
private final Charset charset;
|
private final Charset charset;
|
||||||
private final int retry;
|
|
||||||
private CacheRepository repository = CacheRepository.getInstance();
|
|
||||||
|
|
||||||
public GetTask(URL url) {
|
public GetTask(URL url) {
|
||||||
this(url, UTF_8);
|
this(url, UTF_8);
|
||||||
@ -65,68 +54,35 @@ public final class GetTask extends Task<String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public GetTask(List<URL> urls, Charset charset, int retry) {
|
public GetTask(List<URL> urls, Charset charset, int retry) {
|
||||||
this.urls = new ArrayList<>(urls);
|
super(urls, retry);
|
||||||
this.charset = charset;
|
this.charset = charset;
|
||||||
this.retry = retry;
|
|
||||||
|
|
||||||
setName(urls.get(0).toString());
|
setName(urls.get(0).toString());
|
||||||
setExecutor(Schedulers.io());
|
|
||||||
}
|
|
||||||
|
|
||||||
public GetTask setCacheRepository(CacheRepository repository) {
|
|
||||||
this.repository = repository;
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute() throws Exception {
|
protected EnumCheckETag shouldCheckETag() {
|
||||||
Exception exception = null;
|
return EnumCheckETag.CHECK_E_TAG;
|
||||||
URL failedURL = null;
|
|
||||||
boolean checkETag = true;
|
|
||||||
for (int time = 0; time < retry * urls.size(); ++time) {
|
|
||||||
URL url = urls.get(time / retry);
|
|
||||||
if (isCancelled()) {
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
@Override
|
||||||
updateProgress(0);
|
protected void useCachedResult(Path cachedFile) throws IOException {
|
||||||
HttpURLConnection conn = NetworkUtils.createConnection(url);
|
setResult(FileUtils.readText(cachedFile));
|
||||||
if (checkETag) repository.injectConnection(conn);
|
|
||||||
conn = NetworkUtils.resolveConnection(conn);
|
|
||||||
|
|
||||||
if (conn.getResponseCode() == HttpURLConnection.HTTP_NOT_MODIFIED) {
|
|
||||||
// Handle cache
|
|
||||||
try {
|
|
||||||
Path cache = repository.getCachedRemoteFile(conn);
|
|
||||||
setResult(FileUtils.readText(cache));
|
|
||||||
return;
|
|
||||||
} catch (IOException e) {
|
|
||||||
Logging.LOG.log(Level.WARNING, "Unable to use cached file, redownload it", e);
|
|
||||||
repository.removeRemoteEntry(conn);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
} else if (conn.getResponseCode() / 100 != 2) {
|
|
||||||
throw new IOException("Server error, response code: " + conn.getResponseCode());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
InputStream input = conn.getInputStream();
|
@Override
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
protected Context getContext(URLConnection conn, boolean checkETag) {
|
||||||
byte[] buf = new byte[IOUtils.DEFAULT_BUFFER_SIZE];
|
return new Context() {
|
||||||
int size = conn.getContentLength(), read = 0, len;
|
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
while ((len = input.read(buf)) != -1) {
|
|
||||||
baos.write(buf, 0, len);
|
|
||||||
read += len;
|
|
||||||
|
|
||||||
if (size >= 0)
|
@Override
|
||||||
updateProgress(read, size);
|
public void write(byte[] buffer, int offset, int len) {
|
||||||
|
baos.write(buffer, offset, len);
|
||||||
if (Thread.currentThread().isInterrupted())
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (size > 0 && size != read)
|
@Override
|
||||||
throw new IOException("Not completed! Readed: " + read + ", total size: " + size);
|
public void close() throws IOException {
|
||||||
|
if (!isSuccess()) return;
|
||||||
|
|
||||||
String result = baos.toString(charset.name());
|
String result = baos.toString(charset.name());
|
||||||
setResult(result);
|
setResult(result);
|
||||||
@ -134,15 +90,8 @@ public final class GetTask extends Task<String> {
|
|||||||
if (checkETag) {
|
if (checkETag) {
|
||||||
repository.cacheText(result, conn);
|
repository.cacheText(result, conn);
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
} catch (IOException ex) {
|
|
||||||
failedURL = url;
|
|
||||||
exception = ex;
|
|
||||||
Logging.LOG.log(Level.WARNING, "Failed to download " + url + ", repeat times: " + (time + 1), ex);
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
if (exception != null)
|
|
||||||
throw new DownloadException(failedURL, exception);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -310,7 +310,7 @@ public abstract class Task<T> {
|
|||||||
return progress.getReadOnlyProperty();
|
return progress.getReadOnlyProperty();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void updateProgress(int progress, int total) {
|
protected void updateProgress(long progress, long total) {
|
||||||
updateProgress(1.0 * progress / total);
|
updateProgress(1.0 * progress / total);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,14 +54,18 @@ public final class NetworkUtils {
|
|||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static HttpURLConnection createConnection(URL url) throws IOException {
|
public static URLConnection createConnection(URL url) throws IOException {
|
||||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
URLConnection connection = url.openConnection();
|
||||||
connection.setUseCaches(false);
|
connection.setUseCaches(false);
|
||||||
connection.setConnectTimeout(15000);
|
connection.setConnectTimeout(15000);
|
||||||
connection.setReadTimeout(15000);
|
connection.setReadTimeout(15000);
|
||||||
return connection;
|
return connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static HttpURLConnection createHttpConnection(URL url) throws IOException {
|
||||||
|
return (HttpURLConnection) createConnection(url);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see <a href="https://github.com/curl/curl/blob/3f7b1bb89f92c13e69ee51b710ac54f775aab320/lib/transfer.c#L1427-L1461">Curl</a>
|
* @see <a href="https://github.com/curl/curl/blob/3f7b1bb89f92c13e69ee51b710ac54f775aab320/lib/transfer.c#L1427-L1461">Curl</a>
|
||||||
* @param location the url to be URL encoded
|
* @param location the url to be URL encoded
|
||||||
@ -129,7 +133,7 @@ public final class NetworkUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static String doGet(URL url) throws IOException {
|
public static String doGet(URL url) throws IOException {
|
||||||
HttpURLConnection con = createConnection(url);
|
HttpURLConnection con = createHttpConnection(url);
|
||||||
con = resolveConnection(con);
|
con = resolveConnection(con);
|
||||||
return IOUtils.readFullyAsString(con.getInputStream());
|
return IOUtils.readFullyAsString(con.getInputStream());
|
||||||
}
|
}
|
||||||
@ -151,7 +155,7 @@ public final class NetworkUtils {
|
|||||||
public static String doPost(URL url, String post, String contentType) throws IOException {
|
public static String doPost(URL url, String post, String contentType) throws IOException {
|
||||||
byte[] bytes = post.getBytes(UTF_8);
|
byte[] bytes = post.getBytes(UTF_8);
|
||||||
|
|
||||||
HttpURLConnection con = createConnection(url);
|
HttpURLConnection con = createHttpConnection(url);
|
||||||
con.setRequestMethod("POST");
|
con.setRequestMethod("POST");
|
||||||
con.setDoOutput(true);
|
con.setDoOutput(true);
|
||||||
con.setRequestProperty("Content-Type", contentType + "; charset=utf-8");
|
con.setRequestProperty("Content-Type", contentType + "; charset=utf-8");
|
||||||
@ -176,7 +180,7 @@ public final class NetworkUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static String detectFileName(URL url) throws IOException {
|
public static String detectFileName(URL url) throws IOException {
|
||||||
HttpURLConnection conn = resolveConnection(createConnection(url));
|
HttpURLConnection conn = resolveConnection(createHttpConnection(url));
|
||||||
int code = conn.getResponseCode();
|
int code = conn.getResponseCode();
|
||||||
if (code / 100 == 4)
|
if (code / 100 == 4)
|
||||||
throw new FileNotFoundException();
|
throw new FileNotFoundException();
|
||||||
@ -214,7 +218,7 @@ public final class NetworkUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static boolean urlExists(URL url) throws IOException {
|
public static boolean urlExists(URL url) throws IOException {
|
||||||
HttpURLConnection con = createConnection(url);
|
HttpURLConnection con = createHttpConnection(url);
|
||||||
con = resolveConnection(con);
|
con = resolveConnection(con);
|
||||||
int responseCode = con.getResponseCode();
|
int responseCode = con.getResponseCode();
|
||||||
con.disconnect();
|
con.disconnect();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user