源代码备份
This commit is contained in:
2
HttpComponent/src/main/AndroidManifest.xml
Normal file
2
HttpComponent/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,2 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.arialyy.aria.http" />
|
@ -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,198 @@
|
||||
/*
|
||||
* 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 final class ConnectionHelp {
|
||||
private static final String TAG = "ConnectionHelp";
|
||||
|
||||
/**
|
||||
* 处理url参数
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置头部参数
|
||||
*/
|
||||
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,111 @@
|
||||
/*
|
||||
* 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.RecordHandler;
|
||||
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.loader.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 final class HttpRecordHandler extends RecordHandler {
|
||||
public HttpRecordHandler(AbsTaskWrapper wrapper) {
|
||||
super(wrapper);
|
||||
}
|
||||
|
||||
@Override public void handlerTaskRecord(TaskRecord record) {
|
||||
RecordHelper helper = new RecordHelper(getWrapper(), record);
|
||||
if (getWrapper().isSupportBP() && record.threadNum > 1) {
|
||||
if (record.isBlock) {
|
||||
helper.handleBlockRecord();
|
||||
} else {
|
||||
helper.handleMultiRecord();
|
||||
}
|
||||
} 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 = record.taskType;
|
||||
//最后一个线程的结束位置即为文件的总长度
|
||||
if (threadId == (record.threadNum - 1)) {
|
||||
endL = getFileSize();
|
||||
}
|
||||
tr.endLocation = endL;
|
||||
tr.blockLen = RecordUtil.getBlockLen(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_HTTP || requestType == ITaskWrapper.DG_HTTP) {
|
||||
record.isBlock = Configuration.getInstance().downloadCfg.isUseBlock();
|
||||
} else {
|
||||
record.isBlock = false;
|
||||
}
|
||||
record.taskType = requestType;
|
||||
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 getFileSize() <= IRecordHandler.SUB_LEN
|
||||
|| getEntity().isGroupChild()
|
||||
|| threadNum == 1
|
||||
? 1
|
||||
: threadNum;
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import com.arialyy.aria.core.common.RequestEnum;
|
||||
import com.arialyy.aria.core.inf.ITaskOption;
|
||||
import com.arialyy.aria.core.processor.IHttpFileLenAdapter;
|
||||
import com.arialyy.aria.core.processor.IHttpFileNameAdapter;
|
||||
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.net.CookieManager;
|
||||
import java.net.Proxy;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Http任务设置的信息,如:cookie、请求参数
|
||||
*/
|
||||
public final 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;
|
||||
|
||||
private SoftReference<IHttpFileNameAdapter> fileNameAdapter;
|
||||
|
||||
public IHttpFileLenAdapter getFileLenAdapter() {
|
||||
return fileLenAdapter == null ? null : fileLenAdapter.get();
|
||||
}
|
||||
public IHttpFileNameAdapter getFileNameAdapter() {
|
||||
return fileNameAdapter == null ? null : fileNameAdapter.get();
|
||||
}
|
||||
/**
|
||||
* 如果是匿名内部类,完成后需要将adapter设置为空,否则会出现内存泄漏
|
||||
*/
|
||||
public void setFileLenAdapter(IHttpFileLenAdapter fileLenAdapter) {
|
||||
this.fileLenAdapter = new SoftReference<>(fileLenAdapter);
|
||||
}
|
||||
public void setFileNameAdapter(IHttpFileNameAdapter fileNameAdapter) {
|
||||
this.fileNameAdapter = new SoftReference<>(fileNameAdapter);
|
||||
}
|
||||
public Map<String, String> getFormFields() {
|
||||
return formFields;
|
||||
}
|
||||
|
||||
public void setFormFields(Map<String, String> formFields) {
|
||||
this.formFields = formFields;
|
||||
}
|
||||
|
||||
public String getAttachment() {
|
||||
return TextUtils.isEmpty(attachment) ? "file" : 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 TextUtils.isEmpty(charSet) ? "utf-8" : 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,420 @@
|
||||
/*
|
||||
* 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.net.TrafficStats;
|
||||
import android.net.Uri;
|
||||
import android.os.Process;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
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.loader.IInfoTask;
|
||||
import com.arialyy.aria.core.loader.ILoaderVisitor;
|
||||
import com.arialyy.aria.core.processor.IHttpFileLenAdapter;
|
||||
import com.arialyy.aria.exception.AriaHTTPException;
|
||||
import com.arialyy.aria.http.ConnectionHelp;
|
||||
import com.arialyy.aria.http.HttpTaskOption;
|
||||
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.InputStream;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 下载文件信息获取
|
||||
*/
|
||||
final class HttpDFileInfoTask implements IInfoTask, Runnable {
|
||||
private static final String TAG = "HttpDFileInfoTask";
|
||||
private DownloadEntity mEntity;
|
||||
private DTaskWrapper mTaskWrapper;
|
||||
private int mConnectTimeOut;
|
||||
private Callback callback;
|
||||
private HttpTaskOption taskOption;
|
||||
private boolean isStop = false, isCancel = false;
|
||||
|
||||
HttpDFileInfoTask(DTaskWrapper taskWrapper) {
|
||||
this.mTaskWrapper = taskWrapper;
|
||||
mEntity = taskWrapper.getEntity();
|
||||
mConnectTimeOut = AriaConfig.getInstance().getDConfig().getConnectTimeOut();
|
||||
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 + "-");
|
||||
if (AriaConfig.getInstance().getDConfig().isUseHeadRequest()) {
|
||||
ALog.d(TAG, "head请求");
|
||||
conn.setRequestMethod("HEAD");
|
||||
}
|
||||
conn.setConnectTimeout(mConnectTimeOut);
|
||||
conn.connect();
|
||||
handleConnect(conn);
|
||||
} catch (IOException e) {
|
||||
failDownload(new AriaHTTPException(
|
||||
String.format("下载失败,filePath: %s, url: %s", mEntity.getFilePath(), mEntity.getUrl()),
|
||||
e), true);
|
||||
} finally {
|
||||
if (conn != null) {
|
||||
try {
|
||||
InputStream is = conn.getInputStream();
|
||||
if (is != null) {
|
||||
is.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
conn.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void setCallback(Callback callback) {
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
@Override public void stop() {
|
||||
isStop = true;
|
||||
}
|
||||
|
||||
@Override public void cancel() {
|
||||
isCancel = true;
|
||||
}
|
||||
|
||||
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 AriaHTTPException(
|
||||
String.format("下载失败,内存空间不足;filePath: %s, url: %s", mEntity.getFilePath(),
|
||||
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 {
|
||||
if (taskOption.getFileNameAdapter() != null) {
|
||||
String newName =
|
||||
taskOption.getFileNameAdapter().handleFileName(headers, mEntity.getKey());
|
||||
mEntity.setServerFileName(newName);
|
||||
renameFile(newName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 AriaHTTPException(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 AriaHTTPException(
|
||||
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_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 if (code == 416) { // 处理0k长度的文件的情况
|
||||
ALog.w(TAG, "文件长度为0,不支持断点");
|
||||
mTaskWrapper.setSupportBP(false);
|
||||
mTaskWrapper.setNewTask(true);
|
||||
end = true;
|
||||
} else if (code >= HttpURLConnection.HTTP_BAD_REQUEST) {
|
||||
failDownload(new AriaHTTPException(
|
||||
String.format("任务下载失败,errorCode:%s, url: %s", code, mEntity.getUrl())), false);
|
||||
} else {
|
||||
failDownload(new AriaHTTPException(
|
||||
String.format("任务下载失败,errorCode:%s, errorMsg: %s, url: %s", code,
|
||||
conn.getResponseMessage(), mEntity.getUrl())), !CheckUtil.httpIsBadRequest(code));
|
||||
}
|
||||
if (isStop || isCancel) {
|
||||
return;
|
||||
}
|
||||
if (end) {
|
||||
taskOption.setChunked(isChunked);
|
||||
CompleteInfo info = new CompleteInfo(code, mTaskWrapper);
|
||||
callback.onSucceed(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 (!CheckUtil.checkDPathConflicts(false, newPath, mTaskWrapper.getRequestType())) {
|
||||
ALog.e(TAG, "文件重命名失败");
|
||||
return;
|
||||
}
|
||||
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 (callback != null) {
|
||||
callback.onFail(mEntity, new AriaHTTPException("获取重定向链接失败"), false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (newUrl.startsWith("/")) {
|
||||
Uri uri = Uri.parse(mEntity.getUrl());
|
||||
newUrl = uri.getHost() + newUrl;
|
||||
}
|
||||
|
||||
if (!CheckUtil.checkUrl(newUrl)) {
|
||||
failDownload(new AriaHTTPException("下载失败,重定向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 + "-");
|
||||
if (AriaConfig.getInstance().getDConfig().isUseHeadRequest()) {
|
||||
conn.setRequestMethod("HEAD");
|
||||
}
|
||||
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(AriaHTTPException e, boolean needRetry) {
|
||||
if (isStop || isCancel) {
|
||||
return;
|
||||
}
|
||||
if (callback != null) {
|
||||
callback.onFail(mEntity, e, needRetry);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void accept(ILoaderVisitor visitor) {
|
||||
visitor.addComponent(this);
|
||||
}
|
||||
|
||||
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,221 @@
|
||||
/*
|
||||
* 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.loader.IInfoTask;
|
||||
import com.arialyy.aria.core.loader.ILoaderVisitor;
|
||||
import com.arialyy.aria.exception.AriaException;
|
||||
import com.arialyy.aria.exception.AriaHTTPException;
|
||||
import com.arialyy.aria.http.HttpTaskOption;
|
||||
import com.arialyy.aria.util.ALog;
|
||||
import com.arialyy.aria.util.CommonUtil;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* 组合任务文件信息,用于获取长度未知时,组合任务的长度
|
||||
*/
|
||||
public final class HttpDGInfoTask implements IInfoTask {
|
||||
private String TAG = CommonUtil.getClassName(this);
|
||||
private Callback callback;
|
||||
private DGTaskWrapper wrapper;
|
||||
private final Object LOCK = new Object();
|
||||
private ExecutorService mPool = null;
|
||||
private boolean getLenComplete = false;
|
||||
private AtomicInteger count = new AtomicInteger();
|
||||
private AtomicInteger failCount = new AtomicInteger();
|
||||
private boolean isStop = false, isCancel = false;
|
||||
|
||||
public interface DGInfoCallback extends Callback {
|
||||
|
||||
/**
|
||||
* 子任务失败
|
||||
*/
|
||||
void onSubFail(DownloadEntity subEntity, AriaHTTPException e, boolean needRetry);
|
||||
|
||||
/**
|
||||
* 组合任务停止
|
||||
*/
|
||||
void onStop(long len);
|
||||
}
|
||||
|
||||
/**
|
||||
* 子任务回调
|
||||
*/
|
||||
private Callback subCallback = new Callback() {
|
||||
@Override public void onSucceed(String url, CompleteInfo info) {
|
||||
count.getAndIncrement();
|
||||
checkGetSizeComplete(count.get(), failCount.get());
|
||||
ALog.d(TAG, "获取子任务信息完成");
|
||||
}
|
||||
|
||||
@Override public void onFail(AbsEntity entity, AriaException e, boolean needRetry) {
|
||||
ALog.e(TAG, String.format("获取文件信息失败,url:%s", ((DownloadEntity) entity).getUrl()));
|
||||
count.getAndIncrement();
|
||||
failCount.getAndIncrement();
|
||||
((DGInfoCallback) callback).onSubFail((DownloadEntity) entity, new AriaHTTPException(
|
||||
String.format("子任务获取文件长度失败,url:%s", ((DownloadEntity) entity).getUrl())), needRetry);
|
||||
checkGetSizeComplete(count.get(), failCount.get());
|
||||
}
|
||||
};
|
||||
|
||||
HttpDGInfoTask(DGTaskWrapper wrapper) {
|
||||
this.wrapper = wrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止
|
||||
*/
|
||||
@Override
|
||||
public void stop() {
|
||||
isStop = true;
|
||||
if (mPool != null) {
|
||||
mPool.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void cancel() {
|
||||
isCancel = true;
|
||||
if (mPool != null) {
|
||||
mPool.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void run() {
|
||||
// 如果是isUnknownSize()标志,并且获取大小没有完成,则直接回调onStop
|
||||
if (mPool != null && !getLenComplete) {
|
||||
ALog.d(TAG, "获取长度未完成的情况下,停止组合任务");
|
||||
mPool.shutdown();
|
||||
((DGInfoCallback)callback).onStop(0);
|
||||
return;
|
||||
}
|
||||
// 处理组合任务大小未知的情况
|
||||
if (wrapper.isUnknownSize()) {
|
||||
mPool = Executors.newCachedThreadPool();
|
||||
getGroupSize();
|
||||
try {
|
||||
synchronized (LOCK) {
|
||||
LOCK.wait();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (!mPool.isShutdown()) {
|
||||
mPool.shutdown();
|
||||
}
|
||||
} else {
|
||||
for (DTaskWrapper wrapper : wrapper.getSubTaskWrapper()) {
|
||||
cloneHeader(wrapper);
|
||||
}
|
||||
callback.onSucceed(wrapper.getKey(), new CompleteInfo());
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 获取组合任务大小,使用该方式获取到的组合任务大小,子任务不需要再重新获取文件大小
|
||||
*/
|
||||
private void getGroupSize() {
|
||||
new Thread(new Runnable() {
|
||||
@Override public void run() {
|
||||
for (DTaskWrapper dTaskWrapper : wrapper.getSubTaskWrapper()) {
|
||||
DownloadEntity subEntity = dTaskWrapper.getEntity();
|
||||
if (subEntity.getFileSize() > 0) {
|
||||
count.getAndIncrement();
|
||||
if (subEntity.getCurrentProgress() < subEntity.getFileSize()) {
|
||||
// 如果没有完成需要拷贝一份数据
|
||||
cloneHeader(dTaskWrapper);
|
||||
}
|
||||
checkGetSizeComplete(count.get(), failCount.get());
|
||||
continue;
|
||||
}
|
||||
cloneHeader(dTaskWrapper);
|
||||
HttpDFileInfoTask infoTask = new HttpDFileInfoTask(dTaskWrapper);
|
||||
infoTask.setCallback(subCallback);
|
||||
mPool.execute(infoTask);
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查组合任务大小是否获取完成,获取完成后取消阻塞,并设置组合任务大小
|
||||
*/
|
||||
private void checkGetSizeComplete(int count, int failCount) {
|
||||
if (isStop || isCancel) {
|
||||
ALog.w(TAG, "任务已停止或已取消,isStop = " + isStop + ", isCancel = " + isCancel);
|
||||
notifyLock();
|
||||
return;
|
||||
}
|
||||
if (failCount == wrapper.getSubTaskWrapper().size()) {
|
||||
callback.onFail(wrapper.getEntity(), new AriaHTTPException("获取子任务长度失败"), false);
|
||||
notifyLock();
|
||||
return;
|
||||
}
|
||||
if (count == wrapper.getSubTaskWrapper().size()) {
|
||||
long size = 0;
|
||||
for (DTaskWrapper wrapper : wrapper.getSubTaskWrapper()) {
|
||||
size += wrapper.getEntity().getFileSize();
|
||||
}
|
||||
wrapper.getEntity().setConvertFileSize(CommonUtil.formatFileSize(size));
|
||||
wrapper.getEntity().setFileSize(size);
|
||||
wrapper.getEntity().update();
|
||||
getLenComplete = true;
|
||||
ALog.d(TAG, String.format("获取组合任务长度完成,组合任务总长度:%s,失败的子任务数:%s", size, failCount));
|
||||
callback.onSucceed(wrapper.getKey(), new CompleteInfo());
|
||||
notifyLock();
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyLock() {
|
||||
synchronized (LOCK) {
|
||||
LOCK.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 子任务使用父包裹器的属性
|
||||
*/
|
||||
private void cloneHeader(DTaskWrapper taskWrapper) {
|
||||
HttpTaskOption groupOption = (HttpTaskOption) wrapper.getTaskOption();
|
||||
HttpTaskOption subOption = new HttpTaskOption();
|
||||
|
||||
// 设置属性
|
||||
subOption.setFileLenAdapter(groupOption.getFileLenAdapter());
|
||||
subOption.setFileNameAdapter(groupOption.getFileNameAdapter());
|
||||
subOption.setUseServerFileName(groupOption.isUseServerFileName());
|
||||
|
||||
subOption.setFileNameAdapter(groupOption.getFileNameAdapter());
|
||||
subOption.setRequestEnum(groupOption.getRequestEnum());
|
||||
subOption.setHeaders(groupOption.getHeaders());
|
||||
subOption.setProxy(groupOption.getProxy());
|
||||
subOption.setParams(groupOption.getParams());
|
||||
taskWrapper.setTaskOption(subOption);
|
||||
}
|
||||
|
||||
@Override public void setCallback(Callback callback) {
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
@Override public void accept(ILoaderVisitor visitor) {
|
||||
visitor.addComponent(this);
|
||||
}
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
/*
|
||||
* 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.Looper;
|
||||
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.download.DownloadEntity;
|
||||
import com.arialyy.aria.core.group.AbsGroupLoader;
|
||||
import com.arialyy.aria.core.group.AbsSubDLoadUtil;
|
||||
import com.arialyy.aria.core.listener.DownloadGroupListener;
|
||||
import com.arialyy.aria.core.loader.IInfoTask;
|
||||
import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
|
||||
import com.arialyy.aria.exception.AriaException;
|
||||
import com.arialyy.aria.exception.AriaHTTPException;
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* http 组合任务加载器
|
||||
*/
|
||||
final class HttpDGLoader extends AbsGroupLoader {
|
||||
HttpDGLoader(AbsTaskWrapper groupWrapper, DownloadGroupListener listener) {
|
||||
super(groupWrapper, listener);
|
||||
}
|
||||
|
||||
@Override protected void handlerTask(Looper looper) {
|
||||
if (isBreak()) {
|
||||
return;
|
||||
}
|
||||
mInfoTask.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbsSubDLoadUtil createSubLoader(DTaskWrapper wrapper, boolean needGetFileInfo) {
|
||||
HttpSubDLoaderUtil subUtil = new HttpSubDLoaderUtil(getScheduler(), needGetFileInfo, getKey());
|
||||
subUtil.setParams(wrapper, null);
|
||||
return subUtil;
|
||||
}
|
||||
|
||||
private void startSub() {
|
||||
if (isBreak()) {
|
||||
return;
|
||||
}
|
||||
onPostStart();
|
||||
for (DTaskWrapper wrapper : getWrapper().getSubTaskWrapper()) {
|
||||
DownloadEntity dEntity = wrapper.getEntity();
|
||||
|
||||
startSubLoader(createSubLoader(wrapper, dEntity.getFileSize() < 0));
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void addComponent(IInfoTask infoTask) {
|
||||
mInfoTask = infoTask;
|
||||
mInfoTask.setCallback(new HttpDGInfoTask.DGInfoCallback() {
|
||||
|
||||
@Override
|
||||
public void onSubFail(DownloadEntity subEntity, AriaHTTPException e, boolean needRetry) {
|
||||
getState().countFailNum(subEntity.getKey());
|
||||
}
|
||||
|
||||
@Override public void onStop(long len) {
|
||||
getListener().onStop(len);
|
||||
}
|
||||
|
||||
@Override public void onSucceed(String key, CompleteInfo info) {
|
||||
startSub();
|
||||
}
|
||||
|
||||
@Override public void onFail(AbsEntity entity, AriaException e, boolean needRetry) {
|
||||
fail(e, needRetry);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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.download.DGTaskWrapper;
|
||||
import com.arialyy.aria.core.group.AbsGroupLoader;
|
||||
import com.arialyy.aria.core.group.AbsGroupLoaderUtil;
|
||||
import com.arialyy.aria.core.listener.DownloadGroupListener;
|
||||
import com.arialyy.aria.core.loader.LoaderStructure;
|
||||
import com.arialyy.aria.http.HttpTaskOption;
|
||||
|
||||
/**
|
||||
* Created by AriaL on 2017/6/30.
|
||||
* 任务组下载工具
|
||||
*/
|
||||
public final class HttpDGLoaderUtil extends AbsGroupLoaderUtil {
|
||||
|
||||
@Override protected AbsGroupLoader getLoader() {
|
||||
if (mLoader == null) {
|
||||
getTaskWrapper().generateTaskOption(HttpTaskOption.class);
|
||||
mLoader = new HttpDGLoader(getTaskWrapper(), (DownloadGroupListener) getListener());
|
||||
}
|
||||
return mLoader;
|
||||
}
|
||||
|
||||
@Override protected LoaderStructure buildLoaderStructure() {
|
||||
LoaderStructure structure = new LoaderStructure();
|
||||
structure.addComponent(new HttpDGInfoTask((DGTaskWrapper) getTaskWrapper()));
|
||||
structure.accept(getLoader());
|
||||
return structure;
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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.download.DTaskWrapper;
|
||||
import com.arialyy.aria.core.listener.IEventListener;
|
||||
import com.arialyy.aria.core.loader.AbsNormalLoader;
|
||||
import com.arialyy.aria.core.loader.AbsNormalLoaderUtil;
|
||||
import com.arialyy.aria.core.loader.LoaderStructure;
|
||||
import com.arialyy.aria.core.loader.NormalLoader;
|
||||
import com.arialyy.aria.core.loader.NormalTTBuilder;
|
||||
import com.arialyy.aria.core.loader.NormalThreadStateManager;
|
||||
import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
|
||||
import com.arialyy.aria.core.wrapper.ITaskWrapper;
|
||||
import com.arialyy.aria.http.HttpRecordHandler;
|
||||
import com.arialyy.aria.http.HttpTaskOption;
|
||||
|
||||
/**
|
||||
* @author lyy
|
||||
* Date: 2019-09-21
|
||||
*/
|
||||
public final class HttpDLoaderUtil extends AbsNormalLoaderUtil {
|
||||
|
||||
@Override public AbsNormalLoader getLoader() {
|
||||
if (mLoader == null){
|
||||
getTaskWrapper().generateTaskOption(HttpTaskOption.class);
|
||||
mLoader = new NormalLoader(getTaskWrapper(), getListener());
|
||||
}
|
||||
return mLoader;
|
||||
}
|
||||
|
||||
public LoaderStructure BuildLoaderStructure() {
|
||||
LoaderStructure structure = new LoaderStructure();
|
||||
structure.addComponent(new HttpRecordHandler(getTaskWrapper()))
|
||||
.addComponent(new NormalThreadStateManager(getListener()))
|
||||
.addComponent(new HttpDFileInfoTask((DTaskWrapper) getTaskWrapper()))
|
||||
.addComponent(new NormalTTBuilder(getTaskWrapper(), new HttpDTTBuilderAdapter()));
|
||||
structure.accept(getLoader());
|
||||
return structure;
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
package com.arialyy.aria.http.download;
|
||||
|
||||
import com.arialyy.aria.core.TaskRecord;
|
||||
import com.arialyy.aria.core.common.SubThreadConfig;
|
||||
import com.arialyy.aria.core.loader.AbsNormalTTBuilderAdapter;
|
||||
import com.arialyy.aria.core.loader.IRecordHandler;
|
||||
import com.arialyy.aria.core.task.IThreadTaskAdapter;
|
||||
import com.arialyy.aria.util.ALog;
|
||||
import com.arialyy.aria.util.BufferedRandomAccessFile;
|
||||
import com.arialyy.aria.util.FileUtil;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
final class HttpDTTBuilderAdapter extends AbsNormalTTBuilderAdapter {
|
||||
|
||||
@Override public IThreadTaskAdapter getAdapter(SubThreadConfig config) {
|
||||
return new HttpDThreadTaskAdapter(config);
|
||||
}
|
||||
|
||||
@Override public boolean handleNewTask(TaskRecord record, int totalThreadNum) {
|
||||
if (!record.isBlock) {
|
||||
if (getTempFile().exists()) {
|
||||
FileUtil.deleteFile(getTempFile());
|
||||
}
|
||||
} 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));
|
||||
FileUtil.deleteFile(blockFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
BufferedRandomAccessFile file = null;
|
||||
try {
|
||||
if (totalThreadNum > 1 && !record.isBlock) {
|
||||
file = new BufferedRandomAccessFile(getTempFile().getPath(), "rwd", 8192);
|
||||
//设置文件长度
|
||||
file.setLength(getEntity().getFileSize());
|
||||
}
|
||||
if (getTempFile().exists()) {
|
||||
FileUtil.deleteFile(getTempFile());
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
@ -0,0 +1,275 @@
|
||||
/*
|
||||
* 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.RequestEnum;
|
||||
import com.arialyy.aria.core.common.SubThreadConfig;
|
||||
import com.arialyy.aria.core.download.DTaskWrapper;
|
||||
import com.arialyy.aria.exception.AriaHTTPException;
|
||||
import com.arialyy.aria.http.BaseHttpThreadTaskAdapter;
|
||||
import com.arialyy.aria.http.ConnectionHelp;
|
||||
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 = "HttpDThreadTaskAdapter";
|
||||
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(getThreadConfig().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 (getThreadConfig().isBlock) {
|
||||
readDynamicFile(is);
|
||||
} else {
|
||||
//创建可设置位置的文件
|
||||
file =
|
||||
new BufferedRandomAccessFile(getThreadConfig().tempFile, "rwd",
|
||||
getTaskConfig().getBuffSize());
|
||||
//设置每条线程写入文件的位置
|
||||
if (getThreadRecord().startLocation > 0) {
|
||||
file.seek(getThreadRecord().startLocation);
|
||||
}
|
||||
readNormal(is, file);
|
||||
handleComplete();
|
||||
}
|
||||
} catch (MalformedURLException e) {
|
||||
fail(new AriaHTTPException(String.format("任务【%s】下载失败,filePath: %s, url: %s", getFileName(),
|
||||
getEntity().getFilePath(), getEntity().getUrl()), e), false);
|
||||
} catch (IOException e) {
|
||||
fail(new AriaHTTPException(String.format("任务【%s】下载失败,filePath: %s, url: %s", getFileName(),
|
||||
getEntity().getFilePath(), getEntity().getUrl()), e), true);
|
||||
} catch (ArrayIndexOutOfBoundsException e) {
|
||||
fail(new AriaHTTPException(String.format("任务【%s】下载失败,filePath: %s, url: %s", getFileName(),
|
||||
getEntity().getFilePath(), getEntity().getUrl()), e), false);
|
||||
} catch (Exception e) {
|
||||
fail(new AriaHTTPException(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.getInputStream().close();
|
||||
conn.disconnect();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取chunked数据
|
||||
*/
|
||||
private void readChunked(InputStream is) {
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
fos = new FileOutputStream(getThreadConfig().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 AriaHTTPException(
|
||||
String.format("文件下载失败,savePath: %s, url: %s", getEntity().getFilePath(),
|
||||
getThreadConfig().url), e), 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(getThreadConfig().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 AriaHTTPException(String.format("文件下载失败,savePath: %s, url: %s", getEntity().getFilePath(),
|
||||
getThreadConfig().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,59 @@
|
||||
/*
|
||||
* 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.download.DTaskWrapper;
|
||||
import com.arialyy.aria.core.group.AbsSubDLoadUtil;
|
||||
import com.arialyy.aria.core.group.SubRecordHandler;
|
||||
import com.arialyy.aria.core.loader.GroupSubThreadStateManager;
|
||||
import com.arialyy.aria.core.loader.LoaderStructure;
|
||||
import com.arialyy.aria.core.loader.NormalTTBuilder;
|
||||
import com.arialyy.aria.core.loader.SubLoader;
|
||||
|
||||
/**
|
||||
* @author lyy
|
||||
* Date: 2019-09-28
|
||||
*/
|
||||
final class HttpSubDLoaderUtil extends AbsSubDLoadUtil {
|
||||
/**
|
||||
* @param schedulers 调度器
|
||||
* @param needGetInfo {@code true} 需要获取文件信息。{@code false} 不需要获取文件信息
|
||||
*/
|
||||
HttpSubDLoaderUtil( Handler schedulers, boolean needGetInfo, String parentKey) {
|
||||
super(schedulers, needGetInfo, parentKey);
|
||||
}
|
||||
|
||||
@Override protected SubLoader getLoader() {
|
||||
if (mDLoader == null) {
|
||||
mDLoader = new SubLoader(getWrapper(), getSchedulers());
|
||||
mDLoader.setNeedGetInfo(isNeedGetInfo());
|
||||
mDLoader.setParentKey(getParentKey());
|
||||
}
|
||||
return mDLoader;
|
||||
}
|
||||
|
||||
@Override protected LoaderStructure buildLoaderStructure() {
|
||||
LoaderStructure structure = new LoaderStructure();
|
||||
structure.addComponent(new SubRecordHandler(getWrapper()))
|
||||
.addComponent(new GroupSubThreadStateManager(getSchedulers(),getKey()))
|
||||
.addComponent(new NormalTTBuilder(getWrapper(), new HttpDTTBuilderAdapter()))
|
||||
.addComponent(new HttpDFileInfoTask(getWrapper()));
|
||||
structure.accept(getLoader());
|
||||
return structure;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,94 @@
|
||||
/*
|
||||
* 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.os.Handler;
|
||||
import android.os.Looper;
|
||||
import com.arialyy.aria.core.inf.IThreadStateManager;
|
||||
import com.arialyy.aria.core.listener.IEventListener;
|
||||
import com.arialyy.aria.core.loader.AbsNormalLoader;
|
||||
import com.arialyy.aria.core.loader.IInfoTask;
|
||||
import com.arialyy.aria.core.loader.IRecordHandler;
|
||||
import com.arialyy.aria.core.loader.IThreadTaskBuilder;
|
||||
import com.arialyy.aria.core.manager.ThreadTaskManager;
|
||||
import com.arialyy.aria.core.task.IThreadTask;
|
||||
import com.arialyy.aria.core.upload.UTaskWrapper;
|
||||
import com.arialyy.aria.exception.AriaHTTPException;
|
||||
import com.arialyy.aria.util.ALog;
|
||||
import java.util.List;
|
||||
|
||||
final class HttpULoader extends AbsNormalLoader<UTaskWrapper> {
|
||||
HttpULoader(UTaskWrapper wrapper, IEventListener listener) {
|
||||
super(wrapper, listener);
|
||||
}
|
||||
|
||||
@Override public void addComponent(IRecordHandler recordHandler) {
|
||||
mRecordHandler = recordHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated http 上传任务不需要设置这个
|
||||
*/
|
||||
@Deprecated
|
||||
@Override public void addComponent(IInfoTask infoTask) {
|
||||
|
||||
}
|
||||
|
||||
@Override public void addComponent(IThreadStateManager threadState) {
|
||||
mStateManager = threadState;
|
||||
}
|
||||
|
||||
@Override public void addComponent(IThreadTaskBuilder builder) {
|
||||
mTTBuilder = builder;
|
||||
}
|
||||
|
||||
@Override protected void handleTask(Looper looper) {
|
||||
mRecord = mRecordHandler.getRecord(getFileSize());
|
||||
mStateManager.setLooper(mRecord, looper);
|
||||
List<IThreadTask> tt = mTTBuilder.buildThreadTask(mRecord,
|
||||
new Handler(looper, mStateManager.getHandlerCallback()));
|
||||
if (tt == null || tt.isEmpty()) {
|
||||
ALog.e(TAG, "创建线程任务失败");
|
||||
getListener().onFail(false, new AriaHTTPException("创建线程任务失败"));
|
||||
return;
|
||||
}
|
||||
|
||||
getListener().onStart(0);
|
||||
ThreadTaskManager.getInstance().startThread(mTaskWrapper.getKey(), tt.get(0));
|
||||
|
||||
startTimer();
|
||||
}
|
||||
|
||||
@Override public long getFileSize() {
|
||||
return mTaskWrapper.getEntity().getFileSize();
|
||||
}
|
||||
|
||||
@Override protected void checkComponent() {
|
||||
if (mRecordHandler == null) {
|
||||
throw new NullPointerException("任务记录组件为空");
|
||||
}
|
||||
if (mStateManager == null) {
|
||||
throw new NullPointerException("任务状态管理组件为空");
|
||||
}
|
||||
if (mTTBuilder == null) {
|
||||
throw new NullPointerException("线程任务组件为空");
|
||||
}
|
||||
}
|
||||
|
||||
@Override public long getCurrentProgress() {
|
||||
return mStateManager.getCurrentProgress();
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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.SubThreadConfig;
|
||||
import com.arialyy.aria.core.loader.AbsNormalLoader;
|
||||
import com.arialyy.aria.core.loader.AbsNormalLoaderUtil;
|
||||
import com.arialyy.aria.core.loader.AbsNormalTTBuilderAdapter;
|
||||
import com.arialyy.aria.core.loader.LoaderStructure;
|
||||
import com.arialyy.aria.core.loader.NormalTTBuilder;
|
||||
import com.arialyy.aria.core.loader.NormalThreadStateManager;
|
||||
import com.arialyy.aria.core.loader.UploadThreadStateManager;
|
||||
import com.arialyy.aria.core.task.IThreadTaskAdapter;
|
||||
import com.arialyy.aria.core.upload.UTaskWrapper;
|
||||
import com.arialyy.aria.http.HttpRecordHandler;
|
||||
import com.arialyy.aria.http.HttpTaskOption;
|
||||
|
||||
/**
|
||||
* @author lyy
|
||||
* Date: 2019-09-19
|
||||
*/
|
||||
public final class HttpULoaderUtil extends AbsNormalLoaderUtil {
|
||||
|
||||
@Override public AbsNormalLoader getLoader() {
|
||||
if (mLoader == null) {
|
||||
getTaskWrapper().generateTaskOption(HttpTaskOption.class);
|
||||
mLoader = new HttpULoader((UTaskWrapper) getTaskWrapper(), getListener());
|
||||
}
|
||||
return mLoader;
|
||||
}
|
||||
|
||||
@Override public LoaderStructure BuildLoaderStructure() {
|
||||
LoaderStructure structure = new LoaderStructure();
|
||||
structure.addComponent(new HttpRecordHandler(getTaskWrapper()))
|
||||
// .addComponent(new NormalThreadStateManager(getListener()))
|
||||
.addComponent(new UploadThreadStateManager(getListener()))
|
||||
.addComponent(new NormalTTBuilder(getTaskWrapper(), new AbsNormalTTBuilderAdapter() {
|
||||
@Override public IThreadTaskAdapter getAdapter(SubThreadConfig config) {
|
||||
return new HttpUThreadTaskAdapter(config);
|
||||
}
|
||||
|
||||
@Override public boolean handleNewTask(TaskRecord record, int totalThreadNum) {
|
||||
return true;
|
||||
}
|
||||
}));
|
||||
structure.accept(getLoader());
|
||||
return structure;
|
||||
}
|
||||
}
|
@ -0,0 +1,235 @@
|
||||
/*
|
||||
* 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.AriaHTTPException;
|
||||
import com.arialyy.aria.http.BaseHttpThreadTaskAdapter;
|
||||
import com.arialyy.aria.http.ConnectionHelp;
|
||||
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.FileOutputStream;
|
||||
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 AriaHTTPException(
|
||||
String.format("上传失败,文件不存在;filePath: %s, url: %s", getEntity().getFilePath(),
|
||||
getEntity().getUrl())));
|
||||
return;
|
||||
}
|
||||
URL url;
|
||||
try {
|
||||
url = new URL(CommonUtil.convertUrl(getThreadConfig().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());
|
||||
//内部缓冲区---分段上传防止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);
|
||||
finish(writer);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
fail(new AriaHTTPException(
|
||||
String.format("上传失败,filePath: %s, url: %s", getEntity().getFilePath(),
|
||||
getEntity().getUrl()), e));
|
||||
}
|
||||
}
|
||||
|
||||
private void fail(AriaHTTPException 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");
|
||||
return "multipart/form-data";
|
||||
}
|
||||
|
||||
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 bytesLen;
|
||||
while ((bytesLen = inputStream.read(buffer)) != -1) {
|
||||
mOutputStream.write(buffer, 0, bytesLen);
|
||||
progress(bytesLen);
|
||||
if (getThreadTask().isBreak()) {
|
||||
break;
|
||||
}
|
||||
if (mSpeedBandUtil != null) {
|
||||
mSpeedBandUtil.limitNextBytes(bytesLen);
|
||||
}
|
||||
}
|
||||
|
||||
mOutputStream.flush();
|
||||
inputStream.close();
|
||||
writer.append(LINE_END).flush();
|
||||
// 保证上传的文件和本地的一致,https://www.cnblogs.com/tugenhua0707/p/8975121.html
|
||||
writer.append(PREFIX).append(BOUNDARY).append(PREFIX).append(LINE_END).flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务结束操作
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
private String finish(PrintWriter writer) throws IOException {
|
||||
StringBuilder response = new StringBuilder();
|
||||
int status = mHttpConn.getResponseCode();
|
||||
|
||||
if (status == HttpURLConnection.HTTP_OK) {
|
||||
BufferedReader reader =
|
||||
new BufferedReader(new InputStreamReader(ConnectionHelp.convertInputStream(mHttpConn)));
|
||||
String line;
|
||||
while (getThreadTask().isLive() && (line = reader.readLine()) != null) {
|
||||
response.append(line);
|
||||
}
|
||||
reader.close();
|
||||
mHttpConn.disconnect();
|
||||
getEntity().setResponseStr(response.toString());
|
||||
complete();
|
||||
} else {
|
||||
String msg = "response msg: " + mHttpConn.getResponseMessage() + ",code: " + status;
|
||||
ALog.e(TAG, msg);
|
||||
getEntity().setResponseStr(response.toString());
|
||||
fail(new AriaHTTPException(msg), false);
|
||||
response.append(status);
|
||||
}
|
||||
writer.flush();
|
||||
writer.close();
|
||||
mOutputStream.close();
|
||||
return response.toString();
|
||||
}
|
||||
|
||||
@Override protected UploadEntity getEntity() {
|
||||
return (UploadEntity) super.getEntity();
|
||||
}
|
||||
}
|
2
HttpComponent/src/main/resources/META-INF/MANIFEST.MF
Normal file
2
HttpComponent/src/main/resources/META-INF/MANIFEST.MF
Normal file
@ -0,0 +1,2 @@
|
||||
Manifest-Version: 1.0
|
||||
|
@ -0,0 +1,3 @@
|
||||
com.arialyy.aria.http.download.HttpDLoaderUtil
|
||||
com.arialyy.aria.http.download.HttpDGLoaderUtil
|
||||
com.arialyy.aria.http.upload.HttpULoaderUtil
|
Reference in New Issue
Block a user