package zlc.season.rxdownload2.entity; import org.reactivestreams.Publisher; import org.reactivestreams.Subscription; import java.io.IOException; import java.text.ParseException; import java.util.ArrayList; import java.util.List; import io.reactivex.BackpressureStrategy; import io.reactivex.Flowable; import io.reactivex.FlowableEmitter; import io.reactivex.FlowableOnSubscribe; import io.reactivex.Observable; import io.reactivex.functions.Action; import io.reactivex.functions.Consumer; import io.reactivex.functions.Function; import io.reactivex.schedulers.Schedulers; import okhttp3.ResponseBody; import retrofit2.Response; import zlc.season.rxdownload2.function.Utils; import static zlc.season.rxdownload2.function.Constant.ALREADY_DOWNLOAD_HINT; import static zlc.season.rxdownload2.function.Constant.CONTINUE_DOWNLOAD_CANCEL; import static zlc.season.rxdownload2.function.Constant.CONTINUE_DOWNLOAD_COMPLETED; import static zlc.season.rxdownload2.function.Constant.CONTINUE_DOWNLOAD_FAILED; import static zlc.season.rxdownload2.function.Constant.CONTINUE_DOWNLOAD_FINISH; import static zlc.season.rxdownload2.function.Constant.CONTINUE_DOWNLOAD_PREPARE; import static zlc.season.rxdownload2.function.Constant.CONTINUE_DOWNLOAD_STARTED; import static zlc.season.rxdownload2.function.Constant.MULTITHREADING_DOWNLOAD_CANCEL; import static zlc.season.rxdownload2.function.Constant.MULTITHREADING_DOWNLOAD_COMPLETED; import static zlc.season.rxdownload2.function.Constant.MULTITHREADING_DOWNLOAD_FAILED; import static zlc.season.rxdownload2.function.Constant.MULTITHREADING_DOWNLOAD_FINISH; import static zlc.season.rxdownload2.function.Constant.MULTITHREADING_DOWNLOAD_PREPARE; import static zlc.season.rxdownload2.function.Constant.MULTITHREADING_DOWNLOAD_STARTED; import static zlc.season.rxdownload2.function.Constant.NORMAL_DOWNLOAD_CANCEL; import static zlc.season.rxdownload2.function.Constant.NORMAL_DOWNLOAD_COMPLETED; import static zlc.season.rxdownload2.function.Constant.NORMAL_DOWNLOAD_FAILED; import static zlc.season.rxdownload2.function.Constant.NORMAL_DOWNLOAD_FINISH; import static zlc.season.rxdownload2.function.Constant.NORMAL_DOWNLOAD_PREPARE; import static zlc.season.rxdownload2.function.Constant.NORMAL_DOWNLOAD_STARTED; import static zlc.season.rxdownload2.function.Constant.NORMAL_RETRY_HINT; import static zlc.season.rxdownload2.function.Constant.RANGE_RETRY_HINT; import static zlc.season.rxdownload2.function.Utils.formatStr; import static zlc.season.rxdownload2.function.Utils.log; /** * Author: Season(ssseasonnn@gmail.com) * Date: 2016/11/3 * Time: 09:44 * Download Type */ public abstract class DownloadType { protected TemporaryRecord record; private DownloadType(TemporaryRecord record) { this.record = record; } public void prepareDownload() throws IOException, ParseException { log(prepareLog()); } public Observable<DownloadStatus> startDownload() { return Flowable.just(1) .doOnSubscribe(new Consumer<Subscription>() { @Override public void accept(Subscription subscription) throws Exception { log(startLog()); record.start(); } }) .flatMap(new Function<Integer, Publisher<DownloadStatus>>() { @Override public Publisher<DownloadStatus> apply(Integer integer) throws Exception { return download(); } }) .doOnNext(new Consumer<DownloadStatus>() { @Override public void accept(DownloadStatus status) throws Exception { record.update(status); } }) .doOnError(new Consumer<Throwable>() { @Override public void accept(Throwable throwable) throws Exception { log(errorLog()); record.error(); } }) .doOnComplete(new Action() { @Override public void run() throws Exception { log(completeLog()); record.complete(); } }) .doOnCancel(new Action() { @Override public void run() throws Exception { log(cancelLog()); record.cancel(); } }) .doFinally(new Action() { @Override public void run() throws Exception { log(finishLog()); record.finish(); } }) .toObservable(); } protected abstract Publisher<DownloadStatus> download(); protected String prepareLog() { return ""; } protected String startLog() { return ""; } protected String completeLog() { return ""; } protected String errorLog() { return ""; } protected String cancelLog() { return ""; } protected String finishLog() { return ""; } public static class NormalDownload extends DownloadType { public NormalDownload(TemporaryRecord record) { super(record); } @Override public void prepareDownload() throws IOException, ParseException { super.prepareDownload(); record.prepareNormalDownload(); } @Override protected Publisher<DownloadStatus> download() { return record.download() .flatMap(new Function<Response<ResponseBody>, Publisher<DownloadStatus>>() { @Override public Publisher<DownloadStatus> apply(Response<ResponseBody> response) throws Exception { return save(response); } }) .compose(Utils.<DownloadStatus>retry2(NORMAL_RETRY_HINT, record.getMaxRetryCount())); } @Override protected String prepareLog() { return NORMAL_DOWNLOAD_PREPARE; } @Override protected String startLog() { return NORMAL_DOWNLOAD_STARTED; } @Override protected String completeLog() { return NORMAL_DOWNLOAD_COMPLETED; } @Override protected String errorLog() { return NORMAL_DOWNLOAD_FAILED; } @Override protected String cancelLog() { return NORMAL_DOWNLOAD_CANCEL; } @Override protected String finishLog() { return NORMAL_DOWNLOAD_FINISH; } private Publisher<DownloadStatus> save(final Response<ResponseBody> response) { return Flowable.create(new FlowableOnSubscribe<DownloadStatus>() { @Override public void subscribe(FlowableEmitter<DownloadStatus> e) throws Exception { record.save(e, response); } }, BackpressureStrategy.LATEST); } } public static class ContinueDownload extends DownloadType { public ContinueDownload(TemporaryRecord record) { super(record); } @Override protected Publisher<DownloadStatus> download() { List<Publisher<DownloadStatus>> tasks = new ArrayList<>(); for (int i = 0; i < record.getMaxThreads(); i++) { tasks.add(rangeDownload(i)); } return Flowable.mergeDelayError(tasks); } @Override protected String prepareLog() { return CONTINUE_DOWNLOAD_PREPARE; } @Override protected String startLog() { return CONTINUE_DOWNLOAD_STARTED; } @Override protected String completeLog() { return CONTINUE_DOWNLOAD_COMPLETED; } @Override protected String errorLog() { return CONTINUE_DOWNLOAD_FAILED; } @Override protected String cancelLog() { return CONTINUE_DOWNLOAD_CANCEL; } @Override protected String finishLog() { return CONTINUE_DOWNLOAD_FINISH; } /** * 分段下载任务 * * @param index 下载编号 * @return Observable */ private Publisher<DownloadStatus> rangeDownload(final int index) { return record.rangeDownload(index) .subscribeOn(Schedulers.io()) //Important! .flatMap(new Function<Response<ResponseBody>, Publisher<DownloadStatus>>() { @Override public Publisher<DownloadStatus> apply(Response<ResponseBody> response) throws Exception { return save(index, response.body()); } }) .compose(Utils.<DownloadStatus>retry2(formatStr(RANGE_RETRY_HINT, index), record.getMaxRetryCount())); } /** * 保存断点下载的文件,以及下载进度 * * @param index 下载编号 * @param response 响应值 * @return Flowable */ private Publisher<DownloadStatus> save(final int index, final ResponseBody response) { return Flowable.create(new FlowableOnSubscribe<DownloadStatus>() { @Override public void subscribe(FlowableEmitter<DownloadStatus> emitter) throws Exception { record.save(emitter, index, response); } }, BackpressureStrategy.LATEST); } } public static class MultiThreadDownload extends ContinueDownload { public MultiThreadDownload(TemporaryRecord record) { super(record); } @Override public void prepareDownload() throws IOException, ParseException { super.prepareDownload(); record.prepareRangeDownload(); } @Override protected String prepareLog() { return MULTITHREADING_DOWNLOAD_PREPARE; } @Override protected String startLog() { return MULTITHREADING_DOWNLOAD_STARTED; } @Override protected String completeLog() { return MULTITHREADING_DOWNLOAD_COMPLETED; } @Override protected String errorLog() { return MULTITHREADING_DOWNLOAD_FAILED; } @Override protected String cancelLog() { return MULTITHREADING_DOWNLOAD_CANCEL; } @Override protected String finishLog() { return MULTITHREADING_DOWNLOAD_FINISH; } } public static class AlreadyDownloaded extends DownloadType { public AlreadyDownloaded(TemporaryRecord record) { super(record); } @Override protected Publisher<DownloadStatus> download() { return Flowable.just(new DownloadStatus(record.getContentLength(), record.getContentLength())); } @Override protected String prepareLog() { return ALREADY_DOWNLOAD_HINT; } } }