NewPipe/app/src/main/java/us/shandian/giga/get/DownloadRunnable.java

181 lines
5.6 KiB
Java

package us.shandian.giga.get;
import android.util.Log;
import org.schabi.newpipe.streams.io.SharpStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.nio.channels.ClosedByInterruptException;
import us.shandian.giga.get.DownloadMission.Block;
import static org.schabi.newpipe.BuildConfig.DEBUG;
/**
* Runnable to download blocks of a file until the file is completely downloaded,
* an error occurs or the process is stopped.
*/
public class DownloadRunnable extends Thread {
private static final String TAG = DownloadRunnable.class.getSimpleName();
private final DownloadMission mMission;
private final int mId;
private HttpURLConnection mConn;
DownloadRunnable(DownloadMission mission, int id) {
if (mission == null) throw new NullPointerException("mission is null");
mMission = mission;
mId = id;
}
private void releaseBlock(Block block, long remain) {
// set the block offset to -1 if it is completed
mMission.releaseBlock(block.position, remain < 0 ? -1 : block.done);
}
@Override
public void run() {
boolean retry = false;
Block block = null;
int retryCount = 0;
if (DEBUG) {
Log.d(TAG, mId + ":recovered: " + mMission.recovered);
}
SharpStream f;
try {
f = mMission.storage.getStream();
} catch (IOException e) {
mMission.notifyError(e);// this never should happen
return;
}
while (mMission.running && mMission.errCode == DownloadMission.ERROR_NOTHING) {
if (!retry) {
block = mMission.acquireBlock();
}
if (block == null) {
if (DEBUG) Log.d(TAG, mId + ":no more blocks left, exiting");
break;
}
if (DEBUG) {
if (retry)
Log.d(TAG, mId + ":retry block at position=" + block.position + " from the start");
else
Log.d(TAG, mId + ":acquired block at position=" + block.position + " done=" + block.done);
}
long start = block.position * DownloadMission.BLOCK_SIZE;
long end = start + DownloadMission.BLOCK_SIZE - 1;
start += block.done;
if (end >= mMission.length) {
end = mMission.length - 1;
}
try {
mConn = mMission.openConnection(mId, start, end);
mMission.establishConnection(mId, mConn);
// check if the download can be resumed
if (mConn.getResponseCode() == 416) {
if (block.done > 0) {
// try again from the start (of the block)
block.done = 0;
retry = true;
mConn.disconnect();
continue;
}
throw new DownloadMission.HttpError(416);
}
retry = false;
// The server may be ignoring the range request
if (mConn.getResponseCode() != 206) {
if (DEBUG) {
Log.e(TAG, mId + ":Unsupported " + mConn.getResponseCode());
}
mMission.notifyError(new DownloadMission.HttpError(mConn.getResponseCode()));
break;
}
f.seek(mMission.offsets[mMission.current] + start);
try (InputStream is = mConn.getInputStream()) {
byte[] buf = new byte[DownloadMission.BUFFER_SIZE];
int len;
// use always start <= end
// fixes a deadlock in DownloadRunnable because youtube is sending one byte alone after downloading 26MiB exactly
while (start <= end && mMission.running && (len = is.read(buf, 0, buf.length)) != -1) {
f.write(buf, 0, len);
start += len;
block.done += len;
mMission.notifyProgress(len);
}
}
if (DEBUG && mMission.running) {
Log.d(TAG, mId + ":position " + block.position + " stopped " + start + "/" + end);
}
} catch (Exception e) {
if (!mMission.running || e instanceof ClosedByInterruptException) break;
if (retryCount++ >= mMission.maxRetry) {
mMission.notifyError(e);
break;
}
retry = true;
} finally {
if (!retry) releaseBlock(block, end - start);
}
}
try {
f.close();
} catch (Exception err) {
// ¿ejected media storage? ¿file deleted? ¿storage ran out of space?
}
if (DEBUG) {
Log.d(TAG, "thread " + mId + " exited from main download loop");
}
if (mMission.errCode == DownloadMission.ERROR_NOTHING && mMission.running) {
if (DEBUG) {
Log.d(TAG, "no error has happened, notifying");
}
mMission.notifyFinished();
}
if (DEBUG && !mMission.running) {
Log.d(TAG, "The mission has been paused. Passing.");
}
}
@Override
public void interrupt() {
super.interrupt();
try {
if (mConn != null) mConn.disconnect();
} catch (Exception e) {
// nothing to do
}
}
}