组件化功能基本完成
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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 com.arialyy.aria.core.common.AbsNormalEntity;
|
||||
import com.arialyy.aria.core.common.SubThreadConfig;
|
||||
import com.arialyy.aria.core.task.AbsThreadTaskAdapter;
|
||||
|
||||
/**
|
||||
* @Author lyy
|
||||
* @Date 2019-09-22
|
||||
*/
|
||||
public abstract class BaseHttpThreadTaskAdapter extends AbsThreadTaskAdapter {
|
||||
protected HttpTaskOption mTaskOption;
|
||||
|
||||
protected BaseHttpThreadTaskAdapter(SubThreadConfig config) {
|
||||
super(config);
|
||||
mTaskOption = (HttpTaskOption) getTaskWrapper().getTaskOption();
|
||||
}
|
||||
|
||||
protected String getFileName() {
|
||||
return getEntity().getFileName();
|
||||
}
|
||||
|
||||
protected AbsNormalEntity getEntity() {
|
||||
return (AbsNormalEntity) getTaskWrapper().getEntity();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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 com.arialyy.aria.util.ALog;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
// Data is expected to be a series of data chunks in the form <chunk size><chunk bytes><chunk size><chunk bytes>
|
||||
// The final data chunk should be a 0-length chunk which will indicate end of input.
|
||||
@Deprecated
|
||||
public class ChunkedInputStream extends InputStream {
|
||||
private static final String TAG = "ChunkedInputStream";
|
||||
|
||||
private DataInputStream din;
|
||||
private int unreadBytes = 0; // Bytes remaining in the current chunk of data
|
||||
private byte[] singleByte = new byte[1];
|
||||
private boolean endOfData = false;
|
||||
private String id;
|
||||
|
||||
public ChunkedInputStream(InputStream in, String id) {
|
||||
din = new DataInputStream(in);
|
||||
this.id = id;
|
||||
ALog.d(TAG, String.format("Creating chunked input for %s", id));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
ALog.d(TAG, String.format("%s: Closing chunked input.", id));
|
||||
din.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
int bytesRead = read(singleByte, 0, 1);
|
||||
return (bytesRead == -1) ? -1 : (int) singleByte[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
int bytesRead = 0;
|
||||
|
||||
if (len < 0) {
|
||||
throw new IllegalArgumentException(id + ": Negative read length");
|
||||
} else if (len == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// If there is a current unread chunk, read from that, or else get the next chunk.
|
||||
if (unreadBytes == 0) {
|
||||
try {
|
||||
// Find the next chunk size
|
||||
unreadBytes = din.readInt();
|
||||
if (ALog.DEBUG) {
|
||||
ALog.d(TAG, String.format("%s: Chunk size %s", id, unreadBytes));
|
||||
}
|
||||
if (unreadBytes == 0) {
|
||||
ALog.d(TAG, String.format("%s: Hit end of data", id));
|
||||
endOfData = true;
|
||||
return -1;
|
||||
}
|
||||
} catch (IOException err) {
|
||||
throw new IOException(id + ": Error while attempting to read chunk length", err);
|
||||
}
|
||||
}
|
||||
|
||||
int bytesToRead = Math.min(len, unreadBytes);
|
||||
try {
|
||||
din.readFully(b, off, bytesToRead);
|
||||
} catch (IOException err) {
|
||||
throw new IOException(
|
||||
id + ": Error while attempting to read " + bytesToRead + " bytes from current chunk",
|
||||
err);
|
||||
}
|
||||
unreadBytes -= bytesToRead;
|
||||
bytesRead += bytesToRead;
|
||||
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
public boolean isEndOfData() {
|
||||
return endOfData;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
* 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.text.TextUtils;
|
||||
import com.arialyy.aria.core.AriaConfig;
|
||||
import com.arialyy.aria.core.ProtocolType;
|
||||
import com.arialyy.aria.core.common.RequestEnum;
|
||||
import com.arialyy.aria.util.ALog;
|
||||
import com.arialyy.aria.util.CommonUtil;
|
||||
import com.arialyy.aria.util.SSLContextUtil;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.CookieManager;
|
||||
import java.net.CookieStore;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.ProtocolException;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import java.util.zip.InflaterInputStream;
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
|
||||
/**
|
||||
* Created by lyy on 2017/1/18. 链接帮助类
|
||||
*/
|
||||
public class ConnectionHelp {
|
||||
private static final String TAG = "ConnectionHelp";
|
||||
|
||||
/**
|
||||
* 处理url参数
|
||||
*
|
||||
* @throws MalformedURLException
|
||||
*/
|
||||
public static URL handleUrl(String url, HttpTaskOption taskDelegate)
|
||||
throws MalformedURLException {
|
||||
Map<String, String> params = taskDelegate.getParams();
|
||||
if (params != null && taskDelegate.getRequestEnum() == RequestEnum.GET) {
|
||||
if (url.contains("?")) {
|
||||
ALog.e(TAG, String.format("设置参数失败,url中已经有?,url: %s", url));
|
||||
return new URL(CommonUtil.convertUrl(url));
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(url).append("?");
|
||||
Set<String> keys = params.keySet();
|
||||
for (String key : keys) {
|
||||
sb.append(key).append("=").append(URLEncoder.encode(params.get(key))).append("&");
|
||||
}
|
||||
String temp = sb.toString();
|
||||
temp = temp.substring(0, temp.length() - 1);
|
||||
return new URL(CommonUtil.convertUrl(temp));
|
||||
} else {
|
||||
return new URL(CommonUtil.convertUrl(url));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换HttpUrlConnect的inputStream流
|
||||
*
|
||||
* @return {@link GZIPInputStream}、{@link InflaterInputStream}
|
||||
* @throws IOException
|
||||
*/
|
||||
public static InputStream convertInputStream(HttpURLConnection connection) throws IOException {
|
||||
String encoding = connection.getHeaderField("Content-Encoding");
|
||||
if (TextUtils.isEmpty(encoding)) {
|
||||
return connection.getInputStream();
|
||||
}
|
||||
if (encoding.contains("gzip")) {
|
||||
return new GZIPInputStream(connection.getInputStream());
|
||||
} else if (encoding.contains("deflate")) {
|
||||
return new InflaterInputStream(connection.getInputStream());
|
||||
} else {
|
||||
return connection.getInputStream();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理链接
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
public static HttpURLConnection handleConnection(URL url, HttpTaskOption taskDelegate)
|
||||
throws IOException {
|
||||
HttpURLConnection conn;
|
||||
URLConnection urlConn;
|
||||
if (taskDelegate.getProxy() != null) {
|
||||
urlConn = url.openConnection(taskDelegate.getProxy());
|
||||
} else {
|
||||
urlConn = url.openConnection();
|
||||
}
|
||||
if (urlConn instanceof HttpsURLConnection) {
|
||||
AriaConfig config = AriaConfig.getInstance();
|
||||
conn = (HttpsURLConnection) urlConn;
|
||||
SSLContext sslContext =
|
||||
SSLContextUtil.getSSLContextFromAssets(config.getDConfig().getCaName(),
|
||||
config.getDConfig().getCaPath(), ProtocolType.Default);
|
||||
if (sslContext == null) {
|
||||
sslContext = SSLContextUtil.getDefaultSLLContext(ProtocolType.Default);
|
||||
}
|
||||
SSLSocketFactory ssf = sslContext.getSocketFactory();
|
||||
((HttpsURLConnection) conn).setSSLSocketFactory(ssf);
|
||||
((HttpsURLConnection) conn).setHostnameVerifier(SSLContextUtil.HOSTNAME_VERIFIER);
|
||||
} else {
|
||||
conn = (HttpURLConnection) urlConn;
|
||||
}
|
||||
return conn;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置头部参数
|
||||
*
|
||||
* @throws ProtocolException
|
||||
*/
|
||||
public static HttpURLConnection setConnectParam(HttpTaskOption delegate, HttpURLConnection conn) {
|
||||
if (delegate.getRequestEnum() == RequestEnum.POST) {
|
||||
conn.setDoInput(true);
|
||||
conn.setDoOutput(true);
|
||||
conn.setUseCaches(false);
|
||||
}
|
||||
Set<String> keys = null;
|
||||
if (delegate.getHeaders() != null && delegate.getHeaders().size() > 0) {
|
||||
keys = delegate.getHeaders().keySet();
|
||||
for (String key : keys) {
|
||||
conn.setRequestProperty(key, delegate.getHeaders().get(key));
|
||||
}
|
||||
}
|
||||
if (conn.getRequestProperty("Accept-Language") == null) {
|
||||
conn.setRequestProperty("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8,ja;q=0.7");
|
||||
}
|
||||
if (conn.getRequestProperty("Accept-Encoding") == null) {
|
||||
conn.setRequestProperty("Accept-Encoding", "identity");
|
||||
}
|
||||
if (conn.getRequestProperty("Accept-Charset") == null) {
|
||||
conn.setRequestProperty("Accept-Charset", "UTF-8");
|
||||
}
|
||||
if (conn.getRequestProperty("Connection") == null) {
|
||||
conn.setRequestProperty("Connection", "Keep-Alive");
|
||||
}
|
||||
if (conn.getRequestProperty("Charset") == null) {
|
||||
conn.setRequestProperty("Charset", "UTF-8");
|
||||
}
|
||||
if (conn.getRequestProperty("User-Agent") == null) {
|
||||
conn.setRequestProperty("User-Agent",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36");
|
||||
}
|
||||
if (conn.getRequestProperty("Accept") == null) {
|
||||
StringBuilder accept = new StringBuilder();
|
||||
accept.append("image/gif, ")
|
||||
.append("image/jpeg, ")
|
||||
.append("image/pjpeg, ")
|
||||
.append("image/webp, ")
|
||||
.append("image/apng, ")
|
||||
.append("application/xml, ")
|
||||
.append("application/xaml+xml, ")
|
||||
.append("application/xhtml+xml, ")
|
||||
.append("application/x-shockwave-flash, ")
|
||||
.append("application/x-ms-xbap, ")
|
||||
.append("application/x-ms-application, ")
|
||||
.append("application/msword, ")
|
||||
.append("application/vnd.ms-excel, ")
|
||||
.append("application/vnd.ms-xpsdocument, ")
|
||||
.append("application/vnd.ms-powerpoint, ")
|
||||
.append("application/signed-exchange, ")
|
||||
.append("text/plain, ")
|
||||
.append("text/html, ")
|
||||
.append("*/*");
|
||||
conn.setRequestProperty("Accept", accept.toString());
|
||||
}
|
||||
//302获取重定向地址
|
||||
conn.setInstanceFollowRedirects(false);
|
||||
|
||||
CookieManager manager = delegate.getCookieManager();
|
||||
if (manager != null) {
|
||||
CookieStore store = manager.getCookieStore();
|
||||
if (store != null && store.getCookies().size() > 0) {
|
||||
conn.setRequestProperty("Cookie",
|
||||
TextUtils.join(";", store.getCookies()));
|
||||
}
|
||||
}
|
||||
|
||||
return conn;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,367 @@
|
||||
/*
|
||||
* 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("任务下载失败,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())), 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* 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 com.arialyy.aria.core.TaskRecord;
|
||||
import com.arialyy.aria.core.ThreadRecord;
|
||||
import com.arialyy.aria.core.common.AbsRecordHandlerAdapter;
|
||||
import com.arialyy.aria.core.common.RecordHelper;
|
||||
import com.arialyy.aria.core.config.Configuration;
|
||||
import com.arialyy.aria.core.download.DownloadEntity;
|
||||
import com.arialyy.aria.core.inf.IRecordHandler;
|
||||
import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
|
||||
import com.arialyy.aria.core.wrapper.ITaskWrapper;
|
||||
import com.arialyy.aria.util.RecordUtil;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* @Author lyy
|
||||
* @Date 2019-09-23
|
||||
*/
|
||||
public class HttpRecordAdapter extends AbsRecordHandlerAdapter {
|
||||
public HttpRecordAdapter(AbsTaskWrapper wrapper) {
|
||||
super(wrapper);
|
||||
}
|
||||
|
||||
@Override public void handlerTaskRecord(TaskRecord record) {
|
||||
RecordHelper helper = new RecordHelper(getWrapper(), record);
|
||||
if (record.isBlock) {
|
||||
helper.handleBlockRecord();
|
||||
} else if (!getWrapper().isSupportBP()) {
|
||||
helper.handleNoSupportBPRecord();
|
||||
} else {
|
||||
helper.handleSingleThreadRecord();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThreadRecord createThreadRecord(TaskRecord record, int threadId, long startL, long endL) {
|
||||
ThreadRecord tr;
|
||||
tr = new ThreadRecord();
|
||||
tr.taskKey = record.filePath;
|
||||
tr.threadId = threadId;
|
||||
tr.startLocation = startL;
|
||||
tr.isComplete = false;
|
||||
|
||||
tr.threadType = TaskRecord.TYPE_HTTP_FTP;
|
||||
//最后一个线程的结束位置即为文件的总长度
|
||||
if (threadId == (record.threadNum - 1)) {
|
||||
endL = getEntity().getFileSize();
|
||||
}
|
||||
tr.endLocation = endL;
|
||||
tr.blockLen = RecordUtil.getBlockLen(getEntity().getFileSize(), threadId, record.threadNum);
|
||||
return tr;
|
||||
}
|
||||
|
||||
@Override public TaskRecord createTaskRecord(int threadNum) {
|
||||
TaskRecord record = new TaskRecord();
|
||||
record.fileName = getEntity().getFileName();
|
||||
record.filePath = getEntity().getFilePath();
|
||||
record.threadRecords = new ArrayList<>();
|
||||
record.threadNum = threadNum;
|
||||
|
||||
int requestType = getWrapper().getRequestType();
|
||||
if (requestType == ITaskWrapper.D_FTP || requestType == ITaskWrapper.D_FTP_DIR) {
|
||||
record.isBlock = threadNum > 1 && Configuration.getInstance().downloadCfg.isUseBlock();
|
||||
// 线程数为1,或者使用了分块,则认为是使用动态长度文件
|
||||
record.isOpenDynamicFile = threadNum == 1 || record.isBlock;
|
||||
} else {
|
||||
record.isBlock = false;
|
||||
}
|
||||
record.taskType = TaskRecord.TYPE_HTTP_FTP;
|
||||
record.isGroupRecord = getEntity().isGroupChild();
|
||||
if (record.isGroupRecord) {
|
||||
if (getEntity() instanceof DownloadEntity) {
|
||||
record.dGroupHash = ((DownloadEntity) getEntity()).getGroupHash();
|
||||
}
|
||||
}
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
@Override public int initTaskThreadNum() {
|
||||
int requestTpe = getWrapper().getRequestType();
|
||||
if (requestTpe == ITaskWrapper.U_HTTP
|
||||
|| (requestTpe == ITaskWrapper.D_HTTP && (!getWrapper().isSupportBP())
|
||||
|| ((HttpTaskOption) getWrapper().getTaskOption()).isChunked())) {
|
||||
return 1;
|
||||
}
|
||||
int threadNum = Configuration.getInstance().downloadCfg.getThreadNum();
|
||||
return getEntity().getFileSize() <= IRecordHandler.SUB_LEN
|
||||
|| getEntity().isGroupChild()
|
||||
|| threadNum == 1
|
||||
? 1
|
||||
: threadNum;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* 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 com.arialyy.aria.core.common.RequestEnum;
|
||||
import com.arialyy.aria.core.processor.IHttpFileLenAdapter;
|
||||
import com.arialyy.aria.core.inf.ITaskOption;
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.net.CookieManager;
|
||||
import java.net.Proxy;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Http任务设置的信息,如:cookie、请求参数
|
||||
*/
|
||||
public class HttpTaskOption implements ITaskOption {
|
||||
|
||||
private CookieManager cookieManager;
|
||||
|
||||
/**
|
||||
* 请求参数
|
||||
*/
|
||||
private Map<String, String> params;
|
||||
|
||||
/**
|
||||
* http 请求头
|
||||
*/
|
||||
private Map<String, String> headers = new HashMap<>();
|
||||
|
||||
/**
|
||||
* 字符编码,默认为"utf-8"
|
||||
*/
|
||||
private String charSet = "utf-8";
|
||||
|
||||
/**
|
||||
* 网络请求类型
|
||||
*/
|
||||
private RequestEnum requestEnum = RequestEnum.GET;
|
||||
|
||||
/**
|
||||
* 是否使用服务器通过content-disposition传递的文件名,内容格式{@code attachment; filename="filename.jpg"} {@code true}
|
||||
* 使用
|
||||
*/
|
||||
private boolean useServerFileName = false;
|
||||
|
||||
/**
|
||||
* 重定向链接
|
||||
*/
|
||||
private String redirectUrl = "";
|
||||
|
||||
/**
|
||||
* 是否是chunk模式
|
||||
*/
|
||||
private boolean isChunked = false;
|
||||
/**
|
||||
* 文件上传需要的key
|
||||
*/
|
||||
private String attachment;
|
||||
|
||||
private Proxy proxy;
|
||||
/**
|
||||
* 文件上传表单
|
||||
*/
|
||||
private Map<String, String> formFields = new HashMap<>();
|
||||
|
||||
private SoftReference<IHttpFileLenAdapter> fileLenAdapter;
|
||||
|
||||
public IHttpFileLenAdapter getFileLenAdapter() {
|
||||
return fileLenAdapter == null ? null : fileLenAdapter.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果是匿名内部类,完成后需要将adapter设置为空,否则会出现内存泄漏
|
||||
*/
|
||||
public void setFileLenAdapter(IHttpFileLenAdapter fileLenAdapter) {
|
||||
this.fileLenAdapter = new SoftReference<>(fileLenAdapter);
|
||||
}
|
||||
|
||||
public Map<String, String> getFormFields() {
|
||||
return formFields;
|
||||
}
|
||||
|
||||
public void setFormFields(Map<String, String> formFields) {
|
||||
this.formFields = formFields;
|
||||
}
|
||||
|
||||
public String getAttachment() {
|
||||
return attachment;
|
||||
}
|
||||
|
||||
public void setAttachment(String attachment) {
|
||||
this.attachment = attachment;
|
||||
}
|
||||
|
||||
public boolean isChunked() {
|
||||
return isChunked;
|
||||
}
|
||||
|
||||
public void setChunked(boolean chunked) {
|
||||
isChunked = chunked;
|
||||
}
|
||||
|
||||
public CookieManager getCookieManager() {
|
||||
return cookieManager;
|
||||
}
|
||||
|
||||
public void setCookieManager(CookieManager cookieManager) {
|
||||
this.cookieManager = cookieManager;
|
||||
}
|
||||
|
||||
public Proxy getProxy() {
|
||||
return proxy;
|
||||
}
|
||||
|
||||
public void setProxy(Proxy proxy) {
|
||||
this.proxy = proxy;
|
||||
}
|
||||
|
||||
public Map<String, String> getHeaders() {
|
||||
return headers;
|
||||
}
|
||||
|
||||
public void setHeaders(Map<String, String> headers) {
|
||||
this.headers = headers;
|
||||
}
|
||||
|
||||
public String getCharSet() {
|
||||
return charSet;
|
||||
}
|
||||
|
||||
public void setCharSet(String charSet) {
|
||||
this.charSet = charSet;
|
||||
}
|
||||
|
||||
public RequestEnum getRequestEnum() {
|
||||
return requestEnum;
|
||||
}
|
||||
|
||||
public void setRequestEnum(RequestEnum requestEnum) {
|
||||
this.requestEnum = requestEnum;
|
||||
}
|
||||
|
||||
public boolean isUseServerFileName() {
|
||||
return useServerFileName;
|
||||
}
|
||||
|
||||
public void setUseServerFileName(boolean useServerFileName) {
|
||||
this.useServerFileName = useServerFileName;
|
||||
}
|
||||
|
||||
public String getRedirectUrl() {
|
||||
return redirectUrl;
|
||||
}
|
||||
|
||||
public void setRedirectUrl(String redirectUrl) {
|
||||
this.redirectUrl = redirectUrl;
|
||||
}
|
||||
|
||||
public Map<String, String> getParams() {
|
||||
return params;
|
||||
}
|
||||
|
||||
public void setParams(Map<String, String> params) {
|
||||
this.params = params;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
* 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.download;
|
||||
|
||||
import com.arialyy.aria.core.common.AbsEntity;
|
||||
import com.arialyy.aria.core.common.CompleteInfo;
|
||||
import com.arialyy.aria.core.download.DGTaskWrapper;
|
||||
import com.arialyy.aria.core.download.DTaskWrapper;
|
||||
import com.arialyy.aria.core.download.DownloadEntity;
|
||||
import com.arialyy.aria.core.inf.IEntity;
|
||||
import com.arialyy.aria.core.inf.OnFileInfoCallback;
|
||||
import com.arialyy.aria.exception.AriaIOException;
|
||||
import com.arialyy.aria.exception.BaseException;
|
||||
import com.arialyy.aria.core.group.AbsGroupUtil;
|
||||
import com.arialyy.aria.core.group.AbsSubDLoadUtil;
|
||||
import com.arialyy.aria.core.listener.IDGroupListener;
|
||||
import com.arialyy.aria.http.HttpFileInfoThread;
|
||||
import com.arialyy.aria.http.HttpTaskOption;
|
||||
import com.arialyy.aria.util.ALog;
|
||||
import com.arialyy.aria.util.CommonUtil;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* Created by AriaL on 2017/6/30.
|
||||
* 任务组下载工具
|
||||
*/
|
||||
public class DGroupLoaderUtil extends AbsGroupUtil {
|
||||
private final Object LOCK = new Object();
|
||||
private ExecutorService mPool = null;
|
||||
private boolean getLenComplete = false;
|
||||
private List<DTaskWrapper> mTempWrapper = new ArrayList<>();
|
||||
|
||||
public DGroupLoaderUtil(IDGroupListener listener, DGTaskWrapper taskWrapper) {
|
||||
super(listener, taskWrapper);
|
||||
taskWrapper.generateTaskOption(HttpTaskOption.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbsSubDLoadUtil createSubLoader(DTaskWrapper wrapper, boolean needGetFileInfo) {
|
||||
return new SubDLoaderUtil(getScheduler(), wrapper, needGetFileInfo);
|
||||
}
|
||||
|
||||
@Override public void onPreCancel() {
|
||||
super.onPreCancel();
|
||||
}
|
||||
|
||||
@Override protected boolean onPreStop() {
|
||||
// 如果是isUnknownSize()标志,并且获取大小没有完成,则直接回调onStop
|
||||
if (mPool != null && !getLenComplete) {
|
||||
ALog.d(TAG, "获取长度未完成的情况下,停止组合任务");
|
||||
mPool.shutdown();
|
||||
mListener.onStop(0);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override protected boolean onStart() {
|
||||
super.onStart();
|
||||
initState();
|
||||
if (getState().getCompleteNum() == getState().getSubSize()) {
|
||||
mListener.onComplete();
|
||||
} else {
|
||||
// 处理组合任务大小未知的情况
|
||||
if (getWrapper().isUnknownSize() && getWrapper().getEntity().getFileSize() < 1) {
|
||||
mPool = Executors.newCachedThreadPool();
|
||||
getGroupSize();
|
||||
try {
|
||||
synchronized (LOCK) {
|
||||
LOCK.wait();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return getLenComplete;
|
||||
} else {
|
||||
for (DTaskWrapper wrapper : getWrapper().getSubTaskWrapper()) {
|
||||
if (wrapper.getState() != IEntity.STATE_COMPLETE) {
|
||||
startSubLoader(createSubLoader(wrapper, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取组合任务大小,使用该方式获取到的组合任务大小,子任务不需要再重新获取文件大小
|
||||
*/
|
||||
private void getGroupSize() {
|
||||
new Thread(new Runnable() {
|
||||
int count;
|
||||
int failCount;
|
||||
|
||||
@Override public void run() {
|
||||
for (DTaskWrapper dTaskWrapper : getWrapper().getSubTaskWrapper()) {
|
||||
cloneHeader(dTaskWrapper);
|
||||
mPool.submit(new HttpFileInfoThread(dTaskWrapper, new OnFileInfoCallback() {
|
||||
@Override public void onComplete(String url, CompleteInfo info) {
|
||||
if (!getWrapper().isUnknownSize()) {
|
||||
startSubLoader(createSubLoader((DTaskWrapper) info.wrapper, false));
|
||||
} else {
|
||||
mTempWrapper.add((DTaskWrapper) info.wrapper);
|
||||
}
|
||||
count++;
|
||||
checkGetSizeComplete(count, failCount);
|
||||
ALog.d(TAG, "获取子任务信息完成");
|
||||
}
|
||||
|
||||
@Override public void onFail(AbsEntity entity, BaseException e, boolean needRetry) {
|
||||
ALog.e(TAG, String.format("获取文件信息失败,url:%s", ((DownloadEntity) entity).getUrl()));
|
||||
count++;
|
||||
failCount++;
|
||||
mListener.onSubFail((DownloadEntity) entity, new AriaIOException(TAG,
|
||||
String.format("子任务获取文件长度失败,url:%s", ((DownloadEntity) entity).getUrl())));
|
||||
checkGetSizeComplete(count, failCount);
|
||||
getState().countFailNum(entity.getKey());
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查组合任务大小是否获取完成,获取完成后取消阻塞,并设置组合任务大小
|
||||
*/
|
||||
private void checkGetSizeComplete(int count, int failCount) {
|
||||
if (failCount == getWrapper().getSubTaskWrapper().size()) {
|
||||
getState().setRunning(false);
|
||||
mListener.onFail(false, new AriaIOException(TAG, "获取子任务长度失败"));
|
||||
notifyLock();
|
||||
return;
|
||||
}
|
||||
if (count == getWrapper().getSubTaskWrapper().size()) {
|
||||
long size = 0;
|
||||
for (DTaskWrapper wrapper : getWrapper().getSubTaskWrapper()) {
|
||||
size += wrapper.getEntity().getFileSize();
|
||||
}
|
||||
getWrapper().getEntity().setConvertFileSize(CommonUtil.formatFileSize(size));
|
||||
getWrapper().getEntity().setFileSize(size);
|
||||
getWrapper().getEntity().update();
|
||||
getLenComplete = true;
|
||||
ALog.d(TAG, String.format("获取组合任务长度完成,组合任务总长度:%s,失败的只任务数:%s", size, failCount));
|
||||
// 未知大小的组合任务,延迟下载
|
||||
if (getWrapper().isUnknownSize()) {
|
||||
for (DTaskWrapper wrapper : mTempWrapper) {
|
||||
startSubLoader(createSubLoader(wrapper, false));
|
||||
}
|
||||
}
|
||||
notifyLock();
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyLock() {
|
||||
synchronized (LOCK) {
|
||||
LOCK.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 子任务使用父包裹器的属性
|
||||
*/
|
||||
private void cloneHeader(DTaskWrapper taskWrapper) {
|
||||
HttpTaskOption groupOption = (HttpTaskOption) getWrapper().getTaskOption();
|
||||
HttpTaskOption subOption = (HttpTaskOption) taskWrapper.getTaskOption();
|
||||
|
||||
// 设置属性
|
||||
subOption.setFileLenAdapter(groupOption.getFileLenAdapter());
|
||||
subOption.setRequestEnum(groupOption.getRequestEnum());
|
||||
subOption.setHeaders(groupOption.getHeaders());
|
||||
subOption.setProxy(groupOption.getProxy());
|
||||
subOption.setParams(groupOption.getParams());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* 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.download;
|
||||
|
||||
import com.arialyy.aria.core.TaskRecord;
|
||||
import com.arialyy.aria.core.common.RecordHandler;
|
||||
import com.arialyy.aria.core.common.SubThreadConfig;
|
||||
import com.arialyy.aria.core.download.DownloadEntity;
|
||||
import com.arialyy.aria.core.inf.IRecordHandler;
|
||||
import com.arialyy.aria.core.task.AbsNormalLoaderAdapter;
|
||||
import com.arialyy.aria.core.task.IThreadTask;
|
||||
import com.arialyy.aria.core.task.ThreadTask;
|
||||
import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
|
||||
import com.arialyy.aria.core.wrapper.ITaskWrapper;
|
||||
import com.arialyy.aria.http.HttpRecordAdapter;
|
||||
import com.arialyy.aria.util.ALog;
|
||||
import com.arialyy.aria.util.BufferedRandomAccessFile;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* @Author lyy
|
||||
* @Date 2019-09-21
|
||||
*/
|
||||
final class HttpDLoaderAdapter extends AbsNormalLoaderAdapter {
|
||||
HttpDLoaderAdapter(ITaskWrapper wrapper) {
|
||||
super(wrapper);
|
||||
}
|
||||
|
||||
@Override public boolean handleNewTask(TaskRecord record, int totalThreadNum) {
|
||||
if (!record.isBlock) {
|
||||
if (getTempFile().exists()) {
|
||||
getTempFile().delete();
|
||||
}
|
||||
//CommonUtil.createFile(mTempFile.getPath());
|
||||
} else {
|
||||
for (int i = 0; i < totalThreadNum; i++) {
|
||||
File blockFile =
|
||||
new File(String.format(IRecordHandler.SUB_PATH, getTempFile().getPath(), i));
|
||||
if (blockFile.exists()) {
|
||||
ALog.d(TAG, String.format("分块【%s】已经存在,将删除该分块", i));
|
||||
blockFile.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
BufferedRandomAccessFile file = null;
|
||||
try {
|
||||
if (totalThreadNum > 1 && !record.isBlock) {
|
||||
file = new BufferedRandomAccessFile(new File(getTempFile().getPath()), "rwd", 8192);
|
||||
//设置文件长度
|
||||
file.setLength(getEntity().getFileSize());
|
||||
}
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
ALog.e(TAG, String.format("下载失败,filePath: %s, url: %s", getEntity().getFilePath(),
|
||||
getEntity().getUrl()));
|
||||
} finally {
|
||||
if (file != null) {
|
||||
try {
|
||||
file.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override public IThreadTask createThreadTask(SubThreadConfig config) {
|
||||
ThreadTask task = new ThreadTask(config);
|
||||
HttpDThreadTaskAdapter adapter = new HttpDThreadTaskAdapter(config);
|
||||
task.setAdapter(adapter);
|
||||
return task;
|
||||
}
|
||||
|
||||
@Override public IRecordHandler recordHandler(AbsTaskWrapper wrapper) {
|
||||
RecordHandler recordHandler = new RecordHandler(wrapper);
|
||||
HttpRecordAdapter adapter = new HttpRecordAdapter(wrapper);
|
||||
recordHandler.setAdapter(adapter);
|
||||
return recordHandler;
|
||||
}
|
||||
|
||||
private DownloadEntity getEntity() {
|
||||
return (DownloadEntity) getWrapper().getEntity();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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.download;
|
||||
|
||||
import com.arialyy.aria.core.common.AbsEntity;
|
||||
import com.arialyy.aria.core.common.CompleteInfo;
|
||||
import com.arialyy.aria.core.download.DTaskWrapper;
|
||||
import com.arialyy.aria.core.inf.OnFileInfoCallback;
|
||||
import com.arialyy.aria.core.listener.IEventListener;
|
||||
import com.arialyy.aria.core.loader.AbsLoader;
|
||||
import com.arialyy.aria.core.loader.AbsNormalLoaderUtil;
|
||||
import com.arialyy.aria.core.loader.NormalLoader;
|
||||
import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
|
||||
import com.arialyy.aria.exception.BaseException;
|
||||
import com.arialyy.aria.http.HttpFileInfoThread;
|
||||
import com.arialyy.aria.http.HttpTaskOption;
|
||||
|
||||
/**
|
||||
* @Author lyy
|
||||
* @Date 2019-09-21
|
||||
*/
|
||||
public class HttpDLoaderUtil extends AbsNormalLoaderUtil {
|
||||
public HttpDLoaderUtil(AbsTaskWrapper wrapper, IEventListener listener) {
|
||||
super(wrapper, listener);
|
||||
wrapper.generateTaskOption(HttpTaskOption.class);
|
||||
}
|
||||
|
||||
@Override protected AbsLoader createLoader() {
|
||||
NormalLoader loader = new NormalLoader(getListener(), getTaskWrapper());
|
||||
HttpDLoaderAdapter adapter = new HttpDLoaderAdapter(getTaskWrapper());
|
||||
loader.setAdapter(adapter);
|
||||
return loader;
|
||||
}
|
||||
|
||||
@Override protected Runnable createInfoThread() {
|
||||
return new HttpFileInfoThread((DTaskWrapper) getTaskWrapper(), new OnFileInfoCallback() {
|
||||
@Override public void onComplete(String url, CompleteInfo info) {
|
||||
((NormalLoader) getLoader()).updateTempFile();
|
||||
getLoader().start();
|
||||
}
|
||||
|
||||
@Override public void onFail(AbsEntity entity, BaseException e, boolean needRetry) {
|
||||
fail(e, needRetry);
|
||||
getLoader().closeTimer();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
/*
|
||||
* 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.download;
|
||||
|
||||
import com.arialyy.aria.core.common.SubThreadConfig;
|
||||
import com.arialyy.aria.core.download.DTaskWrapper;
|
||||
import com.arialyy.aria.exception.AriaIOException;
|
||||
import com.arialyy.aria.exception.TaskException;
|
||||
import com.arialyy.aria.http.BaseHttpThreadTaskAdapter;
|
||||
import com.arialyy.aria.http.ConnectionHelp;
|
||||
import com.arialyy.aria.core.common.RequestEnum;
|
||||
import com.arialyy.aria.util.ALog;
|
||||
import com.arialyy.aria.util.BufferedRandomAccessFile;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Created by lyy on 2017/1/18. 下载线程
|
||||
*/
|
||||
final class HttpDThreadTaskAdapter extends BaseHttpThreadTaskAdapter {
|
||||
private final String TAG = "HttpThreadTask";
|
||||
private DTaskWrapper mTaskWrapper;
|
||||
|
||||
HttpDThreadTaskAdapter(SubThreadConfig config) {
|
||||
super(config);
|
||||
}
|
||||
|
||||
@Override protected void handlerThreadTask() {
|
||||
mTaskWrapper = (DTaskWrapper) getTaskWrapper();
|
||||
if (getThreadRecord().isComplete) {
|
||||
handleComplete();
|
||||
return;
|
||||
}
|
||||
HttpURLConnection conn = null;
|
||||
BufferedInputStream is = null;
|
||||
BufferedRandomAccessFile file = null;
|
||||
try {
|
||||
URL url = ConnectionHelp.handleUrl(getConfig().url, mTaskOption);
|
||||
conn = ConnectionHelp.handleConnection(url, mTaskOption);
|
||||
if (mTaskWrapper.isSupportBP()) {
|
||||
ALog.d(TAG,
|
||||
String.format("任务【%s】线程__%s__开始下载【开始位置 : %s,结束位置:%s】", getFileName(),
|
||||
getThreadRecord().threadId, getThreadRecord().startLocation,
|
||||
getThreadRecord().endLocation));
|
||||
conn.setRequestProperty("Range",
|
||||
String.format("bytes=%s-%s", getThreadRecord().startLocation,
|
||||
(getThreadRecord().endLocation - 1)));
|
||||
} else {
|
||||
ALog.w(TAG, "该下载不支持断点");
|
||||
}
|
||||
ConnectionHelp.setConnectParam(mTaskOption, conn);
|
||||
conn.setConnectTimeout(getTaskConfig().getConnectTimeOut());
|
||||
conn.setReadTimeout(getTaskConfig().getIOTimeOut()); //设置读取流的等待时间,必须设置该参数
|
||||
if (mTaskOption.isChunked()) {
|
||||
conn.setDoInput(true);
|
||||
conn.setChunkedStreamingMode(0);
|
||||
}
|
||||
conn.connect();
|
||||
// 传递参数
|
||||
if (mTaskOption.getRequestEnum() == RequestEnum.POST) {
|
||||
Map<String, String> params = mTaskOption.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 paramStr = sb.toString();
|
||||
paramStr = paramStr.substring(0, paramStr.length() - 1);
|
||||
dos.write(paramStr);
|
||||
dos.flush();
|
||||
dos.close();
|
||||
}
|
||||
}
|
||||
|
||||
is = new BufferedInputStream(ConnectionHelp.convertInputStream(conn));
|
||||
if (mTaskOption.isChunked()) {
|
||||
readChunked(is);
|
||||
} else if (getConfig().isOpenDynamicFile) {
|
||||
readDynamicFile(is);
|
||||
} else {
|
||||
//创建可设置位置的文件
|
||||
file =
|
||||
new BufferedRandomAccessFile(getConfig().tempFile, "rwd",
|
||||
getTaskConfig().getBuffSize());
|
||||
//设置每条线程写入文件的位置
|
||||
file.seek(getThreadRecord().startLocation);
|
||||
readNormal(is, file);
|
||||
handleComplete();
|
||||
}
|
||||
} catch (MalformedURLException e) {
|
||||
fail(new TaskException(TAG,
|
||||
String.format("任务【%s】下载失败,filePath: %s, url: %s", getFileName(),
|
||||
getEntity().getFilePath(), getEntity().getUrl()), e), false);
|
||||
} catch (IOException e) {
|
||||
fail(new TaskException(TAG,
|
||||
String.format("任务【%s】下载失败,filePath: %s, url: %s", getFileName(),
|
||||
getEntity().getFilePath(), getEntity().getUrl()), e), true);
|
||||
} catch (Exception e) {
|
||||
fail(new TaskException(TAG,
|
||||
String.format("任务【%s】下载失败,filePath: %s, url: %s", getFileName(),
|
||||
getEntity().getFilePath(), getEntity().getUrl()), e), false);
|
||||
} finally {
|
||||
try {
|
||||
if (file != null) {
|
||||
file.close();
|
||||
}
|
||||
if (is != null) {
|
||||
is.close();
|
||||
}
|
||||
if (conn != null) {
|
||||
conn.disconnect();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取chunked数据
|
||||
*/
|
||||
private void readChunked(InputStream is) {
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
fos = new FileOutputStream(getConfig().tempFile, true);
|
||||
byte[] buffer = new byte[getTaskConfig().getBuffSize()];
|
||||
int len;
|
||||
while (getThreadTask().isLive() && (len = is.read(buffer)) != -1) {
|
||||
if (getThreadTask().isBreak()) {
|
||||
break;
|
||||
}
|
||||
if (mSpeedBandUtil != null) {
|
||||
mSpeedBandUtil.limitNextBytes(len);
|
||||
}
|
||||
fos.write(buffer, 0, len);
|
||||
progress(len);
|
||||
}
|
||||
handleComplete();
|
||||
} catch (IOException e) {
|
||||
fail(new AriaIOException(TAG,
|
||||
String.format("文件下载失败,savePath: %s, url: %s", getEntity().getFilePath(),
|
||||
getConfig().url)), true);
|
||||
} finally {
|
||||
if (fos != null) {
|
||||
try {
|
||||
fos.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 动态长度文件读取方式
|
||||
*/
|
||||
private void readDynamicFile(InputStream is) {
|
||||
FileOutputStream fos = null;
|
||||
FileChannel foc = null;
|
||||
ReadableByteChannel fic = null;
|
||||
try {
|
||||
int len;
|
||||
fos = new FileOutputStream(getConfig().tempFile, true);
|
||||
foc = fos.getChannel();
|
||||
fic = Channels.newChannel(is);
|
||||
ByteBuffer bf = ByteBuffer.allocate(getTaskConfig().getBuffSize());
|
||||
//如果要通过 Future 的 cancel 方法取消正在运行的任务,那么该任务必定是可以 对线程中断做出响应 的任务。
|
||||
|
||||
while (getThreadTask().isLive() && (len = fic.read(bf)) != -1) {
|
||||
if (getThreadTask().isBreak()) {
|
||||
break;
|
||||
}
|
||||
if (mSpeedBandUtil != null) {
|
||||
mSpeedBandUtil.limitNextBytes(len);
|
||||
}
|
||||
if (getRangeProgress() + len >= getThreadRecord().endLocation) {
|
||||
len = (int) (getThreadRecord().endLocation - getRangeProgress());
|
||||
bf.flip();
|
||||
fos.write(bf.array(), 0, len);
|
||||
bf.compact();
|
||||
progress(len);
|
||||
break;
|
||||
} else {
|
||||
bf.flip();
|
||||
foc.write(bf);
|
||||
bf.compact();
|
||||
progress(len);
|
||||
}
|
||||
}
|
||||
handleComplete();
|
||||
} catch (IOException e) {
|
||||
fail(new AriaIOException(TAG,
|
||||
String.format("文件下载失败,savePath: %s, url: %s", getEntity().getFilePath(),
|
||||
getConfig().url), e), true);
|
||||
} finally {
|
||||
try {
|
||||
if (fos != null) {
|
||||
fos.flush();
|
||||
fos.close();
|
||||
}
|
||||
if (foc != null) {
|
||||
foc.close();
|
||||
}
|
||||
if (fic != null) {
|
||||
fic.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取普通的文件流
|
||||
*/
|
||||
private void readNormal(InputStream is, BufferedRandomAccessFile file)
|
||||
throws IOException {
|
||||
byte[] buffer = new byte[getTaskConfig().getBuffSize()];
|
||||
int len;
|
||||
while (getThreadTask().isLive() && (len = is.read(buffer)) != -1) {
|
||||
if (getThreadTask().isBreak()) {
|
||||
break;
|
||||
}
|
||||
if (mSpeedBandUtil != null) {
|
||||
mSpeedBandUtil.limitNextBytes(len);
|
||||
}
|
||||
file.write(buffer, 0, len);
|
||||
progress(len);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理完成配置文件的更新或事件回调
|
||||
*/
|
||||
private void handleComplete() {
|
||||
if (getThreadTask().isBreak()) {
|
||||
return;
|
||||
}
|
||||
if (!getThreadTask().checkBlock()) {
|
||||
return;
|
||||
}
|
||||
|
||||
complete();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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.download;
|
||||
|
||||
import android.os.Handler;
|
||||
import com.arialyy.aria.core.common.AbsEntity;
|
||||
import com.arialyy.aria.core.common.CompleteInfo;
|
||||
import com.arialyy.aria.core.download.DTaskWrapper;
|
||||
import com.arialyy.aria.core.inf.OnFileInfoCallback;
|
||||
import com.arialyy.aria.core.listener.ISchedulers;
|
||||
import com.arialyy.aria.core.loader.NormalLoader;
|
||||
import com.arialyy.aria.exception.BaseException;
|
||||
import com.arialyy.aria.core.group.AbsSubDLoadUtil;
|
||||
import com.arialyy.aria.core.group.ChildDLoadListener;
|
||||
import com.arialyy.aria.http.HttpFileInfoThread;
|
||||
import com.arialyy.aria.http.HttpTaskOption;
|
||||
|
||||
/**
|
||||
* @Author lyy
|
||||
* @Date 2019-09-28
|
||||
*/
|
||||
class SubDLoaderUtil extends AbsSubDLoadUtil {
|
||||
/**
|
||||
* @param schedulers 调度器
|
||||
* @param needGetInfo {@code true} 需要获取文件信息。{@code false} 不需要获取文件信息
|
||||
*/
|
||||
SubDLoaderUtil(Handler schedulers, DTaskWrapper taskWrapper, boolean needGetInfo) {
|
||||
super(schedulers, taskWrapper, needGetInfo);
|
||||
taskWrapper.generateTaskOption(HttpTaskOption.class);
|
||||
}
|
||||
|
||||
@Override protected NormalLoader createLoader(ChildDLoadListener listener, DTaskWrapper wrapper) {
|
||||
NormalLoader loader = new NormalLoader(listener, wrapper);
|
||||
HttpDLoaderAdapter adapter = new HttpDLoaderAdapter(wrapper);
|
||||
loader.setAdapter(adapter);
|
||||
return loader;
|
||||
}
|
||||
|
||||
@Override public void start() {
|
||||
if (isNeedGetInfo()) {
|
||||
new Thread(new HttpFileInfoThread(getWrapper(), new OnFileInfoCallback() {
|
||||
|
||||
@Override public void onComplete(String url, CompleteInfo info) {
|
||||
getDownloader().start();
|
||||
}
|
||||
|
||||
@Override public void onFail(AbsEntity entity, BaseException e, boolean needRetry) {
|
||||
getSchedulers().obtainMessage(ISchedulers.FAIL, SubDLoaderUtil.this).sendToTarget();
|
||||
}
|
||||
})).start();
|
||||
} else {
|
||||
getDownloader().start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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.upload;
|
||||
|
||||
import com.arialyy.aria.core.TaskRecord;
|
||||
import com.arialyy.aria.core.common.RecordHandler;
|
||||
import com.arialyy.aria.core.common.SubThreadConfig;
|
||||
import com.arialyy.aria.core.inf.IRecordHandler;
|
||||
import com.arialyy.aria.core.task.AbsNormalLoaderAdapter;
|
||||
import com.arialyy.aria.core.task.IThreadTask;
|
||||
import com.arialyy.aria.core.task.ThreadTask;
|
||||
import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
|
||||
import com.arialyy.aria.core.wrapper.ITaskWrapper;
|
||||
import com.arialyy.aria.http.HttpRecordAdapter;
|
||||
|
||||
/**
|
||||
* @Author lyy
|
||||
* @Date 2019-09-19
|
||||
*/
|
||||
final class HttpULoaderAdapter extends AbsNormalLoaderAdapter {
|
||||
public HttpULoaderAdapter(ITaskWrapper wrapper) {
|
||||
super(wrapper);
|
||||
}
|
||||
|
||||
@Override public boolean handleNewTask(TaskRecord record, int totalThreadNum) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override public IThreadTask createThreadTask(SubThreadConfig config) {
|
||||
ThreadTask task = new ThreadTask(config);
|
||||
HttpUThreadTaskAdapter adapter = new HttpUThreadTaskAdapter(config);
|
||||
task.setAdapter(adapter);
|
||||
return task;
|
||||
}
|
||||
|
||||
@Override public IRecordHandler recordHandler(AbsTaskWrapper wrapper) {
|
||||
RecordHandler recordHandler = new RecordHandler(wrapper);
|
||||
HttpRecordAdapter adapter = new HttpRecordAdapter(wrapper);
|
||||
recordHandler.setAdapter(adapter);
|
||||
return recordHandler;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.upload;
|
||||
|
||||
import com.arialyy.aria.core.listener.IEventListener;
|
||||
import com.arialyy.aria.core.loader.AbsLoader;
|
||||
import com.arialyy.aria.core.loader.AbsNormalLoaderUtil;
|
||||
import com.arialyy.aria.core.loader.NormalLoader;
|
||||
import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
|
||||
import com.arialyy.aria.http.HttpTaskOption;
|
||||
|
||||
/**
|
||||
* @Author lyy
|
||||
* @Date 2019-09-19
|
||||
*/
|
||||
public class HttpULoaderUtil extends AbsNormalLoaderUtil {
|
||||
protected HttpULoaderUtil(AbsTaskWrapper wrapper, IEventListener listener) {
|
||||
super(wrapper, listener);
|
||||
wrapper.generateTaskOption(HttpTaskOption.class);
|
||||
}
|
||||
|
||||
@Override protected AbsLoader createLoader() {
|
||||
NormalLoader loader = new NormalLoader(getListener(), getTaskWrapper());
|
||||
HttpULoaderAdapter adapter = new HttpULoaderAdapter(getTaskWrapper());
|
||||
loader.setAdapter(adapter);
|
||||
return loader;
|
||||
}
|
||||
|
||||
@Override protected Runnable createInfoThread() {
|
||||
return new Runnable() {
|
||||
@Override public void run() {
|
||||
getLoader().start();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
/*
|
||||
* 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.upload;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import com.arialyy.aria.core.common.SubThreadConfig;
|
||||
import com.arialyy.aria.core.upload.UploadEntity;
|
||||
import com.arialyy.aria.exception.BaseException;
|
||||
import com.arialyy.aria.exception.TaskException;
|
||||
import com.arialyy.aria.http.BaseHttpThreadTaskAdapter;
|
||||
import com.arialyy.aria.util.ALog;
|
||||
import com.arialyy.aria.util.CommonUtil;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.PrintWriter;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Created by Aria.Lao on 2017/7/28. 不支持断点的HTTP上传任务
|
||||
*/
|
||||
final class HttpUThreadTaskAdapter extends BaseHttpThreadTaskAdapter {
|
||||
|
||||
private final String BOUNDARY = UUID.randomUUID().toString(); // 边界标识 随机生成
|
||||
private final String PREFIX = "--", LINE_END = "\r\n";
|
||||
private HttpURLConnection mHttpConn;
|
||||
private OutputStream mOutputStream;
|
||||
|
||||
HttpUThreadTaskAdapter(SubThreadConfig config) {
|
||||
super(config);
|
||||
}
|
||||
|
||||
@Override protected void handlerThreadTask() {
|
||||
File uploadFile = new File(getEntity().getFilePath());
|
||||
if (!uploadFile.exists()) {
|
||||
fail(new TaskException(TAG,
|
||||
String.format("上传失败,文件不存在;filePath: %s, url: %s", getEntity().getFilePath(),
|
||||
getEntity().getUrl())));
|
||||
return;
|
||||
}
|
||||
URL url;
|
||||
try {
|
||||
url = new URL(CommonUtil.convertUrl(getConfig().url));
|
||||
|
||||
mHttpConn = (HttpURLConnection) url.openConnection();
|
||||
mHttpConn.setRequestMethod(mTaskOption.getRequestEnum().name);
|
||||
mHttpConn.setUseCaches(false);
|
||||
mHttpConn.setDoOutput(true);
|
||||
mHttpConn.setDoInput(true);
|
||||
mHttpConn.setRequestProperty("Connection", "Keep-Alive");
|
||||
mHttpConn.setRequestProperty("Content-Type",
|
||||
getContentType() + "; boundary=" + BOUNDARY);
|
||||
mHttpConn.setRequestProperty("User-Agent", getUserAgent());
|
||||
mHttpConn.setConnectTimeout(getTaskConfig().getConnectTimeOut());
|
||||
mHttpConn.setReadTimeout(getTaskConfig().getIOTimeOut());
|
||||
//mHttpConn.setRequestProperty("Range", "bytes=" + 0 + "-" + "100");
|
||||
//内部缓冲区---分段上传防止oom
|
||||
mHttpConn.setChunkedStreamingMode(getTaskConfig().getBuffSize());
|
||||
|
||||
//添加Http请求头部
|
||||
Set<String> keys = mTaskOption.getHeaders().keySet();
|
||||
for (String key : keys) {
|
||||
mHttpConn.setRequestProperty(key, mTaskOption.getHeaders().get(key));
|
||||
}
|
||||
mOutputStream = mHttpConn.getOutputStream();
|
||||
PrintWriter writer =
|
||||
new PrintWriter(new OutputStreamWriter(mOutputStream, mTaskOption.getCharSet()), true);
|
||||
// 添加参数
|
||||
Map<String, String> params = mTaskOption.getParams();
|
||||
if (params != null && !params.isEmpty()) {
|
||||
for (String key : params.keySet()) {
|
||||
addFormField(writer, key, params.get(key));
|
||||
}
|
||||
}
|
||||
|
||||
//添加文件上传表单字段
|
||||
keys = mTaskOption.getFormFields().keySet();
|
||||
for (String key : keys) {
|
||||
addFormField(writer, key, mTaskOption.getFormFields().get(key));
|
||||
}
|
||||
|
||||
uploadFile(writer, mTaskOption.getAttachment(), uploadFile);
|
||||
complete();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
fail(new TaskException(TAG,
|
||||
String.format("上传失败,filePath: %s, url: %s", getEntity().getFilePath(),
|
||||
getEntity().getUrl()), e));
|
||||
}
|
||||
}
|
||||
|
||||
private void fail(BaseException e1) {
|
||||
try {
|
||||
fail(e1, false);
|
||||
if (mOutputStream != null) {
|
||||
mOutputStream.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private String getContentType() {
|
||||
return (mTaskOption.getHeaders() == null || TextUtils.isEmpty(
|
||||
mTaskOption.getHeaders().get("Content-Type"))) ? "multipart/form-data"
|
||||
: mTaskOption.getHeaders().get("Content-Type");
|
||||
}
|
||||
|
||||
private String getUserAgent() {
|
||||
return (mTaskOption.getHeaders() == null || TextUtils.isEmpty(
|
||||
mTaskOption.getHeaders().get("User-Agent")))
|
||||
? "Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2.6)"
|
||||
: mTaskOption.getHeaders().get("User-Agent");
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加文件上传表单字段
|
||||
*/
|
||||
private void addFormField(PrintWriter writer, String name, String value) {
|
||||
writer.append(PREFIX).append(BOUNDARY).append(LINE_END);
|
||||
writer.append("Content-Disposition: form-data; name=\"")
|
||||
.append(name)
|
||||
.append("\"")
|
||||
.append(LINE_END);
|
||||
writer.append("Content-Type: text/plain; charset=")
|
||||
.append(mTaskOption.getCharSet())
|
||||
.append(LINE_END);
|
||||
writer.append(LINE_END);
|
||||
writer.append(value).append(LINE_END);
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传文件
|
||||
*
|
||||
* @param attachment 文件上传attachment
|
||||
* @throws IOException
|
||||
*/
|
||||
private void uploadFile(PrintWriter writer, String attachment, File uploadFile)
|
||||
throws IOException {
|
||||
writer.append(PREFIX).append(BOUNDARY).append(LINE_END);
|
||||
writer.append("Content-Disposition: form-data; name=\"")
|
||||
.append(attachment)
|
||||
.append("\"; filename=\"")
|
||||
.append(getEntity().getFileName())
|
||||
.append("\"")
|
||||
.append(LINE_END);
|
||||
writer.append("Content-Type: ")
|
||||
.append(URLConnection.guessContentTypeFromName(getEntity().getFileName()))
|
||||
.append(LINE_END);
|
||||
writer.append("Content-Transfer-Encoding: binary").append(LINE_END);
|
||||
writer.append(LINE_END);
|
||||
writer.flush();
|
||||
|
||||
FileInputStream inputStream = new FileInputStream(uploadFile);
|
||||
byte[] buffer = new byte[4096];
|
||||
int bytesRead;
|
||||
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||
progress(bytesRead);
|
||||
mOutputStream.write(buffer, 0, bytesRead);
|
||||
if (getThreadTask().isBreak()) {
|
||||
break;
|
||||
}
|
||||
if (mSpeedBandUtil != null) {
|
||||
mSpeedBandUtil.limitNextBytes(bytesRead);
|
||||
}
|
||||
}
|
||||
|
||||
mOutputStream.flush();
|
||||
//outputStream.close(); //不能调用,否则服务器端异常
|
||||
inputStream.close();
|
||||
writer.append(LINE_END);
|
||||
writer.flush();
|
||||
//if (getState().isCancel) {
|
||||
// getState().isRunning = false;
|
||||
// return;
|
||||
//}
|
||||
//getState().isRunning = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务结束操作
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
private String finish(PrintWriter writer) throws IOException {
|
||||
StringBuilder response = new StringBuilder();
|
||||
|
||||
writer.append(LINE_END).flush();
|
||||
writer.append(PREFIX).append(BOUNDARY).append(PREFIX).append(LINE_END);
|
||||
writer.close();
|
||||
|
||||
int status = mHttpConn.getResponseCode();
|
||||
|
||||
if (status == HttpURLConnection.HTTP_OK) {
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(mHttpConn.getInputStream()));
|
||||
String line;
|
||||
while (getThreadTask().isLive() && (line = reader.readLine()) != null) {
|
||||
response.append(line);
|
||||
}
|
||||
reader.close();
|
||||
mHttpConn.disconnect();
|
||||
} else {
|
||||
ALog.e(TAG, "response msg: " + mHttpConn.getResponseMessage() + ",code: " + status);
|
||||
// fail();
|
||||
}
|
||||
writer.flush();
|
||||
writer.close();
|
||||
mOutputStream.close();
|
||||
return response.toString();
|
||||
}
|
||||
|
||||
@Override protected UploadEntity getEntity() {
|
||||
return (UploadEntity) super.getEntity();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user