/*
* Copyright (C) 2013 Peng fei Pan <sky@xiaopan.me>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.xiaopan.sketch.request;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.concurrent.locks.ReentrantLock;
import me.xiaopan.sketch.SLogType;
import me.xiaopan.sketch.Sketch;
import me.xiaopan.sketch.cache.DiskCache;
import me.xiaopan.sketch.http.HttpStack;
import me.xiaopan.sketch.util.DiskLruCache;
import me.xiaopan.sketch.util.SketchUtils;
/**
* 下载请求
*/
public class DownloadRequest extends AsyncRequest {
protected DownloadResult downloadResult;
private DownloadOptions options;
private DownloadListener downloadListener;
private DownloadProgressListener downloadProgressListener;
public DownloadRequest(
Sketch sketch, DownloadInfo info,
DownloadOptions options, DownloadListener downloadListener,
DownloadProgressListener downloadProgressListener) {
super(sketch, info);
this.options = options;
this.downloadListener = downloadListener;
this.downloadProgressListener = downloadProgressListener;
setLogName("DownloadRequest");
}
/**
* 获取磁盘缓存key
*/
public String getDiskCacheKey() {
return ((DownloadInfo) getInfo()).getDiskCacheKey();
}
/**
* 获取下载选项
*/
public DownloadOptions getOptions() {
return options;
}
/**
* 获取下载结果
*/
@SuppressWarnings("unused")
public DownloadResult getDownloadResult() {
return downloadResult;
}
@Override
public void error(ErrorCause errorCause) {
super.error(errorCause);
if (downloadListener != null) {
postRunError();
}
}
@Override
public void canceled(CancelCause cancelCause) {
super.canceled(cancelCause);
if (downloadListener != null) {
postRunCanceled();
}
}
@Override
protected void submitRunDispatch() {
setStatus(Status.WAIT_DISPATCH);
super.submitRunDispatch();
}
@Override
protected void submitRunDownload() {
setStatus(Status.WAIT_DOWNLOAD);
super.submitRunDownload();
}
@Override
protected void submitRunLoad() {
setStatus(Status.WAIT_LOAD);
super.submitRunLoad();
}
@Override
protected void runDispatch() {
if (isCanceled()) {
if (SLogType.REQUEST.isEnabled()) {
printLogW("canceled", "runDispatch", "download request just start");
}
return;
}
// 从磁盘中找缓存文件
if (!options.isCacheInDiskDisabled()) {
setStatus(Status.CHECK_DISK_CACHE);
DiskCache diskCache = getConfiguration().getDiskCache();
DiskCache.Entry diskCacheEntry = diskCache.get(getDiskCacheKey());
if (diskCacheEntry != null) {
if (SLogType.REQUEST.isEnabled()) {
printLogD("from diskCache", "runDispatch");
}
downloadResult = new DownloadResult(diskCacheEntry, ImageFrom.DISK_CACHE);
downloadCompleted();
return;
}
}
// 在下载之前判断如果请求Level限制只能从本地加载的话就取消了
if (options.getRequestLevel() == RequestLevel.LOCAL) {
requestLevelIsLocal();
return;
}
// 下载
if (SLogType.REQUEST.isEnabled()) {
printLogD("download", "runDispatch");
}
submitRunDownload();
}
/**
* 处理RequestLevel是LOCAL
*/
void requestLevelIsLocal() {
boolean isPauseDownload = options.getRequestLevelFrom() == RequestLevelFrom.PAUSE_DOWNLOAD;
if (SLogType.REQUEST.isEnabled()) {
printLogW("canceled", "runDispatch", isPauseDownload ? "pause download" : "requestLevel is local");
}
canceled(isPauseDownload ? CancelCause.PAUSE_DOWNLOAD : CancelCause.REQUEST_LEVEL_IS_LOCAL);
}
@Override
protected void runDownload() {
if (isCanceled()) {
if (SLogType.REQUEST.isEnabled()) {
printLogW("canceled", "runDownload", "start download");
}
return;
}
DiskCache diskCache = getConfiguration().getDiskCache();
// 使用磁盘缓存就必须要上锁
ReentrantLock diskCacheEditLock = null;
if (!getOptions().isCacheInDiskDisabled()) {
setStatus(Status.GET_DISK_CACHE_EDIT_LOCK);
diskCacheEditLock = diskCache.getEditLock(getDiskCacheKey());
if (diskCacheEditLock != null) {
diskCacheEditLock.lock();
}
}
DownloadResult justDownloadResult = download(diskCache, getDiskCacheKey());
// 解锁
if (diskCacheEditLock != null) {
diskCacheEditLock.unlock();
}
if (isCanceled()) {
if (SLogType.REQUEST.isEnabled()) {
printLogW("canceled", "runDownload", "download after");
}
return;
}
downloadResult = justDownloadResult;
downloadCompleted();
}
private DownloadResult download(DiskCache diskCache, String diskCacheKey) {
if (isCanceled()) {
if (SLogType.REQUEST.isEnabled()) {
printLogW("canceled", "runDownload", "get disk cache edit lock after");
}
return null;
}
// 检查磁盘缓存
if (!getOptions().isCacheInDiskDisabled()) {
setStatus(Status.CHECK_DISK_CACHE);
DiskCache.Entry diskCacheEntry = diskCache.get(diskCacheKey);
if (diskCacheEntry != null) {
return new DownloadResult(diskCacheEntry, ImageFrom.DISK_CACHE);
}
}
// 下载
HttpStack httpStack = getConfiguration().getHttpStack();
int retryCount = 0;
int maxRetryCount = httpStack.getMaxRetryCount();
DownloadResult justDownloadResult = null;
while (true) {
try {
justDownloadResult = realDownload(httpStack, diskCache, diskCacheKey);
break;
} catch (Throwable e) {
e.printStackTrace();
getConfiguration().getMonitor().onDownloadError(this, e);
if (isCanceled()) {
if (SLogType.REQUEST.isEnabled()) {
printLogW("canceled", "runDownload", "download failed");
}
break;
}
if (httpStack.canRetry(e) && retryCount < maxRetryCount) {
retryCount++;
if (SLogType.REQUEST.isEnabled()) {
printLogW("download failed", "runDownload", "retry");
}
} else {
if (SLogType.REQUEST.isEnabled()) {
printLogE("download failed", "runDownload", "end");
}
break;
}
}
}
return justDownloadResult;
}
private DownloadResult realDownload(HttpStack httpStack, DiskCache diskCache, String diskCacheKey)
throws IOException, DiskLruCache.EditorChangedException, DiskLruCache.ClosedException, DiskLruCache.FileNotExistException {
setStatus(Status.CONNECTING);
HttpStack.ImageHttpResponse httpResponse = httpStack.getHttpResponse(getRealUri());
if (isCanceled()) {
httpResponse.releaseConnection();
if (SLogType.REQUEST.isEnabled()) {
printLogW("canceled", "runDownload", "connect after");
}
return null;
}
setStatus(Status.CHECK_RESPONSE);
// 检查状态码
int responseCode;
try {
responseCode = httpResponse.getResponseCode();
} catch (IOException e) {
e.printStackTrace();
httpResponse.releaseConnection();
if (SLogType.REQUEST.isEnabled()) {
printLogE("get response code failed", "runDownload", "responseHeaders: " + httpResponse.getResponseHeadersString());
}
throw new IllegalStateException("get response code exception", e);
}
if (responseCode != 200) {
httpResponse.releaseConnection();
if (SLogType.REQUEST.isEnabled()) {
printLogE("response code exception", "runDownload", "responseHeaders: " + httpResponse.getResponseHeadersString());
}
throw new IllegalStateException("response code exception: " + responseCode);
}
// 检查内容长度
long contentLength = httpResponse.getContentLength();
if (contentLength <= 0 && !httpResponse.isContentChunked()) {
httpResponse.releaseConnection();
if (SLogType.REQUEST.isEnabled()) {
printLogE("content length exception", "runDownload", "contentLength: " + contentLength, "responseHeaders: " + httpResponse.getResponseHeadersString());
}
throw new IllegalStateException("contentLength exception: " + contentLength + "responseHeaders: " + httpResponse.getResponseHeadersString());
}
setStatus(Status.READ_DATA);
// 获取输入流
InputStream inputStream = httpResponse.getContent();
if (isCanceled()) {
SketchUtils.close(inputStream);
if (SLogType.REQUEST.isEnabled()) {
printLogW("canceled", "runDownload", "get input stream after");
}
return null;
}
DiskCache.Editor diskCacheEditor = null;
if (!getOptions().isCacheInDiskDisabled()) {
diskCacheEditor = diskCache.edit(diskCacheKey);
}
OutputStream outputStream;
if (diskCacheEditor != null) {
try {
outputStream = new BufferedOutputStream(diskCacheEditor.newOutputStream(), 8 * 1024);
} catch (FileNotFoundException e) {
SketchUtils.close(inputStream);
diskCacheEditor.abort();
throw e;
}
} else {
outputStream = new ByteArrayOutputStream();
}
// 读取数据
int completedLength = 0;
boolean readFully;
try {
completedLength = readData(inputStream, outputStream, (int) contentLength);
readFully = contentLength <= 0 || completedLength == contentLength;
if (diskCacheEditor != null) {
if (readFully) {
diskCacheEditor.commit();
} else {
diskCacheEditor.abort();
}
}
} catch (IOException e) {
if (diskCacheEditor != null) {
diskCacheEditor.abort();
diskCacheEditor = null;
}
throw e;
} catch (DiskLruCache.ClosedException e) {
e.printStackTrace();
diskCacheEditor.abort();
throw e;
} catch (DiskLruCache.FileNotExistException e) {
e.printStackTrace();
diskCacheEditor.abort();
throw e;
} finally {
SketchUtils.close(outputStream);
SketchUtils.close(inputStream);
}
if (isCanceled()) {
if (SLogType.REQUEST.isEnabled()) {
printLogW("canceled", "runDownload", "read data after", readFully ? "read fully" : "not read fully");
}
return null;
}
if (SLogType.REQUEST.isEnabled()) {
printLogI("download success", "runDownload", "fileLength: " + completedLength + "/" + contentLength);
}
// 提交磁盘缓存并返回
if (diskCacheEditor != null) {
DiskCache.Entry diskCacheEntry = diskCache.get(diskCacheKey);
if (diskCacheEntry != null) {
return new DownloadResult(diskCacheEntry, ImageFrom.NETWORK);
} else {
if (SLogType.REQUEST.isEnabled()) {
printLogW("not found disk cache", "runDownload", "download after");
}
throw new IllegalStateException("not found disk cache entry, key is " + diskCacheKey);
}
} else {
return new DownloadResult(((ByteArrayOutputStream) outputStream).toByteArray(), ImageFrom.NETWORK);
}
}
private int readData(InputStream inputStream, OutputStream outputStream, int contentLength) throws IOException {
int realReadCount;
int completedLength = 0;
long lastCallbackTime = 0;
byte[] buffer = new byte[8 * 1024];
while (true) {
if (isCanceled()) {
break;
}
realReadCount = inputStream.read(buffer);
if (realReadCount != -1) {
outputStream.write(buffer, 0, realReadCount);
completedLength += realReadCount;
// 每秒钟回调一次进度
long currentTime = System.currentTimeMillis();
if (currentTime - lastCallbackTime >= 1000) {
lastCallbackTime = currentTime;
updateProgress(contentLength, completedLength);
}
} else {
// 结束的时候再次回调一下进度,确保页面上能显示100%
updateProgress(contentLength, completedLength);
break;
}
}
outputStream.flush();
return completedLength;
}
@Override
protected void runLoad() {
}
/**
* 更新进度
*/
private void updateProgress(int totalLength, int completedLength) {
if (downloadProgressListener != null && totalLength > 0) {
postRunUpdateProgress(totalLength, completedLength);
}
}
/**
* 下载完成后续处理
*/
protected void downloadCompleted() {
if (downloadResult != null && downloadResult.hasData()) {
postRunCompleted();
} else {
error(ErrorCause.DOWNLOAD_FAIL);
}
}
@Override
protected void runUpdateProgressInMainThread(int totalLength, int completedLength) {
if (isFinished()) {
if (SLogType.REQUEST.isEnabled()) {
printLogW("finished", "runUpdateProgressInMainThread");
}
return;
}
if (downloadProgressListener != null) {
downloadProgressListener.onUpdateDownloadProgress(totalLength, completedLength);
}
}
@Override
protected void runCompletedInMainThread() {
if (isCanceled()) {
if (SLogType.REQUEST.isEnabled()) {
printLogW("canceled", "runCompletedInMainThread");
}
return;
}
setStatus(Status.COMPLETED);
if (downloadListener != null && downloadResult != null && downloadResult.hasData()) {
downloadListener.onCompleted(downloadResult);
}
}
@Override
protected void runErrorInMainThread() {
if (isCanceled()) {
if (SLogType.REQUEST.isEnabled()) {
printLogW("canceled", "runErrorInMainThread");
}
return;
}
if (downloadListener != null) {
downloadListener.onError(getErrorCause());
}
}
@Override
protected void runCanceledInMainThread() {
if (downloadListener != null) {
downloadListener.onCanceled(getCancelCause());
}
}
}