Files
Aria/HttpComponent/src/main/java/com/arialyy/aria/http/HttpFileInfoThread.java
2019-09-29 20:07:21 +08:00

367 lines
13 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 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.processor.IHttpFileLenAdapter;
import com.arialyy.aria.core.inf.OnFileInfoCallback;
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) {
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.checkSDMemorySpace(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("任务下载失败errorCode404, 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())), true);
}
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);
}
/**
* 处理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.checkUrlNotThrow(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;
}
}
}