mirror of
https://github.com/AngelAuraMC/Amethyst-Android.git
synced 2025-09-10 13:16:04 -04:00
Feat[downloader]: downloader improvements (#6428)
- Add download size queries through a HEAD request - Use the file size for progress instead of file count when all file size are available - Add download speed meter
This commit is contained in:
parent
99c8ea2bfd
commit
5d80d9baec
@ -43,7 +43,7 @@ public class DownloadMirror {
|
|||||||
return;
|
return;
|
||||||
}catch (FileNotFoundException e) {
|
}catch (FileNotFoundException e) {
|
||||||
Log.w("DownloadMirror", "Cannot find the file on the mirror", e);
|
Log.w("DownloadMirror", "Cannot find the file on the mirror", e);
|
||||||
Log.i("DownloadMirror", "Failling back to default source");
|
Log.i("DownloadMirror", "Falling back to default source");
|
||||||
}
|
}
|
||||||
DownloadUtils.downloadFileMonitored(urlInput, outputFile, buffer, monitor);
|
DownloadUtils.downloadFileMonitored(urlInput, outputFile, buffer, monitor);
|
||||||
}
|
}
|
||||||
@ -63,11 +63,30 @@ public class DownloadMirror {
|
|||||||
return;
|
return;
|
||||||
}catch (FileNotFoundException e) {
|
}catch (FileNotFoundException e) {
|
||||||
Log.w("DownloadMirror", "Cannot find the file on the mirror", e);
|
Log.w("DownloadMirror", "Cannot find the file on the mirror", e);
|
||||||
Log.i("DownloadMirror", "Failling back to default source");
|
Log.i("DownloadMirror", "Falling back to default source");
|
||||||
}
|
}
|
||||||
DownloadUtils.downloadFile(urlInput, outputFile);
|
DownloadUtils.downloadFile(urlInput, outputFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the content length of a file on the current mirror. If the file is missing on the mirror,
|
||||||
|
* or the mirror does not give out the length, request the length from the original source
|
||||||
|
* @param downloadClass Class of the download. Can either be DOWNLOAD_CLASS_LIBRARIES,
|
||||||
|
* DOWNLOAD_CLASS_METADATA or DOWNLOAD_CLASS_ASSETS
|
||||||
|
* @param urlInput The original (Mojang) URL for the download
|
||||||
|
* @return the length of the file denoted by the URL in bytes, or -1 if not available
|
||||||
|
*/
|
||||||
|
public static long getContentLengthMirrored(int downloadClass, String urlInput) throws IOException {
|
||||||
|
long length = DownloadUtils.getContentLength(getMirrorMapping(downloadClass, urlInput));
|
||||||
|
if(length < 1) {
|
||||||
|
Log.w("DownloadMirror", "Unable to get content length from mirror");
|
||||||
|
Log.i("DownloadMirror", "Falling back to default source");
|
||||||
|
return DownloadUtils.getContentLength(urlInput);
|
||||||
|
}else {
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the current download source is a mirror and not an official source.
|
* Check if the current download source is a mirror and not an official source.
|
||||||
* @return true if the source is a mirror, false otherwise
|
* @return true if the source is a mirror, false otherwise
|
||||||
|
@ -37,14 +37,18 @@ import java.util.concurrent.atomic.AtomicLong;
|
|||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
public class MinecraftDownloader {
|
public class MinecraftDownloader {
|
||||||
|
private static final double ONE_MEGABYTE = (1024d * 1024d);
|
||||||
public static final String MINECRAFT_RES = "https://resources.download.minecraft.net/";
|
public static final String MINECRAFT_RES = "https://resources.download.minecraft.net/";
|
||||||
private AtomicReference<Exception> mDownloaderThreadException;
|
private AtomicReference<Exception> mDownloaderThreadException;
|
||||||
private ArrayList<DownloaderTask> mScheduledDownloadTasks;
|
private ArrayList<DownloaderTask> mScheduledDownloadTasks;
|
||||||
private AtomicLong mDownloadFileCounter;
|
private AtomicLong mProcessedFileCounter;
|
||||||
private AtomicLong mDownloadSizeCounter;
|
private AtomicLong mProcessedSizeCounter; // Total bytes of processed files (passed SHA1 or downloaded)
|
||||||
private long mDownloadFileCount;
|
private AtomicLong mInternetUsageCounter; // How many bytes downloaded over Internet
|
||||||
|
private long mTotalFileCount;
|
||||||
|
private long mTotalSize;
|
||||||
private File mSourceJarFile; // The source client JAR picked during the inheritance process
|
private File mSourceJarFile; // The source client JAR picked during the inheritance process
|
||||||
private File mTargetJarFile; // The destination client JAR to which the source will be copied to.
|
private File mTargetJarFile; // The destination client JAR to which the source will be copied to.
|
||||||
|
private boolean mUseFileCounter; // Whether a file counter or a size counter should be used for progress
|
||||||
|
|
||||||
private static final ThreadLocal<byte[]> sThreadLocalDownloadBuffer = new ThreadLocal<>();
|
private static final ThreadLocal<byte[]> sThreadLocalDownloadBuffer = new ThreadLocal<>();
|
||||||
|
|
||||||
@ -80,12 +84,15 @@ public class MinecraftDownloader {
|
|||||||
// Put up a dummy progress line, for the activity to start the service and do all the other necessary
|
// Put up a dummy progress line, for the activity to start the service and do all the other necessary
|
||||||
// work to keep the launcher alive. We will replace this line when we will start downloading stuff.
|
// work to keep the launcher alive. We will replace this line when we will start downloading stuff.
|
||||||
ProgressLayout.setProgress(ProgressLayout.DOWNLOAD_MINECRAFT, 0, R.string.newdl_starting);
|
ProgressLayout.setProgress(ProgressLayout.DOWNLOAD_MINECRAFT, 0, R.string.newdl_starting);
|
||||||
|
SpeedCalculator speedCalculator = new SpeedCalculator();
|
||||||
|
|
||||||
mTargetJarFile = createGameJarPath(versionName);
|
mTargetJarFile = createGameJarPath(versionName);
|
||||||
mScheduledDownloadTasks = new ArrayList<>();
|
mScheduledDownloadTasks = new ArrayList<>();
|
||||||
mDownloadFileCounter = new AtomicLong(0);
|
mProcessedFileCounter = new AtomicLong(0);
|
||||||
mDownloadSizeCounter = new AtomicLong(0);
|
mProcessedSizeCounter = new AtomicLong(0);
|
||||||
|
mInternetUsageCounter = new AtomicLong(0);
|
||||||
mDownloaderThreadException = new AtomicReference<>(null);
|
mDownloaderThreadException = new AtomicReference<>(null);
|
||||||
|
mUseFileCounter = false;
|
||||||
|
|
||||||
if(!downloadAndProcessMetadata(activity, verInfo, versionName)) {
|
if(!downloadAndProcessMetadata(activity, verInfo, versionName)) {
|
||||||
throw new RuntimeException(activity.getString(R.string.exception_failed_to_unpack_jre17));
|
throw new RuntimeException(activity.getString(R.string.exception_failed_to_unpack_jre17));
|
||||||
@ -104,11 +111,9 @@ public class MinecraftDownloader {
|
|||||||
try {
|
try {
|
||||||
while (mDownloaderThreadException.get() == null &&
|
while (mDownloaderThreadException.get() == null &&
|
||||||
!downloaderPool.awaitTermination(33, TimeUnit.MILLISECONDS)) {
|
!downloaderPool.awaitTermination(33, TimeUnit.MILLISECONDS)) {
|
||||||
long dlFileCounter = mDownloadFileCounter.get();
|
double speed = speedCalculator.feed(mInternetUsageCounter.get()) / ONE_MEGABYTE;
|
||||||
int progress = (int)((dlFileCounter * 100L) / mDownloadFileCount);
|
if(mUseFileCounter) reportProgressFileCounter(speed);
|
||||||
ProgressLayout.setProgress(ProgressLayout.DOWNLOAD_MINECRAFT, progress,
|
else reportProgressSizeCounter(speed);
|
||||||
R.string.newdl_downloading_game_files, dlFileCounter,
|
|
||||||
mDownloadFileCount, (double)mDownloadSizeCounter.get() / (1024d * 1024d));
|
|
||||||
}
|
}
|
||||||
Exception thrownException = mDownloaderThreadException.get();
|
Exception thrownException = mDownloaderThreadException.get();
|
||||||
if(thrownException != null) {
|
if(thrownException != null) {
|
||||||
@ -123,6 +128,23 @@ public class MinecraftDownloader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void reportProgressFileCounter(double speed) {
|
||||||
|
long dlFileCounter = mProcessedFileCounter.get();
|
||||||
|
int progress = (int)((dlFileCounter * 100L) / mTotalFileCount);
|
||||||
|
ProgressLayout.setProgress(ProgressLayout.DOWNLOAD_MINECRAFT, progress,
|
||||||
|
R.string.newdl_downloading_game_files, dlFileCounter,
|
||||||
|
mTotalFileCount, speed);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reportProgressSizeCounter(double speed) {
|
||||||
|
long dlFileSize = mProcessedSizeCounter.get();
|
||||||
|
double dlSizeMegabytes = (double) dlFileSize / ONE_MEGABYTE;
|
||||||
|
double dlTotalMegabytes = (double) mTotalSize / ONE_MEGABYTE;
|
||||||
|
int progress = (int)((dlFileSize * 100L) / mTotalSize);
|
||||||
|
ProgressLayout.setProgress(ProgressLayout.DOWNLOAD_MINECRAFT, progress,
|
||||||
|
R.string.newdl_downloading_game_files_size, dlSizeMegabytes, dlTotalMegabytes, speed);
|
||||||
|
}
|
||||||
|
|
||||||
private File createGameJsonPath(String versionId) {
|
private File createGameJsonPath(String versionId) {
|
||||||
return new File(Tools.DIR_HOME_VERSION, versionId + File.separator + versionId + ".json");
|
return new File(Tools.DIR_HOME_VERSION, versionId + File.separator + versionId + ".json");
|
||||||
}
|
}
|
||||||
@ -233,7 +255,19 @@ public class MinecraftDownloader {
|
|||||||
private void scheduleDownload(File targetFile, int downloadClass, String url, String sha1,
|
private void scheduleDownload(File targetFile, int downloadClass, String url, String sha1,
|
||||||
long size, boolean skipIfFailed) throws IOException {
|
long size, boolean skipIfFailed) throws IOException {
|
||||||
FileUtils.ensureParentDirectory(targetFile);
|
FileUtils.ensureParentDirectory(targetFile);
|
||||||
mDownloadFileCount++;
|
mTotalFileCount++;
|
||||||
|
if(size < 0) {
|
||||||
|
size = DownloadMirror.getContentLengthMirrored(downloadClass, url);
|
||||||
|
}
|
||||||
|
if(size < 0) {
|
||||||
|
// If we were unable to get the content length ourselves, we automatically fall back
|
||||||
|
// to tracking the progress using the file counter.
|
||||||
|
size = 0;
|
||||||
|
mUseFileCounter = true;
|
||||||
|
Log.i("MinecraftDownloader", "Failed to determine size of "+targetFile.getName()+", switching to file counter");
|
||||||
|
}else {
|
||||||
|
mTotalSize += size;
|
||||||
|
}
|
||||||
mScheduledDownloadTasks.add(
|
mScheduledDownloadTasks.add(
|
||||||
new DownloaderTask(targetFile, downloadClass, url, sha1, size, skipIfFailed)
|
new DownloaderTask(targetFile, downloadClass, url, sha1, size, skipIfFailed)
|
||||||
);
|
);
|
||||||
@ -401,18 +435,20 @@ public class MinecraftDownloader {
|
|||||||
}catch (Exception e) {
|
}catch (Exception e) {
|
||||||
if(!mSkipIfFailed) throw e;
|
if(!mSkipIfFailed) throw e;
|
||||||
}
|
}
|
||||||
mDownloadFileCounter.incrementAndGet();
|
mProcessedFileCounter.incrementAndGet();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void finishWithoutDownloading() {
|
private void finishWithoutDownloading() {
|
||||||
mDownloadFileCounter.incrementAndGet();
|
mProcessedFileCounter.incrementAndGet();
|
||||||
mDownloadSizeCounter.addAndGet(mDownloadSize);
|
mProcessedSizeCounter.addAndGet(mDownloadSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateProgress(int curr, int max) {
|
public void updateProgress(int curr, int max) {
|
||||||
mDownloadSizeCounter.addAndGet(curr - mLastCurr);
|
int delta = curr - mLastCurr;
|
||||||
mLastCurr = curr;
|
mProcessedSizeCounter.addAndGet(delta);
|
||||||
|
mInternetUsageCounter.addAndGet(delta);
|
||||||
|
mLastCurr = curr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,44 @@
|
|||||||
|
package net.kdt.pojavlaunch.tasks;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple class to calculate the average Internet speed using a simple moving average.
|
||||||
|
*/
|
||||||
|
public class SpeedCalculator {
|
||||||
|
private long mLastMillis;
|
||||||
|
private long mLastBytes;
|
||||||
|
private int mIndex;
|
||||||
|
private final double[] mPreviousInputs;
|
||||||
|
private double mSum;
|
||||||
|
|
||||||
|
public SpeedCalculator() {
|
||||||
|
this(64);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SpeedCalculator(int averageDepth) {
|
||||||
|
mPreviousInputs = new double[averageDepth];
|
||||||
|
}
|
||||||
|
|
||||||
|
private double addToAverage(double speed) {
|
||||||
|
mSum -= mPreviousInputs[mIndex];
|
||||||
|
mSum += speed;
|
||||||
|
mPreviousInputs[mIndex] = speed;
|
||||||
|
if(++mIndex == mPreviousInputs.length) mIndex = 0;
|
||||||
|
double dLength = mPreviousInputs.length;
|
||||||
|
return (mSum + (dLength / 2d)) / dLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the current amount of bytes downloaded.
|
||||||
|
* @param bytes the new amount of bytes downloaded
|
||||||
|
* @return the current download speed in bytes per second
|
||||||
|
*/
|
||||||
|
public double feed(long bytes) {
|
||||||
|
long millis = System.currentTimeMillis();
|
||||||
|
long deltaBytes = bytes - mLastBytes;
|
||||||
|
long deltaMillis = millis - mLastMillis;
|
||||||
|
mLastBytes = bytes;
|
||||||
|
mLastMillis = millis;
|
||||||
|
double speed = (double)deltaBytes / ((double)deltaMillis / 1000d);
|
||||||
|
return addToAverage(speed);
|
||||||
|
}
|
||||||
|
}
|
@ -155,6 +155,23 @@ public class DownloadUtils {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the content length for a given URL.
|
||||||
|
* @param url the URL to get the length for
|
||||||
|
* @return the length in bytes or -1 if not available
|
||||||
|
* @throws IOException if an I/O error occurs.
|
||||||
|
*/
|
||||||
|
public static long getContentLength(String url) throws IOException {
|
||||||
|
HttpURLConnection urlConnection = (HttpURLConnection) new URL(url).openConnection();
|
||||||
|
urlConnection.setRequestMethod("HEAD");
|
||||||
|
urlConnection.setDoInput(false);
|
||||||
|
urlConnection.setDoOutput(false);
|
||||||
|
urlConnection.connect();
|
||||||
|
int responseCode = urlConnection.getResponseCode();
|
||||||
|
if(responseCode >= 200 && responseCode <= 299) return urlConnection.getContentLength();
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
public interface ParseCallback<T> {
|
public interface ParseCallback<T> {
|
||||||
T process(String input) throws ParseException;
|
T process(String input) throws ParseException;
|
||||||
}
|
}
|
||||||
|
@ -374,7 +374,8 @@
|
|||||||
<string name="exception_failed_to_unpack_jre17">Failed to install JRE 17</string>
|
<string name="exception_failed_to_unpack_jre17">Failed to install JRE 17</string>
|
||||||
<string name="newdl_starting">Reading game metadata…</string>
|
<string name="newdl_starting">Reading game metadata…</string>
|
||||||
<string name="newdl_downloading_metadata">Downloading game metadata (%s)</string>
|
<string name="newdl_downloading_metadata">Downloading game metadata (%s)</string>
|
||||||
<string name="newdl_downloading_game_files">Downloading game files… (%d/%d, %.2f MB)</string>
|
<string name="newdl_downloading_game_files">Downloading game… (%d/%d, %.2f MB/s)</string>
|
||||||
|
<string name="newdl_downloading_game_files_size">Downloading game… (%.2f/%.2f MB, %.2f MB/s)</string>
|
||||||
<string name="cropper_title">Select image region</string>
|
<string name="cropper_title">Select image region</string>
|
||||||
<string name="cropper_lock_vertical">V. lock</string>
|
<string name="cropper_lock_vertical">V. lock</string>
|
||||||
<string name="cropper_lock_horizontal">H. lock</string>
|
<string name="cropper_lock_horizontal">H. lock</string>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user