You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
Android-Download/HttpComponent/src/main/java/com/arialyy/aria/http/HttpFileInfoThread.java

372 lines
13 KiB

/*
* Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria)
*
* 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 com.arialyy.aria.http;
import android.net.TrafficStats;
import android.net.Uri;
import android.os.Process;
import android.text.TextUtils;
import com.arialyy.aria.core.AriaConfig;
import com.arialyy.aria.core.common.CompleteInfo;
import com.arialyy.aria.core.common.RequestEnum;
import com.arialyy.aria.core.download.DTaskWrapper;
import com.arialyy.aria.core.download.DownloadEntity;
import com.arialyy.aria.core.inf.OnFileInfoCallback;
import com.arialyy.aria.core.processor.IHttpFileLenAdapter;
import com.arialyy.aria.exception.AriaIOException;
import com.arialyy.aria.exception.BaseException;
import com.arialyy.aria.exception.TaskException;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CheckUtil;
import com.arialyy.aria.util.CommonUtil;
import com.arialyy.aria.util.FileUtil;
import com.arialyy.aria.util.RecordUtil;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.net.CookieManager;
import java.net.HttpCookie;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
/**
* 下载文件信息获取
*/
public class HttpFileInfoThread implements Runnable {
private static final String TAG = "HttpFileInfoThread";
private DownloadEntity mEntity;
private DTaskWrapper mTaskWrapper;
private int mConnectTimeOut;
private OnFileInfoCallback onFileInfoCallback;
private HttpTaskOption taskOption;
public HttpFileInfoThread(DTaskWrapper taskWrapper, OnFileInfoCallback callback) {
this.mTaskWrapper = taskWrapper;
mEntity = taskWrapper.getEntity();
mConnectTimeOut = AriaConfig.getInstance().getDConfig().getConnectTimeOut();
onFileInfoCallback = callback;
taskOption = (HttpTaskOption) taskWrapper.getTaskOption();
}
@Override public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
TrafficStats.setThreadStatsTag(UUID.randomUUID().toString().hashCode());
HttpURLConnection conn = null;
try {
URL url = ConnectionHelp.handleUrl(mEntity.getUrl(), taskOption);
conn = ConnectionHelp.handleConnection(url, taskOption);
ConnectionHelp.setConnectParam(taskOption, conn);
conn.setRequestProperty("Range", "bytes=" + 0 + "-");
conn.setConnectTimeout(mConnectTimeOut);
conn.connect();
handleConnect(conn);
} catch (IOException e) {
e.printStackTrace();
failDownload(new AriaIOException(TAG,
String.format("下载失败,filePath: %s, url: %s", mEntity.getDownloadPath(), mEntity.getUrl())),
true);
} finally {
if (conn != null) {
try {
conn.getInputStream().close();
} catch (IOException e) {
e.printStackTrace();
}
conn.disconnect();
}
}
}
private void handleConnect(HttpURLConnection conn) throws IOException {
if (taskOption.getRequestEnum() == RequestEnum.POST) {
Map<String, String> params = taskOption.getParams();
if (params != null) {
OutputStreamWriter dos = new OutputStreamWriter(conn.getOutputStream());
Set<String> keys = params.keySet();
StringBuilder sb = new StringBuilder();
for (String key : keys) {
sb.append(key).append("=").append(URLEncoder.encode(params.get(key))).append("&");
}
String url = sb.toString();
url = url.substring(0, url.length() - 1);
dos.write(url);
dos.flush();
dos.close();
}
}
IHttpFileLenAdapter lenAdapter = taskOption.getFileLenAdapter();
if (lenAdapter == null) {
lenAdapter = new FileLenAdapter();
} else {
ALog.d(TAG, "使用自定义adapter");
}
long len = lenAdapter.handleFileLen(conn.getHeaderFields());
if (!FileUtil.checkMemorySpace(mEntity.getFilePath(), len)) {
failDownload(new TaskException(TAG,
String.format("下载失败,内存空间不足;filePath: %s, url: %s", mEntity.getDownloadPath(),
mEntity.getUrl())), false);
return;
}
int code = conn.getResponseCode();
boolean end = false;
if (TextUtils.isEmpty(mEntity.getMd5Code())) {
String md5Code = conn.getHeaderField("Content-MD5");
mEntity.setMd5Code(md5Code);
}
boolean isChunked = false;
final String str = conn.getHeaderField("Transfer-Encoding");
if (!TextUtils.isEmpty(str) && str.equals("chunked")) {
isChunked = true;
}
Map<String, List<String>> headers = conn.getHeaderFields();
String disposition = conn.getHeaderField("Content-Disposition");
if (taskOption.isUseServerFileName()) {
if (!TextUtils.isEmpty(disposition)) {
mEntity.setDisposition(CommonUtil.encryptBASE64(disposition));
handleContentDisposition(disposition);
} else {
ALog.w(TAG, "Content-Disposition对于端字段为空,使用服务器端文件名失败");
}
}
CookieManager msCookieManager = new CookieManager();
List<String> cookiesHeader = headers.get("Set-Cookie");
if (cookiesHeader != null) {
for (String cookie : cookiesHeader) {
msCookieManager.getCookieStore().add(null, HttpCookie.parse(cookie).get(0));
}
taskOption.setCookieManager(msCookieManager);
}
mTaskWrapper.setCode(code);
if (code == HttpURLConnection.HTTP_PARTIAL) {
if (!checkLen(len) && !isChunked) {
if (len < 0) {
failDownload(
new AriaIOException(TAG, String.format("任务下载失败,文件长度小于0, url: %s", mEntity.getUrl())),
false);
}
return;
}
mEntity.setFileSize(len);
mTaskWrapper.setSupportBP(true);
end = true;
} else if (code == HttpURLConnection.HTTP_OK) {
String contentType = conn.getHeaderField("Content-Type");
if (TextUtils.isEmpty(contentType)) {
return;
}
if (contentType.equals("text/html")) {
BufferedReader reader =
new BufferedReader(new InputStreamReader(ConnectionHelp.convertInputStream(conn)));
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
reader.close();
handleUrlReTurn(conn, CommonUtil.getWindowReplaceUrl(sb.toString()));
return;
} else if (!checkLen(len) && !isChunked) {
if (len < 0) {
failDownload(
new AriaIOException(TAG, String.format("任务下载失败,文件长度小于0, url: %s", mEntity.getUrl())),
false);
}
ALog.d(TAG, "len < 0");
return;
}
mEntity.setFileSize(len);
mTaskWrapper.setNewTask(true);
mTaskWrapper.setSupportBP(false);
end = true;
} else if (code == HttpURLConnection.HTTP_NOT_FOUND) {
failDownload(new AriaIOException(TAG,
String.format("任务下载失败,errorCode:404, url: %s", mEntity.getUrl())), true);
} else if (code == HttpURLConnection.HTTP_MOVED_TEMP
|| code == HttpURLConnection.HTTP_MOVED_PERM
|| code == HttpURLConnection.HTTP_SEE_OTHER
|| code == HttpURLConnection.HTTP_CREATED // 201 跳转
|| code == 307) {
handleUrlReTurn(conn, conn.getHeaderField("Location"));
} else {
failDownload(new AriaIOException(TAG,
String.format("任务下载失败,errorCode:%s, errorMsg: %s, url: %s", code,
conn.getResponseMessage(), mEntity.getUrl())), !CheckUtil.httpIsBadRequest(code));
}
if (end) {
taskOption.setChunked(isChunked);
if (onFileInfoCallback != null) {
CompleteInfo info = new CompleteInfo(code, mTaskWrapper);
onFileInfoCallback.onComplete(mEntity.getUrl(), info);
}
mEntity.update();
}
}
/**
* 处理"Content-Disposition"参数
* <a href=https://cloud.tencent.com/developer/section/1189916>Content-Disposition</a></>
*
* @throws UnsupportedEncodingException
*/
private void handleContentDisposition(String disposition) throws UnsupportedEncodingException {
if (disposition.contains(";")) {
String[] infos = disposition.split(";");
if (infos[0].equals("attachment")) {
for (String info : infos) {
if (info.startsWith("filename") && info.contains("=")) {
String[] temp = info.split("=");
if (temp.length > 1) {
String newName = URLDecoder.decode(temp[1], "utf-8").replaceAll("\"", "");
mEntity.setServerFileName(newName);
renameFile(newName);
break;
}
}
}
} else if (infos[0].equals("form-data") && infos.length > 2) {
String[] temp = infos[2].split("=");
if (temp.length > 1) {
String newName = URLDecoder.decode(temp[1], "utf-8").replaceAll("\"", "");
mEntity.setServerFileName(newName);
renameFile(newName);
}
} else {
ALog.w(TAG, "不识别的Content-Disposition参数");
}
}
}
/**
* 重命名文件
*/
private void renameFile(String newName) {
if (TextUtils.isEmpty(newName)) {
ALog.w(TAG, "重命名失败【服务器返回的文件名为空】");
return;
}
ALog.d(TAG, String.format("文件重命名为:%s", newName));
File oldFile = new File(mEntity.getFilePath());
String newPath = oldFile.getParent() + "/" + newName;
if (oldFile.exists()) {
boolean b = oldFile.renameTo(new File(newPath));
ALog.d(TAG, String.format("文件重命名%s", b ? "成功" : "失败"));
}
mEntity.setFileName(newName);
mEntity.setFilePath(newPath);
RecordUtil.modifyTaskRecord(oldFile.getPath(), newPath, mEntity.getTaskType());
}
/**
* 处理30x跳转
*/
private void handleUrlReTurn(HttpURLConnection conn, String newUrl) throws IOException {
ALog.d(TAG, "30x跳转,新url为【" + newUrl + "】");
if (TextUtils.isEmpty(newUrl) || newUrl.equalsIgnoreCase("null")) {
if (onFileInfoCallback != null) {
onFileInfoCallback.onFail(mEntity, new TaskException(TAG, "获取重定向链接失败"), false);
}
return;
}
if (newUrl.startsWith("/")) {
Uri uri = Uri.parse(mEntity.getUrl());
newUrl = uri.getHost() + newUrl;
}
if (!CheckUtil.checkUrl(newUrl)) {
failDownload(new TaskException(TAG, "下载失败,重定向url错误"), false);
return;
}
taskOption.setRedirectUrl(newUrl);
mEntity.setRedirect(true);
mEntity.setRedirectUrl(newUrl);
String cookies = conn.getHeaderField("Set-Cookie");
conn.disconnect();
URL url = ConnectionHelp.handleUrl(newUrl, taskOption);
conn = ConnectionHelp.handleConnection(url, taskOption);
ConnectionHelp.setConnectParam(taskOption, conn);
conn.setRequestProperty("Cookie", cookies);
conn.setRequestProperty("Range", "bytes=" + 0 + "-");
conn.setConnectTimeout(mConnectTimeOut);
conn.connect();
handleConnect(conn);
conn.disconnect();
}
/**
* 检查长度是否合法,并且检查新获取的文件长度是否和数据库的文件长度一直,如果不一致,则表示该任务为新任务
*
* @param len 从服务器获取的文件长度
* @return {@code true}合法
*/
private boolean checkLen(long len) {
if (len != mEntity.getFileSize()) {
ALog.d(TAG, "长度不一致,任务为新任务");
mTaskWrapper.setNewTask(true);
}
return true;
}
private void failDownload(BaseException e, boolean needRetry) {
if (onFileInfoCallback != null) {
onFileInfoCallback.onFail(mEntity, e, needRetry);
}
}
private static class FileLenAdapter implements IHttpFileLenAdapter {
@Override public long handleFileLen(Map<String, List<String>> headers) {
if (headers == null || headers.isEmpty()) {
ALog.e(TAG, "header为空,获取文件长度失败");
return -1;
}
List<String> sLength = headers.get("Content-Length");
if (sLength == null || sLength.isEmpty()) {
return -1;
}
String temp = sLength.get(0);
long len = TextUtils.isEmpty(temp) ? -1 : Long.parseLong(temp);
// 某些服务,如果设置了conn.setRequestProperty("Range", "bytes=" + 0 + "-");
// 会返回 Content-Range: bytes 0-225427911/225427913
if (len < 0) {
List<String> sRange = headers.get("Content-Range");
if (sRange == null || sRange.isEmpty()) {
len = -1;
} else {
int start = temp.indexOf("/");
len = Long.parseLong(temp.substring(start + 1));
}
}
return len;
}
}
}