package us.shandian.giga.get; import android.util.Log; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.extractor.stream.AudioStream; import org.schabi.newpipe.extractor.stream.StreamExtractor; import org.schabi.newpipe.extractor.stream.SubtitlesStream; import org.schabi.newpipe.extractor.stream.VideoStream; import java.io.IOException; import java.net.HttpURLConnection; import java.nio.channels.ClosedByInterruptException; import java.util.List; import static us.shandian.giga.get.DownloadMission.ERROR_RESOURCE_GONE; public class DownloadMissionRecover extends Thread { private static final String TAG = "DownloadMissionRecover"; static final int mID = -3; private final DownloadMission mMission; private final MissionRecoveryInfo mRecovery; private final Exception mFromError; private HttpURLConnection mConn; DownloadMissionRecover(DownloadMission mission, Exception originError) { mMission = mission; mFromError = originError; mRecovery = mission.recoveryInfo[mission.current]; } @Override public void run() { if (mMission.source == null) { mMission.notifyError(mFromError); return; } try { /*if (mMission.source.startsWith(MissionRecoveryInfo.DIRECT_SOURCE)) { resolve(mMission.source.substring(MissionRecoveryInfo.DIRECT_SOURCE.length())); return; }*/ StreamingService svr = NewPipe.getServiceByUrl(mMission.source); if (svr == null) { throw new RuntimeException("Unknown source service"); } StreamExtractor extractor = svr.getStreamExtractor(mMission.source); extractor.fetchPage(); if (!mMission.running || super.isInterrupted()) return; String url = null; switch (mMission.kind) { case 'a': for (AudioStream audio : extractor.getAudioStreams()) { if (audio.average_bitrate == mRecovery.desiredBitrate && audio.getFormat() == mRecovery.format) { url = audio.getUrl(); break; } } break; case 'v': List videoStreams; if (mRecovery.desired2) videoStreams = extractor.getVideoOnlyStreams(); else videoStreams = extractor.getVideoStreams(); for (VideoStream video : videoStreams) { if (video.resolution.equals(mRecovery.desired) && video.getFormat() == mRecovery.format) { url = video.getUrl(); break; } } break; case 's': for (SubtitlesStream subtitles : extractor.getSubtitles(mRecovery.format)) { String tag = subtitles.getLanguageTag(); if (tag.equals(mRecovery.desired) && subtitles.isAutoGenerated() == mRecovery.desired2) { url = subtitles.getURL(); break; } } break; default: throw new RuntimeException("Unknown stream type"); } resolve(url); } catch (Exception e) { if (!mMission.running || e instanceof ClosedByInterruptException) return; mRecovery.attempts++; mMission.notifyError(e); } } private void resolve(String url) throws IOException, DownloadMission.HttpError { if (mRecovery.validateCondition == null) { Log.w(TAG, "validation condition not defined, the resource can be stale"); } if (mMission.unknownLength || mRecovery.validateCondition == null) { recover(url, false); return; } /////////////////////////////////////////////////////////////////////// ////// Validate the http resource doing a range request ///////////////////// try { mConn = mMission.openConnection(url, mID, mMission.length - 10, mMission.length); mConn.setRequestProperty("If-Range", mRecovery.validateCondition); mMission.establishConnection(mID, mConn); int code = mConn.getResponseCode(); switch (code) { case 200: case 413: // stale recover(url, true); return; case 206: // in case of validation using the Last-Modified date, check the resource length long[] contentRange = parseContentRange(mConn.getHeaderField("Content-Range")); boolean lengthMismatch = contentRange[2] != -1 && contentRange[2] != mMission.length; recover(url, lengthMismatch); return; } throw new DownloadMission.HttpError(code); } catch (Exception e) { if (!mMission.running || e instanceof ClosedByInterruptException) return; throw e; } finally { this.interrupt(); } } private void recover(String url, boolean stale) { Log.i(TAG, String.format("download recovered name=%s isStale=%s url=%s", mMission.storage.getName(), stale, url) ); if (url == null) { mMission.notifyError(ERROR_RESOURCE_GONE, null); return; } mMission.urls[mMission.current] = url; mRecovery.attempts = 0; if (stale) { mMission.resetState(false, false, DownloadMission.ERROR_NOTHING); } mMission.writeThisToFile(); if (!mMission.running || super.isInterrupted()) return; mMission.running = false; mMission.start(); } private long[] parseContentRange(String value) { long[] range = new long[3]; if (value == null) { // this never should happen return range; } try { value = value.trim(); if (!value.startsWith("bytes")) { return range;// unknown range type } int space = value.lastIndexOf(' ') + 1; int dash = value.indexOf('-', space) + 1; int bar = value.indexOf('/', dash); // start range[0] = Long.parseLong(value.substring(space, dash - 1)); // end range[1] = Long.parseLong(value.substring(dash, bar)); // resource length value = value.substring(bar + 1); if (value.equals("*")) { range[2] = -1;// unknown length received from the server but should be valid } else { range[2] = Long.parseLong(value); } } catch (Exception e) { // nothing to do } return range; } @Override public void interrupt() { super.interrupt(); if (mConn != null) { try { mConn.disconnect(); } catch (Exception e) { // nothing to do } } } }