package zlc.season.rxdownload2.function;
import android.content.Context;
import android.support.annotation.Nullable;
import java.io.File;
import java.io.IOException;
import java.text.ParseException;
import java.util.List;
import io.reactivex.Observable;
import io.reactivex.ObservableEmitter;
import io.reactivex.ObservableOnSubscribe;
import io.reactivex.ObservableSource;
import io.reactivex.annotations.NonNull;
import io.reactivex.disposables.Disposable;
import io.reactivex.exceptions.CompositeException;
import io.reactivex.functions.Action;
import io.reactivex.functions.Consumer;
import io.reactivex.functions.Function;
import retrofit2.Response;
import retrofit2.Retrofit;
import zlc.season.rxdownload2.db.DataBaseHelper;
import zlc.season.rxdownload2.entity.DownloadBean;
import zlc.season.rxdownload2.entity.DownloadRecord;
import zlc.season.rxdownload2.entity.DownloadStatus;
import zlc.season.rxdownload2.entity.DownloadType;
import zlc.season.rxdownload2.entity.TemporaryRecord;
import static android.os.Environment.DIRECTORY_DOWNLOADS;
import static android.os.Environment.getExternalStoragePublicDirectory;
import static zlc.season.rxdownload2.function.Constant.DOWNLOAD_URL_EXISTS;
import static zlc.season.rxdownload2.function.Constant.REQUEST_RETRY_HINT;
import static zlc.season.rxdownload2.function.Constant.TEST_RANGE_SUPPORT;
import static zlc.season.rxdownload2.function.Constant.URL_ILLEGAL;
import static zlc.season.rxdownload2.function.Utils.formatStr;
import static zlc.season.rxdownload2.function.Utils.log;
import static zlc.season.rxdownload2.function.Utils.retry;
/**
* Author: Season(ssseasonnn@gmail.com)
* Date: 2016/11/2
* Time: 09:39
* Download helper
*/
public class DownloadHelper {
private int maxRetryCount = 3;
private int maxThreads = 3;
private String defaultSavePath;
private DownloadApi downloadApi;
private DataBaseHelper dataBaseHelper;
private TemporaryRecordTable recordTable;
public DownloadHelper(Context context) {
downloadApi = RetrofitProvider.getInstance().create(DownloadApi.class);
defaultSavePath = getExternalStoragePublicDirectory(DIRECTORY_DOWNLOADS).getPath();
recordTable = new TemporaryRecordTable();
dataBaseHelper = DataBaseHelper.getSingleton(context.getApplicationContext());
}
public void setRetrofit(Retrofit retrofit) {
downloadApi = retrofit.create(DownloadApi.class);
}
public void setDefaultSavePath(String defaultSavePath) {
this.defaultSavePath = defaultSavePath;
}
public void setMaxRetryCount(int maxRetryCount) {
this.maxRetryCount = maxRetryCount;
}
public void setMaxThreads(int maxThreads) {
this.maxThreads = maxThreads;
}
/**
* return Files
*
* @param url url
* @return Files = {file,tempFile,lmfFile}
*/
@Nullable
public File[] getFiles(String url) {
DownloadRecord record = dataBaseHelper.readSingleRecord(url);
if (record == null) {
return null;
} else {
return Utils.getFiles(record.getSaveName(), record.getSavePath());
}
}
/**
* dispatch download
*
* @param bean download bean
* @return DownloadStatus
*/
public Observable<DownloadStatus> downloadDispatcher(final DownloadBean bean) {
return Observable.just(1)
.doOnSubscribe(new Consumer<Disposable>() {
@Override
public void accept(Disposable disposable) throws Exception {
addTempRecord(bean);
}
})
.flatMap(new Function<Integer, ObservableSource<DownloadType>>() {
@Override
public ObservableSource<DownloadType> apply(Integer integer) throws Exception {
return getDownloadType(bean.getUrl());
}
})
.flatMap(new Function<DownloadType, ObservableSource<DownloadStatus>>() {
@Override
public ObservableSource<DownloadStatus> apply(DownloadType type) throws Exception {
return download(type);
}
})
.doOnError(new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
logError(throwable);
}
})
.doFinally(new Action() {
@Override
public void run() throws Exception {
recordTable.delete(bean.getUrl());
}
});
}
private ObservableSource<DownloadStatus> download(DownloadType downloadType)
throws IOException, ParseException {
downloadType.prepareDownload();
return downloadType.startDownload();
}
private void logError(Throwable throwable) {
if (throwable instanceof CompositeException) {
CompositeException realException = (CompositeException) throwable;
List<Throwable> exceptions = realException.getExceptions();
for (Throwable each : exceptions) {
log(each);
}
} else {
log(throwable);
}
}
/**
* Add a temporary record to the record recordTable.
*
* @param bean download bean
*/
private void addTempRecord(DownloadBean bean) {
if (recordTable.contain(bean.getUrl())) {
throw new IllegalArgumentException(formatStr(DOWNLOAD_URL_EXISTS, bean.getUrl()));
}
recordTable.add(bean.getUrl(), new TemporaryRecord(bean));
}
/**
* get download type.
*
* @param url url
* @return download type
*/
private Observable<DownloadType> getDownloadType(final String url) {
return Observable.just(1)
.flatMap(new Function<Integer, ObservableSource<Object>>() {
@Override
public ObservableSource<Object> apply(Integer integer)
throws Exception {
return checkUrl(url);
}
})
.flatMap(new Function<Object, ObservableSource<Object>>() {
@Override
public ObservableSource<Object> apply(Object o) throws Exception {
return checkRange(url);
}
})
.doOnNext(new Consumer<Object>() {
@Override
public void accept(Object o) throws Exception {
recordTable.init(url, maxThreads, maxRetryCount, defaultSavePath,
downloadApi, dataBaseHelper);
}
})
.flatMap(new Function<Object, ObservableSource<DownloadType>>() {
@Override
public ObservableSource<DownloadType> apply(Object o) throws Exception {
return recordTable.fileExists(url) ? existsType(url) : nonExistsType(url);
}
});
}
/**
* Gets the download type of file non-existence.
*
* @param url file url
* @return Download Type
*/
private Observable<DownloadType> nonExistsType(final String url) {
return Observable.just(1)
.flatMap(new Function<Integer, ObservableSource<DownloadType>>() {
@Override
public ObservableSource<DownloadType> apply(Integer integer)
throws Exception {
return Observable.just(recordTable.generateNonExistsType(url));
}
});
}
/**
* Gets the download type of file existence.
*
* @param url file url
* @return Download Type
*/
private Observable<DownloadType> existsType(final String url) {
return Observable.just(1)
.map(new Function<Integer, String>() {
@Override
public String apply(Integer integer) throws Exception {
return recordTable.readLastModify(url);
}
})
.flatMap(new Function<String, ObservableSource<Object>>() {
@Override
public ObservableSource<Object> apply(String s) throws Exception {
return checkFile(url, s);
}
})
.flatMap(new Function<Object, ObservableSource<DownloadType>>() {
@Override
public ObservableSource<DownloadType> apply(Object o)
throws Exception {
return Observable.just(recordTable.generateFileExistsType(url));
}
});
}
/**
* check url
*
* @param url url
* @return empty
*/
private ObservableSource<Object> checkUrl(final String url) {
return downloadApi.check(url)
.flatMap(new Function<Response<Void>, ObservableSource<Object>>() {
@Override
public ObservableSource<Object> apply(@NonNull Response<Void> resp)
throws Exception {
if (!resp.isSuccessful()) {
return checkUrlByGet(url);
} else {
return saveFileInfo(url, resp);
}
}
})
.compose(retry(REQUEST_RETRY_HINT, maxRetryCount));
}
private ObservableSource<Object> saveFileInfo(final String url, final Response<Void> resp) {
return Observable.create(new ObservableOnSubscribe<Object>() {
@Override
public void subscribe(ObservableEmitter<Object> emitter) throws Exception {
recordTable.saveFileInfo(url, resp);
emitter.onNext(new Object());
emitter.onComplete();
}
});
}
private ObservableSource<Object> checkUrlByGet(final String url) {
return downloadApi.checkByGet(url)
.doOnNext(new Consumer<Response<Void>>() {
@Override
public void accept(Response<Void> response) throws Exception {
if (!response.isSuccessful()) {
throw new IllegalArgumentException(formatStr(URL_ILLEGAL, url));
} else {
recordTable.saveFileInfo(url, response);
}
}
})
.map(new Function<Response<Void>, Object>() {
@Override
public Object apply(Response<Void> response) throws Exception {
return new Object();
}
})
.compose(retry(REQUEST_RETRY_HINT, maxRetryCount));
}
/**
* http checkRangeByHead request,checkRange need info.
*
* @param url url
* @return empty Observable
*/
private ObservableSource<Object> checkRange(final String url) {
return downloadApi.checkRangeByHead(TEST_RANGE_SUPPORT, url)
.doOnNext(new Consumer<Response<Void>>() {
@Override
public void accept(Response<Void> response) throws Exception {
recordTable.saveRangeInfo(url, response);
}
})
.map(new Function<Response<Void>, Object>() {
@Override
public Object apply(Response<Void> response) throws Exception {
return new Object();
}
})
.compose(retry(REQUEST_RETRY_HINT, maxRetryCount));
}
/**
* http checkRangeByHead request,checkRange need info, check whether if server file has changed.
*
* @param url url
* @return empty Observable
*/
private ObservableSource<Object> checkFile(final String url, String lastModify) {
return downloadApi.checkFileByHead(lastModify, url)
.doOnNext(new Consumer<Response<Void>>() {
@Override
public void accept(Response<Void> response) throws Exception {
recordTable.saveFileState(url, response);
}
})
.map(new Function<Response<Void>, Object>() {
@Override
public Object apply(Response<Void> response) throws Exception {
return new Object();
}
})
.compose(retry(REQUEST_RETRY_HINT, maxRetryCount));
}
public Observable<List<DownloadRecord>> readAllRecords() {
return dataBaseHelper.readAllRecords();
}
public Observable<DownloadRecord> readRecord(String url) {
return dataBaseHelper.readRecord(url);
}
}