Merge aa292e4389
into d129e0a802
commit
ad71105b38
@ -0,0 +1,8 @@ |
||||
# These are supported funding model platforms |
||||
# https://help.github.com/cn/github/administering-a-repository/displaying-a-sponsor-button-in-your-repository |
||||
# github: ["AriaLyy"] |
||||
# patreon: AriaLyy |
||||
# liberapay: Aria |
||||
open_collective: Aria |
||||
custom: ["https://paypal.me/arialyy"] |
||||
|
@ -1,17 +1,28 @@ |
||||
--- |
||||
name: 问题描述 |
||||
about: 请详细描述你遇到的问题 |
||||
about: 提交问题前,请先阅读文档和搜索issue |
||||
|
||||
--- |
||||
|
||||
## Aria版本 |
||||
<!-- 提交问题前,请先阅读文档和搜索相应问题的issue --> |
||||
|
||||
## 版本 |
||||
* 框架版本 |
||||
|
||||
## 什么问题 |
||||
* 系统版本 |
||||
|
||||
## 错误的url |
||||
|
||||
## 如何复现此问题 |
||||
## 错误日志 |
||||
<!-- 请提供详细的错误日志 --> |
||||
|
||||
|
||||
## 重现步骤 |
||||
<!-- 请提供明确的步骤 --> |
||||
1. |
||||
2. |
||||
3. |
||||
4. |
||||
|
||||
|
||||
## 控制台日志 |
||||
|
||||
|
@ -0,0 +1,20 @@ |
||||
# Number of days of inactivity before an issue becomes stale |
||||
daysUntilStale: 7 |
||||
# Number of days of inactivity before a stale issue is closed |
||||
daysUntilClose: 1000 |
||||
# Issues with these labels will never be considered stale |
||||
exemptLabels: |
||||
- bug |
||||
- adaptation |
||||
- new features |
||||
- suggest |
||||
- pending |
||||
# Label to use when marking an issue as stale |
||||
staleLabel: wontfix |
||||
# Comment to post when marking an issue as stale. Set to `false` to disable |
||||
markComment: > |
||||
This issue has been automatically marked as stale because it has not had |
||||
recent activity. It will be closed if no further activity occurs. Thank you |
||||
for your contributions. |
||||
# Comment to post when closing a stale issue. Set to `false` to disable |
||||
closeComment: false |
@ -0,0 +1,44 @@ |
||||
/* |
||||
* 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.core.command; |
||||
|
||||
import com.arialyy.aria.core.download.AbsGroupTaskWrapper; |
||||
import com.arialyy.aria.core.task.ITask; |
||||
import com.arialyy.aria.core.wrapper.AbsTaskWrapper; |
||||
|
||||
public class CmdHelper { |
||||
/** |
||||
* 创建任务命令 |
||||
* |
||||
* @param taskType {@link ITask#DOWNLOAD}、{@link ITask#DOWNLOAD_GROUP}、{@link ITask#UPLOAD} |
||||
*/ |
||||
public static <T extends AbsTaskWrapper> AbsNormalCmd createNormalCmd(T entity, int cmd, |
||||
int taskType) { |
||||
return NormalCmdFactory.getInstance().createCmd(entity, cmd, taskType); |
||||
} |
||||
|
||||
/** |
||||
* 创建任务组命令 |
||||
* |
||||
* @param childUrl 子任务url |
||||
*/ |
||||
public static <T extends AbsGroupTaskWrapper> AbsGroupCmd createGroupCmd(T entity, int cmd, |
||||
String childUrl) { |
||||
return GroupCmdFactory.getInstance().createCmd(entity, cmd, childUrl); |
||||
} |
||||
} |
@ -0,0 +1,200 @@ |
||||
/* |
||||
* 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.core.command; |
||||
|
||||
import android.text.TextUtils; |
||||
import com.arialyy.aria.core.common.AbsEntity; |
||||
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.download.DownloadGroupEntity; |
||||
import com.arialyy.aria.core.inf.IEntity; |
||||
import com.arialyy.aria.core.inf.IOptionConstant; |
||||
import com.arialyy.aria.core.listener.ISchedulers; |
||||
import com.arialyy.aria.core.manager.TaskWrapperManager; |
||||
import com.arialyy.aria.core.queue.AbsTaskQueue; |
||||
import com.arialyy.aria.core.queue.DGroupTaskQueue; |
||||
import com.arialyy.aria.core.queue.DTaskQueue; |
||||
import com.arialyy.aria.core.queue.UTaskQueue; |
||||
import com.arialyy.aria.core.task.AbsTask; |
||||
import com.arialyy.aria.core.upload.UTaskWrapper; |
||||
import com.arialyy.aria.core.upload.UploadEntity; |
||||
import com.arialyy.aria.core.wrapper.AbsTaskWrapper; |
||||
import com.arialyy.aria.core.wrapper.ITaskWrapper; |
||||
import com.arialyy.aria.orm.DbEntity; |
||||
import com.arialyy.aria.util.ALog; |
||||
import com.arialyy.aria.util.CommonUtil; |
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
/** |
||||
* 恢复任务工具 |
||||
*/ |
||||
public class ResumeThread implements Runnable { |
||||
private String TAG = CommonUtil.getClassName(getClass()); |
||||
private List<AbsTaskWrapper> mWaitList = new ArrayList<>(); |
||||
private boolean isDownloadCmd; |
||||
private String sqlCondition; |
||||
|
||||
ResumeThread(boolean isDownload, String sqlCondition) { |
||||
this.isDownloadCmd = isDownload; |
||||
this.sqlCondition = sqlCondition; |
||||
} |
||||
|
||||
/** |
||||
* 查找数据库中的所有任务数据 |
||||
* |
||||
* @param type {@code 1}单任务下载任务;{@code 2}任务组下载任务;{@code 3} 单任务上传任务 |
||||
*/ |
||||
private void findTaskData(int type) { |
||||
if (type == 1) { |
||||
List<DownloadEntity> entities = |
||||
DbEntity.findDatas(DownloadEntity.class, |
||||
String.format("NOT(isGroupChild) AND NOT(isComplete) AND %s ORDER BY stopTime DESC", |
||||
sqlCondition)); |
||||
if (entities != null && !entities.isEmpty()) { |
||||
for (DownloadEntity entity : entities) { |
||||
addResumeEntity(TaskWrapperManager.getInstance() |
||||
.getNormalTaskWrapper(DTaskWrapper.class, entity.getId())); |
||||
} |
||||
} |
||||
} else if (type == 2) { |
||||
List<DownloadGroupEntity> entities = |
||||
DbEntity.findDatas(DownloadGroupEntity.class, |
||||
String.format("NOT(isComplete) AND %s ORDER BY stopTime DESC", |
||||
sqlCondition)); |
||||
if (entities != null && !entities.isEmpty()) { |
||||
for (DownloadGroupEntity entity : entities) { |
||||
addResumeEntity( |
||||
TaskWrapperManager.getInstance() |
||||
.getGroupWrapper(DGTaskWrapper.class, entity.getId())); |
||||
} |
||||
} |
||||
} else if (type == 3) { |
||||
List<UploadEntity> entities = |
||||
DbEntity.findDatas(UploadEntity.class, |
||||
String.format("NOT(isComplete) AND %s ORDER BY stopTime DESC", |
||||
sqlCondition)); |
||||
if (entities != null && !entities.isEmpty()) { |
||||
for (UploadEntity entity : entities) { |
||||
addResumeEntity(TaskWrapperManager.getInstance() |
||||
.getNormalTaskWrapper(UTaskWrapper.class, entity.getId())); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 添加恢复实体 |
||||
*/ |
||||
private void addResumeEntity(AbsTaskWrapper te) { |
||||
if (te == null || te.getEntity() == null || TextUtils.isEmpty(te.getKey())) { |
||||
return; |
||||
} |
||||
mWaitList.add(te); |
||||
} |
||||
|
||||
/** |
||||
* 处理等待状态的任务 |
||||
*/ |
||||
private void resumeWaitTask() { |
||||
|
||||
if (mWaitList == null || mWaitList.isEmpty()) { |
||||
return; |
||||
} |
||||
List<AbsEntity> resumeEntities = new ArrayList<>(); |
||||
|
||||
for (AbsTaskWrapper wrapper : mWaitList) { |
||||
AbsTaskQueue queue = null; |
||||
if (wrapper instanceof DTaskWrapper) { |
||||
queue = DTaskQueue.getInstance(); |
||||
} else if (wrapper instanceof UTaskWrapper) { |
||||
queue = UTaskQueue.getInstance(); |
||||
} else if (wrapper instanceof DGTaskWrapper) { |
||||
queue = DGroupTaskQueue.getInstance(); |
||||
} |
||||
|
||||
if (queue == null) { |
||||
ALog.e(TAG, "任务类型错误"); |
||||
continue; |
||||
} |
||||
|
||||
if (wrapper.getEntity() == null || TextUtils.isEmpty(wrapper.getKey())) { |
||||
ALog.e(TAG, "任务实体为空或key为空"); |
||||
continue; |
||||
} |
||||
|
||||
AbsTask task = queue.getTask(wrapper.getKey()); |
||||
if (task != null) { |
||||
ALog.w(TAG, "任务已存在"); |
||||
continue; |
||||
} |
||||
|
||||
int maxTaskNum = queue.getMaxTaskNum(); |
||||
task = queue.createTask(wrapper); |
||||
if (task == null) { |
||||
continue; |
||||
} |
||||
|
||||
handleWrapper(wrapper); |
||||
|
||||
if (queue.getCurrentExePoolNum() < maxTaskNum) { |
||||
queue.startTask(task); |
||||
} else { |
||||
wrapper.getEntity().setState(IEntity.STATE_WAIT); |
||||
sendWaitState(task); |
||||
resumeEntities.add(wrapper.getEntity()); |
||||
} |
||||
} |
||||
if (!resumeEntities.isEmpty()) { |
||||
DbEntity.updateManyData(resumeEntities); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 处理ftp的wrapper |
||||
*/ |
||||
private void handleWrapper(AbsTaskWrapper wrapper) { |
||||
int requestType = wrapper.getRequestType(); |
||||
if (requestType == ITaskWrapper.D_FTP |
||||
|| requestType == ITaskWrapper.U_FTP |
||||
|| requestType == ITaskWrapper.D_FTP_DIR) { |
||||
wrapper.getOptionParams() |
||||
.setParams(IOptionConstant.ftpUrlEntity, |
||||
CommonUtil.getFtpUrlInfo(wrapper.getEntity().getKey())); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 发送等待状态 |
||||
*/ |
||||
private void sendWaitState(AbsTask task) { |
||||
if (task != null) { |
||||
task.getTaskWrapper().setState(IEntity.STATE_WAIT); |
||||
task.getOutHandler().obtainMessage(ISchedulers.WAIT, task).sendToTarget(); |
||||
} |
||||
} |
||||
|
||||
@Override public void run() { |
||||
if (isDownloadCmd) { |
||||
findTaskData(1); |
||||
findTaskData(2); |
||||
} else { |
||||
findTaskData(3); |
||||
} |
||||
resumeWaitTask(); |
||||
} |
||||
} |
@ -1,51 +0,0 @@ |
||||
/* |
||||
* 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.core.common; |
||||
|
||||
import androidx.annotation.CheckResult; |
||||
import com.arialyy.aria.core.common.controller.ControllerType; |
||||
import com.arialyy.aria.core.common.controller.FeatureController; |
||||
import com.arialyy.aria.core.inf.AbsTarget; |
||||
import com.arialyy.aria.core.inf.AbsTaskWrapper; |
||||
import com.arialyy.aria.util.CommonUtil; |
||||
|
||||
public abstract class BaseDelegate<TARGET extends AbsTarget> { |
||||
protected final String TAG; |
||||
protected TARGET mTarget; |
||||
protected AbsTaskWrapper mWrapper; |
||||
|
||||
public BaseDelegate(TARGET target, AbsTaskWrapper wrapper) { |
||||
TAG = CommonUtil.getClassName(getClass()); |
||||
mTarget = target; |
||||
mWrapper = wrapper; |
||||
} |
||||
|
||||
protected AbsTaskWrapper getTaskWrapper() { |
||||
return mWrapper; |
||||
} |
||||
|
||||
/** |
||||
* 使用对应等控制器,注意: |
||||
* 1、对于不存在的任务(第一次下载),只能使用{@link ControllerType#CREATE_CONTROLLER} |
||||
* 2、对于已存在的任务,只能使用{@link ControllerType#TASK_CONTROLLER} |
||||
* |
||||
* @param clazz {@link ControllerType#CREATE_CONTROLLER}、{@link ControllerType#TASK_CONTROLLER} |
||||
*/ |
||||
@CheckResult(suggest = Suggest.TASK_CONTROLLER) |
||||
public synchronized <T extends FeatureController> T controller(@ControllerType Class<T> clazz) { |
||||
return FeatureController.newInstance(clazz, getTaskWrapper()); |
||||
} |
||||
} |
@ -0,0 +1,495 @@ |
||||
/* |
||||
* 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.core.common; |
||||
|
||||
import android.text.TextUtils; |
||||
import com.arialyy.aria.core.FtpUrlEntity; |
||||
import com.arialyy.aria.core.IdEntity; |
||||
import com.arialyy.aria.core.ProtocolType; |
||||
import com.arialyy.aria.core.processor.IFtpUploadInterceptor; |
||||
import com.arialyy.aria.util.ALog; |
||||
import com.arialyy.aria.util.CheckUtil; |
||||
import java.text.DateFormatSymbols; |
||||
import java.util.Collection; |
||||
import java.util.Locale; |
||||
import java.util.Map; |
||||
import java.util.StringTokenizer; |
||||
import java.util.TreeMap; |
||||
|
||||
/** |
||||
* Created by laoyuyu on 2018/3/9. |
||||
*/ |
||||
public class FtpOption extends BaseOption { |
||||
|
||||
private String charSet, userName, password, account; |
||||
private boolean isNeedLogin = false; |
||||
private FtpUrlEntity urlEntity; |
||||
private String protocol; |
||||
private boolean isImplicit = true; |
||||
private IFtpUploadInterceptor uploadInterceptor; |
||||
private int connMode = FtpConnectionMode.DATA_CONNECTION_MODE_PASV; |
||||
private int minPort, maxPort; |
||||
private String activeExternalIPAddress; |
||||
|
||||
//---------------- ftp client 配置信息 start
|
||||
private String defaultDateFormatStr = null; |
||||
private String recentDateFormatStr = null; |
||||
private String serverLanguageCode = null; |
||||
private String shortMonthNames = null; |
||||
private String serverTimeZoneId = null; |
||||
private String systemKey = FTPServerIdentifier.SYST_UNIX; |
||||
//---------------- ftp client 配置信息 end
|
||||
private IdEntity idEntity = new IdEntity(); |
||||
|
||||
public FtpOption() { |
||||
super(); |
||||
} |
||||
|
||||
public FtpOption charSet(String charSet) { |
||||
if (TextUtils.isEmpty(charSet)) { |
||||
throw new NullPointerException("字符编码为空"); |
||||
} |
||||
this.charSet = charSet; |
||||
return this; |
||||
} |
||||
|
||||
public FtpOption login(String userName, String password) { |
||||
return login(userName, password, null); |
||||
} |
||||
|
||||
public FtpOption login(String userName, String password, String account) { |
||||
if (TextUtils.isEmpty(userName)) { |
||||
ALog.e(TAG, "用户名不能为null"); |
||||
return this; |
||||
} else if (TextUtils.isEmpty(password)) { |
||||
ALog.e(TAG, "密码不能为null"); |
||||
return this; |
||||
} |
||||
this.userName = userName; |
||||
this.password = password; |
||||
this.account = account; |
||||
isNeedLogin = true; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 设置协议类型 |
||||
* |
||||
* @param protocol {@link ProtocolType} |
||||
*/ |
||||
public FtpOption setProtocol(String protocol) { |
||||
if (TextUtils.isEmpty(protocol)) { |
||||
ALog.e(TAG, "设置协议失败,协议信息为空"); |
||||
return this; |
||||
} |
||||
this.protocol = protocol; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 设置私钥证书别名 |
||||
* |
||||
* @param keyAlias 别名 |
||||
*/ |
||||
public FtpOption setAlias(String keyAlias) { |
||||
if (TextUtils.isEmpty(keyAlias)) { |
||||
ALog.e(TAG, "设置证书别名失败,证书别名为空"); |
||||
return this; |
||||
} |
||||
idEntity.keyAlias = keyAlias; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 设置ca证书密码 |
||||
* |
||||
* @param storePass ca证书密码 |
||||
*/ |
||||
public FtpOption setStorePass(String storePass) { |
||||
if (TextUtils.isEmpty(storePass)) { |
||||
ALog.e(TAG, "设置证书密码失败,证书密码为空"); |
||||
return this; |
||||
} |
||||
idEntity.storePass = storePass; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 设置cer证书路径 |
||||
* |
||||
* @param storePath 证书路径 |
||||
*/ |
||||
public FtpOption setStorePath(String storePath) { |
||||
if (TextUtils.isEmpty(storePath)) { |
||||
ALog.e(TAG, "设置证书路径失败,证书路径为空"); |
||||
return this; |
||||
} |
||||
idEntity.storePath = storePath; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 设置安全模式,默认true |
||||
* |
||||
* @param isImplicit true 隐式,false 显式 |
||||
*/ |
||||
public FtpOption setImplicit(boolean isImplicit) { |
||||
this.isImplicit = isImplicit; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* FTP文件上传拦截器,如果远端已有同名文件,可使用该拦截器控制覆盖文件或修改该文件上传到服务器端端文件名 |
||||
*/ |
||||
public FtpOption setUploadInterceptor(IFtpUploadInterceptor uploadInterceptor) { |
||||
if (uploadInterceptor == null) { |
||||
throw new NullPointerException("ftp拦截器为空"); |
||||
} |
||||
CheckUtil.checkMemberClass(uploadInterceptor.getClass()); |
||||
this.uploadInterceptor = uploadInterceptor; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 设置数据传输模式,默认为被动模式。 |
||||
* 主动模式:传输文件时,客户端开启一个端口,ftp服务器连接到客户端的该端口,ftp服务器推送数据到客户端 |
||||
* 被动模式:传输文件时,ftp服务器开启一个端口,客户端连接到ftp服务器的这个端口,客户端请求ftp服务器的数据 |
||||
* 请注意:主动模式是服务器主动连接到android,如果使用住的模式,请确保ftp服务器能ping通android |
||||
* |
||||
* @param connMode {@link FtpConnectionMode#DATA_CONNECTION_MODE_PASV}, |
||||
* {@link FtpConnectionMode#DATA_CONNECTION_MODE_ACTIVITY} |
||||
*/ |
||||
public FtpOption setConnectionMode(int connMode) { |
||||
if (connMode != FtpConnectionMode.DATA_CONNECTION_MODE_PASV |
||||
&& connMode != FtpConnectionMode.DATA_CONNECTION_MODE_ACTIVITY) { |
||||
ALog.e(TAG, "连接模式设置失败,默认启用被动模式"); |
||||
return this; |
||||
} |
||||
this.connMode = connMode; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 主动模式下的端口范围 |
||||
*/ |
||||
public FtpOption setActivePortRange(int minPort, int maxPort) { |
||||
|
||||
if (minPort > maxPort) { |
||||
ALog.e(TAG, "设置端口范围错误,minPort > maxPort"); |
||||
return this; |
||||
} |
||||
if (minPort <= 0 || minPort >= 65535) { |
||||
ALog.e(TAG, "端口范围错误"); |
||||
return this; |
||||
} |
||||
if (maxPort >= 65535) { |
||||
ALog.e(TAG, "端口范围错误"); |
||||
return this; |
||||
} |
||||
this.minPort = minPort; |
||||
this.maxPort = maxPort; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 主动模式下,对外ip(可被Ftp服务器访问的ip) |
||||
*/ |
||||
public FtpOption setActiveExternalIPAddress(String ip) { |
||||
if (TextUtils.isEmpty(ip)) { |
||||
ALog.e(TAG, "ip为空"); |
||||
return this; |
||||
} |
||||
if (!CheckUtil.checkIp(ip)) { |
||||
ALog.e(TAG, "ip地址错误:" + ip); |
||||
return this; |
||||
} |
||||
this.activeExternalIPAddress = ip; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 设置ftp服务器所在的操作系统的标志,如果出现文件获取失败,请设置该标志为 |
||||
* 默认使用{@link FTPServerIdentifier#SYST_UNIX} |
||||
* |
||||
* @param identifier {@link FTPServerIdentifier} |
||||
*/ |
||||
public FtpOption setServerIdentifier(String identifier) { |
||||
this.systemKey = identifier; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 解析ftp信息时,默认的文件日期格式,如:setDefaultDateFormatStr("d MMM yyyy") |
||||
* |
||||
* @param defaultDateFormatStr 日期格式 |
||||
*/ |
||||
public FtpOption setDefaultDateFormatStr(String defaultDateFormatStr) { |
||||
this.defaultDateFormatStr = defaultDateFormatStr; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 解析ftp信息时,默认的文件修改日期格式,如:setRecentDateFormatStr("d MMM HH:mm") |
||||
* |
||||
* @param recentDateFormatStr 日期格式 |
||||
*/ |
||||
public FtpOption setRecentDateFormatStr(String recentDateFormatStr) { |
||||
this.recentDateFormatStr = recentDateFormatStr; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 设置服务器使用的时区,java.util.TimeZone,如:America/Chicago or Asia/Rangoon. |
||||
* |
||||
* @param serverTimeZoneId 时区 |
||||
*/ |
||||
public void setServerTimeZoneId(String serverTimeZoneId) { |
||||
this.serverTimeZoneId = serverTimeZoneId; |
||||
} |
||||
|
||||
/** |
||||
* <p> |
||||
* setter for the shortMonthNames property. |
||||
* This property allows the user to specify a set of month names |
||||
* used by the server that is different from those that may be |
||||
* specified using the {@link #setServerLanguageCode(String) serverLanguageCode} |
||||
* property. |
||||
* </p><p> |
||||
* This should be a string containing twelve strings each composed of |
||||
* three characters, delimited by pipe (|) characters. Currently, |
||||
* only 8-bit ASCII characters are known to be supported. For example, |
||||
* a set of month names used by a hypothetical Icelandic FTP server might |
||||
* conceivably be specified as |
||||
* <code>"jan|feb|mar|apr|maí|jún|júl|ágú|sep|okt|nóv|des"</code>. |
||||
* </p> |
||||
* |
||||
* @param shortMonthNames The value to set to the shortMonthNames property. |
||||
*/ |
||||
public void setShortMonthNames(String shortMonthNames) { |
||||
this.shortMonthNames = shortMonthNames; |
||||
} |
||||
|
||||
/** |
||||
* 设置服务器语言代码 |
||||
* |
||||
* @param serverLanguageCode {@link #LANGUAGE_CODE_MAP} |
||||
*/ |
||||
public FtpOption setServerLanguageCode(String serverLanguageCode) { |
||||
this.serverLanguageCode = serverLanguageCode; |
||||
return this; |
||||
} |
||||
|
||||
public void setUrlEntity(FtpUrlEntity urlEntity) { |
||||
this.urlEntity = urlEntity; |
||||
urlEntity.needLogin = isNeedLogin; |
||||
urlEntity.user = userName; |
||||
urlEntity.password = password; |
||||
urlEntity.account = account; |
||||
urlEntity.idEntity = idEntity; |
||||
if (!TextUtils.isEmpty(idEntity.storePath) || !TextUtils.isEmpty(idEntity.prvKey)) { |
||||
urlEntity.isFtps = true; |
||||
urlEntity.protocol = protocol; |
||||
urlEntity.isImplicit = isImplicit; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* ftp 服务器所在的操作系统标志 |
||||
*/ |
||||
public interface FTPServerIdentifier { |
||||
/** |
||||
* Identifier by which a unix-based ftp server is known throughout |
||||
* the commons-net ftp system. |
||||
*/ |
||||
String SYST_UNIX = "UNIX"; |
||||
|
||||
/** |
||||
* Identifier for alternate UNIX parser; same as {@link #SYST_UNIX} but leading spaces are |
||||
* trimmed from file names. This is to maintain backwards compatibility with |
||||
* the original behaviour of the parser which ignored multiple spaces between the date |
||||
* and the start of the file name. |
||||
* |
||||
* @since 3.4 |
||||
*/ |
||||
String SYST_UNIX_TRIM_LEADING = "UNIX_LTRIM"; |
||||
|
||||
/** |
||||
* Identifier by which a vms-based ftp server is known throughout |
||||
* the commons-net ftp system. |
||||
*/ |
||||
String SYST_VMS = "VMS"; |
||||
|
||||
/** |
||||
* Identifier by which a WindowsNT-based ftp server is known throughout |
||||
* the commons-net ftp system. |
||||
*/ |
||||
String SYST_NT = "WINDOWS"; |
||||
|
||||
/** |
||||
* Identifier by which an OS/2-based ftp server is known throughout |
||||
* the commons-net ftp system. |
||||
*/ |
||||
String SYST_OS2 = "OS/2"; |
||||
|
||||
/** |
||||
* Identifier by which an OS/400-based ftp server is known throughout |
||||
* the commons-net ftp system. |
||||
*/ |
||||
String SYST_OS400 = "OS/400"; |
||||
|
||||
/** |
||||
* Identifier by which an AS/400-based ftp server is known throughout |
||||
* the commons-net ftp system. |
||||
*/ |
||||
String SYST_AS400 = "AS/400"; |
||||
|
||||
/** |
||||
* Identifier by which an MVS-based ftp server is known throughout |
||||
* the commons-net ftp system. |
||||
*/ |
||||
String SYST_MVS = "MVS"; |
||||
|
||||
/** |
||||
* Some servers return an "UNKNOWN Type: L8" message |
||||
* in response to the SYST command. We set these to be a Unix-type system. |
||||
* This may happen if the ftpd in question was compiled without system |
||||
* information. |
||||
* |
||||
* NET-230 - Updated to be UPPERCASE so that the check done in |
||||
* createFileEntryParser will succeed. |
||||
* |
||||
* @since 1.5 |
||||
*/ |
||||
String SYST_L8 = "TYPE: L8"; |
||||
|
||||
/** |
||||
* Identifier by which an Netware-based ftp server is known throughout |
||||
* the commons-net ftp system. |
||||
* |
||||
* @since 1.5 |
||||
*/ |
||||
String SYST_NETWARE = "NETWARE"; |
||||
|
||||
/** |
||||
* Identifier by which a Mac pre OS-X -based ftp server is known throughout |
||||
* the commons-net ftp system. |
||||
* |
||||
* @since 3.1 |
||||
*/ |
||||
// Full string is "MACOS Peter's Server"; the substring below should be enough
|
||||
String SYST_MACOS_PETER = "MACOS PETER"; // NET-436
|
||||
} |
||||
|
||||
/** |
||||
* 支持的语言代码 |
||||
*/ |
||||
private static final Map<String, Object> LANGUAGE_CODE_MAP = new TreeMap<>(); |
||||
|
||||
static { |
||||
|
||||
// if there are other commonly used month name encodings which
|
||||
// correspond to particular locales, please add them here.
|
||||
|
||||
// many locales code short names for months as all three letters
|
||||
// these we handle simply.
|
||||
LANGUAGE_CODE_MAP.put("en", Locale.ENGLISH); |
||||
LANGUAGE_CODE_MAP.put("de", Locale.GERMAN); |
||||
LANGUAGE_CODE_MAP.put("it", Locale.ITALIAN); |
||||
LANGUAGE_CODE_MAP.put("es", new Locale("es", "", "")); // spanish
|
||||
LANGUAGE_CODE_MAP.put("pt", new Locale("pt", "", "")); // portuguese
|
||||
LANGUAGE_CODE_MAP.put("da", new Locale("da", "", "")); // danish
|
||||
LANGUAGE_CODE_MAP.put("sv", new Locale("sv", "", "")); // swedish
|
||||
LANGUAGE_CODE_MAP.put("no", new Locale("no", "", "")); // norwegian
|
||||
LANGUAGE_CODE_MAP.put("nl", new Locale("nl", "", "")); // dutch
|
||||
LANGUAGE_CODE_MAP.put("ro", new Locale("ro", "", "")); // romanian
|
||||
LANGUAGE_CODE_MAP.put("sq", new Locale("sq", "", "")); // albanian
|
||||
LANGUAGE_CODE_MAP.put("sh", new Locale("sh", "", "")); // serbo-croatian
|
||||
LANGUAGE_CODE_MAP.put("sk", new Locale("sk", "", "")); // slovak
|
||||
LANGUAGE_CODE_MAP.put("sl", new Locale("sl", "", "")); // slovenian
|
||||
|
||||
// some don't
|
||||
LANGUAGE_CODE_MAP.put("fr", |
||||
"jan|f\u00e9v|mar|avr|mai|jun|jui|ao\u00fb|sep|oct|nov|d\u00e9c"); //french
|
||||
} |
||||
|
||||
/** |
||||
* Looks up the supplied language code in the internally maintained table of |
||||
* language codes. Returns a DateFormatSymbols object configured with |
||||
* short month names corresponding to the code. If there is no corresponding |
||||
* entry in the table, the object returned will be that for |
||||
* <code>Locale.US</code> |
||||
* |
||||
* @param languageCode See {@link #setServerLanguageCode(String) serverLanguageCode} |
||||
* @return a DateFormatSymbols object configured with short month names |
||||
* corresponding to the supplied code, or with month names for |
||||
* <code>Locale.US</code> if there is no corresponding entry in the internal |
||||
* table. |
||||
*/ |
||||
public static DateFormatSymbols lookupDateFormatSymbols(String languageCode) { |
||||
Object lang = LANGUAGE_CODE_MAP.get(languageCode); |
||||
if (lang != null) { |
||||
if (lang instanceof Locale) { |
||||
return new DateFormatSymbols((Locale) lang); |
||||
} else if (lang instanceof String) { |
||||
return getDateFormatSymbols((String) lang); |
||||
} |
||||
} |
||||
return new DateFormatSymbols(Locale.US); |
||||
} |
||||
|
||||
/** |
||||
* Returns a DateFormatSymbols object configured with short month names |
||||
* as in the supplied string |
||||
* |
||||
* @param shortmonths This should be as described in |
||||
* {@link #setShortMonthNames(String) shortMonthNames} |
||||
* @return a DateFormatSymbols object configured with short month names |
||||
* as in the supplied string |
||||
*/ |
||||
public static DateFormatSymbols getDateFormatSymbols(String shortmonths) { |
||||
String[] months = splitShortMonthString(shortmonths); |
||||
DateFormatSymbols dfs = new DateFormatSymbols(Locale.US); |
||||
dfs.setShortMonths(months); |
||||
return dfs; |
||||
} |
||||
|
||||
private static String[] splitShortMonthString(String shortmonths) { |
||||
StringTokenizer st = new StringTokenizer(shortmonths, "|"); |
||||
int monthcnt = st.countTokens(); |
||||
if (12 != monthcnt) { |
||||
throw new IllegalArgumentException("expecting a pipe-delimited string containing 12 tokens"); |
||||
} |
||||
String[] months = new String[13]; |
||||
int pos = 0; |
||||
while (st.hasMoreTokens()) { |
||||
months[pos++] = st.nextToken(); |
||||
} |
||||
months[pos] = ""; |
||||
return months; |
||||
} |
||||
|
||||
/** |
||||
* Returns a Collection of all the language codes currently supported |
||||
* by this class. See {@link #setServerLanguageCode(String) serverLanguageCode} |
||||
* for a functional descrption of language codes within this system. |
||||
* |
||||
* @return a Collection of all the language codes currently supported |
||||
* by this class
|
||||
*/ |
||||
public static Collection<String> getSupportedLanguageCodes() { |
||||
return LANGUAGE_CODE_MAP.keySet(); |
||||
} |
||||
} |
@ -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.core.common; |
||||
|
||||
import android.text.TextUtils; |
||||
import com.arialyy.aria.core.processor.IHttpFileLenAdapter; |
||||
import com.arialyy.aria.core.processor.IHttpFileNameAdapter; |
||||
import com.arialyy.aria.util.ALog; |
||||
import com.arialyy.aria.util.CheckUtil; |
||||
import java.net.Proxy; |
||||
import java.util.HashMap; |
||||
import java.util.Map; |
||||
|
||||
/** |
||||
* HTTP任务设置 |
||||
*/ |
||||
public class HttpOption extends BaseOption { |
||||
|
||||
private Map<String, String> params; |
||||
private Map<String, String> headers; |
||||
private RequestEnum requestEnum = RequestEnum.GET; |
||||
private Map<String, String> formFields; |
||||
private Proxy proxy; |
||||
private boolean useServerFileName = false; |
||||
private IHttpFileLenAdapter fileLenAdapter; |
||||
private IHttpFileNameAdapter fileNameAdapter; |
||||
private String attachment; |
||||
|
||||
public HttpOption() { |
||||
super(); |
||||
} |
||||
|
||||
/** |
||||
* 设置请求类型 |
||||
* |
||||
* @param requestEnum {@link RequestEnum} |
||||
*/ |
||||
public HttpOption setRequestType(RequestEnum requestEnum) { |
||||
this.requestEnum = requestEnum; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 设置http请求参数 |
||||
*/ |
||||
public HttpOption setParams(Map<String, String> params) { |
||||
if (this.params == null) { |
||||
this.params = new HashMap<>(); |
||||
} |
||||
this.params.putAll(params); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 设置http请求参数 |
||||
*/ |
||||
public HttpOption setParam(String key, String value) { |
||||
if (TextUtils.isEmpty(key) || TextUtils.isEmpty(value)) { |
||||
ALog.d(TAG, "key 或value 为空"); |
||||
return this; |
||||
} |
||||
if (params == null) { |
||||
params = new HashMap<>(); |
||||
} |
||||
params.put(key, value); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 设置http表单字段 |
||||
*/ |
||||
public HttpOption setFormFields(Map<String, String> formFields) { |
||||
this.formFields = formFields; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 设置文件上传需要的key |
||||
* |
||||
* @param attachment 如果为空,默认为"file" |
||||
*/ |
||||
public HttpOption setAttachment(String attachment) { |
||||
if (TextUtils.isEmpty(attachment)) { |
||||
attachment = "file"; |
||||
} |
||||
this.attachment = attachment; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 给url请求添加Header数据 |
||||
* 如果新的header数据和数据保存的不一致,则更新数据库中对应的header数据 |
||||
* |
||||
* @param key header对应的key |
||||
* @param value header对应的value |
||||
*/ |
||||
public HttpOption addHeader(String key, String value) { |
||||
if (TextUtils.isEmpty(key)) { |
||||
ALog.w(TAG, "设置header失败,header对应的key不能为null"); |
||||
return this; |
||||
} else if (TextUtils.isEmpty(value)) { |
||||
ALog.w(TAG, "设置header失败,header对应的value不能为null"); |
||||
return this; |
||||
} |
||||
if (this.headers == null) { |
||||
this.headers = new HashMap<>(); |
||||
} |
||||
this.headers.put(key, value); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 给url请求添加一组header数据 |
||||
* 如果新的header数据和数据保存的不一致,则更新数据库中对应的header数据 |
||||
* |
||||
* @param headers 一组http header数据 |
||||
*/ |
||||
public HttpOption addHeaders(Map<String, String> headers) { |
||||
if (headers.size() == 0) { |
||||
ALog.w(TAG, "设置header失败,map没有header数据"); |
||||
return this; |
||||
} |
||||
if (this.headers == null) { |
||||
this.headers = new HashMap<>(); |
||||
} |
||||
this.headers.putAll(headers); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 设置代理 |
||||
*/ |
||||
public HttpOption setUrlProxy(Proxy proxy) { |
||||
this.proxy = proxy; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 是否使用服务器通过content-disposition传递的文件名,内容格式{@code attachment;filename=***} |
||||
* 如果获取不到服务器文件名,则使用用户设置的文件名 |
||||
* |
||||
* @param use {@code true} 使用 |
||||
*/ |
||||
public HttpOption useServerFileName(boolean use) { |
||||
this.useServerFileName = use; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 如果你需要使用header中特定的key来设置文件长度,或有定制文件长度的需要,那么你可以通过该方法自行处理文件长度 |
||||
*/ |
||||
public HttpOption setFileLenAdapter(IHttpFileLenAdapter fileLenAdapter) { |
||||
if (fileLenAdapter == null) { |
||||
throw new IllegalArgumentException("adapter为空"); |
||||
} |
||||
CheckUtil.checkMemberClass(fileLenAdapter.getClass()); |
||||
this.fileLenAdapter = fileLenAdapter; |
||||
return this; |
||||
} |
||||
public HttpOption setFilNameAdapter(IHttpFileNameAdapter fileNameAdapter) { |
||||
if (fileNameAdapter == null) { |
||||
throw new IllegalArgumentException("adapter为空"); |
||||
} |
||||
CheckUtil.checkMemberClass(fileNameAdapter.getClass()); |
||||
this.fileNameAdapter = fileNameAdapter; |
||||
return this; |
||||
} |
||||
} |
@ -1,199 +0,0 @@ |
||||
/* |
||||
* 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.core.common; |
||||
|
||||
import android.os.Handler; |
||||
import android.os.Looper; |
||||
import com.arialyy.aria.core.download.BaseDListener; |
||||
import com.arialyy.aria.core.event.EventMsgUtil; |
||||
import com.arialyy.aria.core.inf.AbsNormalEntity; |
||||
import com.arialyy.aria.core.inf.AbsTaskWrapper; |
||||
import com.arialyy.aria.core.inf.IEventListener; |
||||
import com.arialyy.aria.core.manager.ThreadTaskManager; |
||||
import com.arialyy.aria.util.ALog; |
||||
import java.io.File; |
||||
|
||||
public abstract class NormalFileer<ENTITY extends AbsNormalEntity, TASK_WRAPPER extends AbsTaskWrapper<ENTITY>> |
||||
extends AbsFileer<ENTITY, TASK_WRAPPER> { |
||||
private ThreadStateManager mStateManager; |
||||
private Handler mStateHandler; |
||||
protected int mTotalThreadNum; //总线程数
|
||||
private int mStartThreadNum; //启动的线程数
|
||||
|
||||
protected NormalFileer(IEventListener listener, TASK_WRAPPER wrapper) { |
||||
super(listener, wrapper); |
||||
EventMsgUtil.getDefault().register(this); |
||||
} |
||||
|
||||
/** |
||||
* 处理新任务 |
||||
* |
||||
* @return {@code true}创建新任务成功 |
||||
*/ |
||||
protected abstract boolean handleNewTask(); |
||||
|
||||
/** |
||||
* 选择单任务线程的类型 |
||||
*/ |
||||
protected abstract AbsThreadTask selectThreadTask(SubThreadConfig<TASK_WRAPPER> config); |
||||
|
||||
/** |
||||
* 设置最大下载/上传速度 |
||||
* |
||||
* @param maxSpeed 单位为:kb |
||||
*/ |
||||
protected void setMaxSpeed(int maxSpeed) { |
||||
for (int i = 0; i < getTaskList().size(); i++) { |
||||
AbsThreadTask task = getTaskList().valueAt(i); |
||||
if (task != null && mStartThreadNum > 0) { |
||||
task.setMaxSpeed(maxSpeed / mStartThreadNum); |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Override public void onDestroy() { |
||||
super.onDestroy(); |
||||
EventMsgUtil.getDefault().unRegister(this); |
||||
} |
||||
|
||||
@Override protected void onPostPre() { |
||||
super.onPostPre(); |
||||
mTotalThreadNum = mRecord.threadNum; |
||||
} |
||||
|
||||
@Override protected IThreadState getStateManager(Looper looper) { |
||||
mStateManager = new ThreadStateManager(looper, mRecord, mListener); |
||||
mStateHandler = new Handler(looper, mStateManager); |
||||
return mStateManager; |
||||
} |
||||
|
||||
@Override protected void handleTask() { |
||||
if (mTaskWrapper.isSupportBP()) { |
||||
handleBreakpoint(); |
||||
} else { |
||||
handleNoSupportBP(); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 启动断点任务时,创建单线程任务 |
||||
* |
||||
* @param record 线程记录 |
||||
* @param startNum 启动的线程数 |
||||
*/ |
||||
private AbsThreadTask createSingThreadTask(ThreadRecord record, int startNum) { |
||||
SubThreadConfig<TASK_WRAPPER> config = new SubThreadConfig<>(); |
||||
config.url = mEntity.isRedirect() ? mEntity.getRedirectUrl() : mEntity.getUrl(); |
||||
config.tempFile = |
||||
mRecord.isBlock ? new File( |
||||
String.format(RecordHandler.SUB_PATH, mTempFile.getPath(), record.threadId)) |
||||
: mTempFile; |
||||
config.isBlock = mRecord.isBlock; |
||||
config.isOpenDynamicFile = mRecord.isOpenDynamicFile; |
||||
config.startThreadNum = startNum; |
||||
config.taskWrapper = mTaskWrapper; |
||||
config.record = record; |
||||
config.stateHandler = mStateHandler; |
||||
return selectThreadTask(config); |
||||
} |
||||
|
||||
private void handleBreakpoint() { |
||||
long fileLength = mEntity.getFileSize(); |
||||
long blockSize = fileLength / mTotalThreadNum; |
||||
long currentProgress = 0; |
||||
|
||||
mRecord.fileLength = fileLength; |
||||
if (mTaskWrapper.isNewTask() && !handleNewTask()) { |
||||
return; |
||||
} |
||||
|
||||
for (ThreadRecord tr : mRecord.threadRecords) { |
||||
if (!tr.isComplete) { |
||||
mStartThreadNum++; |
||||
} |
||||
} |
||||
|
||||
for (int i = 0; i < mTotalThreadNum; i++) { |
||||
long startL = i * blockSize, endL = (i + 1) * blockSize; |
||||
ThreadRecord tr = mRecord.threadRecords.get(i); |
||||
|
||||
if (tr.isComplete) {//该线程已经完成
|
||||
currentProgress += endL - startL; |
||||
ALog.d(TAG, String.format("任务【%s】线程__%s__已完成", mTaskWrapper.getEntity().getFileName(), i)); |
||||
mStateHandler.obtainMessage(IThreadState.STATE_COMPLETE).sendToTarget(); |
||||
if (mStateManager.isComplete()) { |
||||
mRecord.deleteData(); |
||||
mListener.onComplete(); |
||||
return; |
||||
} |
||||
continue; |
||||
} |
||||
|
||||
//如果有记录,则恢复任务
|
||||
long r = tr.startLocation; |
||||
//记录的位置需要在线程区间中
|
||||
if (startL < r && r <= (i == (mTotalThreadNum - 1) ? fileLength : endL)) { |
||||
currentProgress += r - startL; |
||||
} |
||||
ALog.d(TAG, String.format("任务【%s】线程__%s__恢复任务", mEntity.getFileName(), i)); |
||||
|
||||
AbsThreadTask task = createSingThreadTask(tr, mStartThreadNum); |
||||
if (task == null) return; |
||||
getTaskList().put(tr.threadId, task); |
||||
} |
||||
if (currentProgress != 0 && currentProgress != mEntity.getCurrentProgress()) { |
||||
ALog.d(TAG, String.format("进度修正,当前进度:%s", currentProgress)); |
||||
mEntity.setCurrentProgress(currentProgress); |
||||
} |
||||
mStateHandler.obtainMessage(IThreadState.STATE_UPDATE_PROGRESS, currentProgress) |
||||
.sendToTarget(); |
||||
startThreadTask(); |
||||
} |
||||
|
||||
/** |
||||
* 启动单线程任务 |
||||
*/ |
||||
private void startThreadTask() { |
||||
if (isBreak()) { |
||||
return; |
||||
} |
||||
if (mStateManager.getCurrentProgress() > 0) { |
||||
mListener.onResume(mStateManager.getCurrentProgress()); |
||||
} else { |
||||
mListener.onStart(mStateManager.getCurrentProgress()); |
||||
} |
||||
|
||||
for (int i = 0; i < getTaskList().size(); i++) { |
||||
ThreadTaskManager.getInstance().startThread(mTaskWrapper.getKey(), getTaskList().valueAt(i)); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 处理不支持断点的任务 |
||||
*/ |
||||
private void handleNoSupportBP() { |
||||
if (mListener instanceof BaseDListener) { |
||||
((BaseDListener) mListener).supportBreakpoint(false); |
||||
} |
||||
mStartThreadNum = 1; |
||||
|
||||
AbsThreadTask task = createSingThreadTask(mRecord.threadRecords.get(0), 1); |
||||
if (task == null) return; |
||||
getTaskList().put(0, task); |
||||
ThreadTaskManager.getInstance().startThread(mTaskWrapper.getKey(), task); |
||||
mListener.onStart(0); |
||||
} |
||||
} |
@ -1,457 +0,0 @@ |
||||
/* |
||||
* 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.core.common; |
||||
|
||||
import com.arialyy.aria.core.config.Configuration; |
||||
import com.arialyy.aria.core.download.DTaskWrapper; |
||||
import com.arialyy.aria.core.download.DownloadEntity; |
||||
import com.arialyy.aria.core.download.m3u8.BaseM3U8Loader; |
||||
import com.arialyy.aria.core.inf.AbsNormalEntity; |
||||
import com.arialyy.aria.core.inf.AbsTaskWrapper; |
||||
import com.arialyy.aria.core.inf.ITaskWrapper; |
||||
import com.arialyy.aria.core.upload.UploadEntity; |
||||
import com.arialyy.aria.orm.DbEntity; |
||||
import com.arialyy.aria.util.ALog; |
||||
import com.arialyy.aria.util.CommonUtil; |
||||
import com.arialyy.aria.util.DbDataHelper; |
||||
import com.arialyy.aria.util.FileUtil; |
||||
import com.arialyy.aria.util.RecordUtil; |
||||
import java.io.File; |
||||
import java.util.ArrayList; |
||||
import java.util.HashSet; |
||||
import java.util.List; |
||||
import java.util.Properties; |
||||
import java.util.Set; |
||||
|
||||
/** |
||||
* 处理任务记录,分配线程区间 |
||||
*/ |
||||
public class RecordHandler { |
||||
private final String TAG = "RecordHandler"; |
||||
|
||||
public static final int TYPE_DOWNLOAD = 1; |
||||
public static final int TYPE_UPLOAD = 2; |
||||
public static final int TYPE_M3U8_VOD = 3; |
||||
public static final int TYPE_M3U8_LIVE = 4; |
||||
|
||||
private static final String STATE = "_state_"; |
||||
private static final String RECORD = "_record_"; |
||||
/** |
||||
* 小于1m的文件不启用多线程 |
||||
*/ |
||||
private static final long SUB_LEN = 1024 * 1024; |
||||
|
||||
/** |
||||
* 分块文件路径 |
||||
*/ |
||||
public static final String SUB_PATH = "%s.%s.part"; |
||||
|
||||
@Deprecated private File mConfigFile; |
||||
private TaskRecord mTaskRecord; |
||||
private AbsTaskWrapper mTaskWrapper; |
||||
private AbsNormalEntity mEntity; |
||||
|
||||
RecordHandler(AbsTaskWrapper wrapper) { |
||||
mTaskWrapper = wrapper; |
||||
mEntity = (AbsNormalEntity) mTaskWrapper.getEntity(); |
||||
} |
||||
|
||||
/** |
||||
* 获取任务记录,如果任务记录存在,检查任务记录 |
||||
* 检查记录 对于分块任务: 子分块不存在或被删除,子线程将重新下载 |
||||
* 对于普通任务: 预下载文件不存在,则任务任务呗删除 |
||||
* 如果任务记录不存在或线程记录不存在,初始化记录 |
||||
* |
||||
* @return 任务记录 |
||||
*/ |
||||
TaskRecord getRecord() { |
||||
mConfigFile = new File(CommonUtil.getFileConfigPath(false, mEntity.getFileName())); |
||||
if (mConfigFile.exists()) { |
||||
convertDb(); |
||||
} else { |
||||
mTaskRecord = DbDataHelper.getTaskRecord(getFilePath()); |
||||
if (mTaskRecord == null) { |
||||
initRecord(true); |
||||
} else { |
||||
if (mTaskRecord.threadRecords == null || mTaskRecord.threadRecords.isEmpty()) { |
||||
initRecord(false); |
||||
} |
||||
|
||||
if (mTaskWrapper.getRequestType() == ITaskWrapper.M3U8_VOD) { |
||||
handleM3U8Record(); |
||||
} else if (mTaskWrapper.getRequestType() == ITaskWrapper.M3U8_LIVE) { |
||||
ALog.i(TAG, "直播下载不处理历史记录"); |
||||
} else { |
||||
if (mTaskRecord.isBlock) { |
||||
handleBlockRecord(); |
||||
} else if (!mTaskWrapper.isSupportBP()) { |
||||
handleNoSupportBPRecord(); |
||||
} else if (!mTaskRecord.isBlock && mTaskRecord.threadNum > 1) { |
||||
handleNoBlockMultiThreadRecord(); |
||||
} else { |
||||
handleSingleThreadRecord(); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
saveRecord(); |
||||
return mTaskRecord; |
||||
} |
||||
|
||||
/** |
||||
* 处理m3u8记录, |
||||
* 1、如果分片文件存在,并且分片文件的记录没有完成,则需要删除该分片文件 |
||||
* 2、如果记录显示已完成,但是分片文件不存在,则重新开始该分片 |
||||
* 3、如果记录显示已完成,并且文件存在,记录当前任务进度 |
||||
*/ |
||||
private void handleM3U8Record() { |
||||
DTaskWrapper wrapper = (DTaskWrapper) mTaskWrapper; |
||||
String cacheDir = wrapper.asM3U8().getCacheDir(); |
||||
long currentProgress = 0; |
||||
int completeNum = 0; |
||||
for (ThreadRecord record : mTaskRecord.threadRecords) { |
||||
File temp = new File(BaseM3U8Loader.getTsFilePath(cacheDir, record.threadId)); |
||||
if (!record.isComplete) { |
||||
if (temp.exists()) { |
||||
temp.delete(); |
||||
} |
||||
record.startLocation = 0; |
||||
//ALog.d(TAG, String.format("分片【%s】未完成,将重新下载该分片", record.threadId));
|
||||
} else { |
||||
if (!temp.exists()) { |
||||
record.startLocation = 0; |
||||
record.isComplete = false; |
||||
ALog.w(TAG, String.format("分片【%s】不存在,将重新下载该分片", record.threadId)); |
||||
} else { |
||||
completeNum++; |
||||
currentProgress += temp.length(); |
||||
} |
||||
} |
||||
} |
||||
wrapper.asM3U8().setCompleteNum(completeNum); |
||||
wrapper.getEntity().setCurrentProgress(currentProgress); |
||||
mTaskRecord.bandWidth = wrapper.asM3U8().getBandWidth(); |
||||
} |
||||
|
||||
/** |
||||
* 处理不支持断点的记录 |
||||
*/ |
||||
private void handleNoSupportBPRecord() { |
||||
ThreadRecord tr = mTaskRecord.threadRecords.get(0); |
||||
tr.startLocation = 0; |
||||
tr.endLocation = mEntity.getFileSize(); |
||||
tr.taskKey = mTaskRecord.filePath; |
||||
tr.blockLen = tr.endLocation; |
||||
tr.isComplete = false; |
||||
} |
||||
|
||||
/** |
||||
* 处理为不分块的多线程任务 |
||||
*/ |
||||
private void handleNoBlockMultiThreadRecord() { |
||||
File file = new File(mTaskRecord.filePath); |
||||
if (!file.exists()) { |
||||
ALog.w(TAG, String.format("文件【%s】不存在,重新分配线程区间", mTaskRecord.filePath)); |
||||
DbEntity.deleteData(ThreadRecord.class, "taskKey=?", mTaskRecord.filePath); |
||||
initRecord(false); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 处理单线程的任务的记录 |
||||
*/ |
||||
private void handleSingleThreadRecord() { |
||||
File file = new File(mTaskRecord.filePath); |
||||
ThreadRecord tr = mTaskRecord.threadRecords.get(0); |
||||
if (!file.exists()) { |
||||
ALog.w(TAG, String.format("文件【%s】不存在,任务将重新开始", file.getPath())); |
||||
tr.startLocation = 0; |
||||
tr.isComplete = false; |
||||
tr.endLocation = mEntity.getFileSize(); |
||||
} else if (mTaskRecord.isOpenDynamicFile) { |
||||
if (file.length() > mEntity.getFileSize()) { |
||||
ALog.i(TAG, String.format("文件【%s】错误,任务重新开始", file.getPath())); |
||||
file.delete(); |
||||
tr.startLocation = 0; |
||||
tr.isComplete = false; |
||||
tr.endLocation = mEntity.getFileSize(); |
||||
} else if (file.length() == mEntity.getFileSize()) { |
||||
tr.isComplete = true; |
||||
} else { |
||||
if (file.length() != tr.startLocation) { |
||||
ALog.i(TAG, String.format("修正【%s】的进度记录为:%s", file.getPath(), file.length())); |
||||
tr.startLocation = file.length(); |
||||
tr.isComplete = false; |
||||
} |
||||
} |
||||
} |
||||
mTaskWrapper.setNewTask(false); |
||||
} |
||||
|
||||
/** |
||||
* 处理分块任务的记录,分块文件(blockFileLen)长度必须需要小于等于线程区间(threadRectLen)的长度 |
||||
*/ |
||||
private void handleBlockRecord() { |
||||
// 默认线程分块长度
|
||||
long normalRectLen = mEntity.getFileSize() / mTaskRecord.threadRecords.size(); |
||||
for (ThreadRecord tr : mTaskRecord.threadRecords) { |
||||
long threadRect = tr.blockLen; |
||||
|
||||
File temp = new File(String.format(SUB_PATH, mTaskRecord.filePath, tr.threadId)); |
||||
if (!temp.exists()) { |
||||
ALog.i(TAG, String.format("分块文件【%s】不存在,该分块将重新开始", temp.getPath())); |
||||
tr.isComplete = false; |
||||
tr.startLocation = tr.threadId * normalRectLen; |
||||
} else { |
||||
if (!tr.isComplete) { |
||||
ALog.i(TAG, String.format( |
||||
"startLocation = %s; endLocation = %s; block = %s; tempLen = %s; threadId = %s", |
||||
tr.startLocation, tr.endLocation, threadRect, temp.length(), tr.threadId)); |
||||
|
||||
long blockFileLen = temp.length(); // 磁盘中的分块文件长度
|
||||
/* |
||||
* 检查磁盘中的分块文件 |
||||
*/ |
||||
if (blockFileLen > threadRect) { |
||||
ALog.i(TAG, String.format("分块【%s】错误,分块长度【%s】 > 线程区间长度【%s】,将重新开始该分块", |
||||
tr.threadId, blockFileLen, threadRect)); |
||||
temp.delete(); |
||||
tr.startLocation = tr.threadId * threadRect; |
||||
continue; |
||||
} |
||||
|
||||
long realLocation = |
||||
tr.threadId * normalRectLen + blockFileLen; //正常情况下,该线程的startLocation的位置
|
||||
/* |
||||
* 检查记录文件 |
||||
*/ |
||||
if (blockFileLen == threadRect) { |
||||
ALog.i(TAG, String.format("分块【%s】已完成,更新记录", temp.getPath())); |
||||
tr.startLocation = blockFileLen; |
||||
tr.isComplete = true; |
||||
} else if (tr.startLocation != realLocation) { // 处理记录小于分块文件长度的情况
|
||||
ALog.i(TAG, String.format("修正分块【%s】的进度记录为:%s", temp.getPath(), realLocation)); |
||||
tr.startLocation = realLocation; |
||||
} |
||||
} else { |
||||
ALog.i(TAG, String.format("分块【%s】已完成", temp.getPath())); |
||||
} |
||||
} |
||||
} |
||||
mTaskWrapper.setNewTask(false); |
||||
} |
||||
|
||||
/** |
||||
* convertDb 是兼容性代码 从3.4.1开始,线程配置信息将存储在数据库中。 将配置文件的内容复制到数据库中,并将配置文件删除 |
||||
*/ |
||||
private void convertDb() { |
||||
List<RecordWrapper> records = |
||||
DbEntity.findRelationData(RecordWrapper.class, "TaskRecord.filePath=?", |
||||
getFilePath()); |
||||
if (records == null || records.size() == 0) { |
||||
Properties pro = FileUtil.loadConfig(mConfigFile); |
||||
if (pro.isEmpty()) { |
||||
ALog.d(TAG, "老版本的线程记录为空,任务为新任务"); |
||||
initRecord(true); |
||||
return; |
||||
} |
||||
|
||||
Set<Object> keys = pro.keySet(); |
||||
// 老版本记录是5s存一次,但是5s中内,如果线程执行完成,record记录是没有的,只有state记录...
|
||||
// 第一步应该是record 和 state去重取正确的线程数
|
||||
Set<Integer> set = new HashSet<>(); |
||||
for (Object key : keys) { |
||||
String str = String.valueOf(key); |
||||
int i = Integer.parseInt(str.substring(str.length() - 1)); |
||||
set.add(i); |
||||
} |
||||
int threadNum = set.size(); |
||||
if (threadNum == 0) { |
||||
ALog.d(TAG, "线程数为空,任务为新任务"); |
||||
initRecord(true); |
||||
return; |
||||
} |
||||
mTaskWrapper.setNewTask(false); |
||||
mTaskRecord = createTaskRecord(threadNum); |
||||
mTaskRecord.isOpenDynamicFile = false; |
||||
mTaskRecord.isBlock = false; |
||||
File tempFile = new File(getFilePath()); |
||||
for (int i = 0; i < threadNum; i++) { |
||||
ThreadRecord tRecord = new ThreadRecord(); |
||||
tRecord.taskKey = mTaskRecord.filePath; |
||||
Object state = pro.getProperty(tempFile.getName() + STATE + i); |
||||
Object record = pro.getProperty(tempFile.getName() + RECORD + i); |
||||
if (state != null && Integer.parseInt(String.valueOf(state)) == 1) { |
||||
tRecord.isComplete = true; |
||||
continue; |
||||
} |
||||
if (record != null) { |
||||
long temp = Long.parseLong(String.valueOf(record)); |
||||
tRecord.startLocation = temp > 0 ? temp : 0; |
||||
} else { |
||||
tRecord.startLocation = 0; |
||||
} |
||||
mTaskRecord.threadRecords.add(tRecord); |
||||
} |
||||
mConfigFile.delete(); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 初始化任务记录,分配线程区间 |
||||
* |
||||
* @param newRecord {@code true} 需要创建新{@link TaskRecord} |
||||
*/ |
||||
private void initRecord(boolean newRecord) { |
||||
if (newRecord) { |
||||
mTaskRecord = createTaskRecord(getNewTaskThreadNum()); |
||||
} |
||||
mTaskWrapper.setNewTask(true); |
||||
int requestType = mTaskWrapper.getRequestType(); |
||||
if (requestType == ITaskWrapper.M3U8_LIVE) { |
||||
return; |
||||
} |
||||
long blockSize = mEntity.getFileSize() / mTaskRecord.threadNum; |
||||
// 处理线程区间记录
|
||||
for (int i = 0; i < mTaskRecord.threadNum; i++) { |
||||
long startL = i * blockSize, endL = (i + 1) * blockSize; |
||||
ThreadRecord tr; |
||||
tr = new ThreadRecord(); |
||||
tr.taskKey = mTaskRecord.filePath; |
||||
tr.threadId = i; |
||||
tr.startLocation = startL; |
||||
tr.isComplete = false; |
||||
if (requestType == ITaskWrapper.M3U8_VOD) { |
||||
tr.startLocation = 0; |
||||
tr.threadType = TaskRecord.TYPE_M3U8_VOD; |
||||
tr.tsUrl = ((DTaskWrapper) mTaskWrapper).asM3U8().getUrls().get(i); |
||||
} else { |
||||
tr.threadType = TaskRecord.TYPE_HTTP_FTP; |
||||
//最后一个线程的结束位置即为文件的总长度
|
||||
if (i == (mTaskRecord.threadNum - 1)) { |
||||
endL = mEntity.getFileSize(); |
||||
} |
||||
tr.endLocation = endL; |
||||
tr.blockLen = RecordUtil.getBlockLen(mEntity.getFileSize(), i, mTaskRecord.threadNum); |
||||
} |
||||
mTaskRecord.threadRecords.add(tr); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 创建任务记录 |
||||
* |
||||
* @param threadNum 线程总数 |
||||
*/ |
||||
private TaskRecord createTaskRecord(int threadNum) { |
||||
TaskRecord record = new TaskRecord(); |
||||
record.fileName = mEntity.getFileName(); |
||||
record.filePath = getFilePath(); |
||||
record.threadRecords = new ArrayList<>(); |
||||
record.threadNum = threadNum; |
||||
int requestType = mTaskWrapper.getRequestType(); |
||||
if (requestType == ITaskWrapper.M3U8_VOD) { |
||||
record.taskType = TaskRecord.TYPE_M3U8_VOD; |
||||
record.isOpenDynamicFile = true; |
||||
record.bandWidth = ((DTaskWrapper)mTaskWrapper).asM3U8().getBandWidth(); |
||||
} else if (requestType == ITaskWrapper.M3U8_LIVE) { |
||||
record.taskType = TaskRecord.TYPE_M3U8_LIVE; |
||||
record.isOpenDynamicFile = true; |
||||
record.bandWidth = ((DTaskWrapper)mTaskWrapper).asM3U8().getBandWidth(); |
||||
} else { |
||||
if (getRecordType() == TYPE_DOWNLOAD) { |
||||
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 = mEntity.isGroupChild(); |
||||
if (record.isGroupRecord) { |
||||
if (mEntity instanceof DownloadEntity) { |
||||
record.dGroupHash = ((DownloadEntity) mEntity).getGroupHash(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
return record; |
||||
} |
||||
|
||||
/** |
||||
* 保存任务记录 |
||||
*/ |
||||
private void saveRecord() { |
||||
mTaskRecord.threadNum = mTaskRecord.threadRecords.size(); |
||||
mTaskRecord.save(); |
||||
if (mTaskRecord.threadRecords != null && !mTaskRecord.threadRecords.isEmpty()) { |
||||
DbEntity.saveAll(mTaskRecord.threadRecords); |
||||
} |
||||
ALog.d(TAG, String.format("保存记录,线程记录数:%s", mTaskRecord.threadRecords.size())); |
||||
} |
||||
|
||||
/** |
||||
* 获取记录类型 |
||||
* |
||||
* @return {@link #TYPE_DOWNLOAD}、{@link #TYPE_UPLOAD} |
||||
*/ |
||||
private int getRecordType() { |
||||
if (mEntity instanceof DownloadEntity) { |
||||
return TYPE_DOWNLOAD; |
||||
} else { |
||||
return TYPE_UPLOAD; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 获取任务路径 |
||||
* |
||||
* @return 任务文件路径 |
||||
*/ |
||||
private String getFilePath() { |
||||
if (mEntity instanceof DownloadEntity) { |
||||
return ((DownloadEntity) mTaskWrapper.getEntity()).getFilePath(); |
||||
} else { |
||||
return ((UploadEntity) mTaskWrapper.getEntity()).getFilePath(); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 小于1m的文件或是任务组的子任务、线程数强制为1 |
||||
* 不支持断点或chunked模式的线程数都为,线程数强制为1 |
||||
*/ |
||||
private int getNewTaskThreadNum() { |
||||
if (getRecordType() == TYPE_DOWNLOAD) { |
||||
if (mTaskWrapper.getRequestType() == ITaskWrapper.M3U8_VOD) { |
||||
return ((DTaskWrapper) mTaskWrapper).asM3U8().getUrls().size(); |
||||
} |
||||
if (mTaskWrapper.getRequestType() == ITaskWrapper.M3U8_LIVE) { |
||||
return 1; |
||||
} |
||||
if (!mTaskWrapper.isSupportBP() || mTaskWrapper.asHttp().isChunked()) { |
||||
return 1; |
||||
} |
||||
int threadNum = Configuration.getInstance().downloadCfg.getThreadNum(); |
||||
return mEntity.getFileSize() <= SUB_LEN |
||||
|| mEntity.isGroupChild() |
||||
|| threadNum == 1 |
||||
? 1 |
||||
: threadNum; |
||||
} else { |
||||
return 1; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,135 @@ |
||||
/* |
||||
* 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.core.common; |
||||
|
||||
import android.text.TextUtils; |
||||
import com.arialyy.aria.core.FtpUrlEntity; |
||||
import com.arialyy.aria.core.IdEntity; |
||||
import com.arialyy.aria.core.ProtocolType; |
||||
import com.arialyy.aria.util.ALog; |
||||
import com.arialyy.aria.util.ComponentUtil; |
||||
|
||||
/** |
||||
* Created by laoyuyu on 2018/3/9. |
||||
*/ |
||||
public class SFtpOption extends BaseOption { |
||||
|
||||
private String charSet, userName, password; |
||||
private boolean isNeedLogin = false; |
||||
private FtpUrlEntity urlEntity; |
||||
private String protocol; |
||||
private IdEntity idEntity = new IdEntity(); |
||||
|
||||
public SFtpOption() { |
||||
super(); |
||||
ComponentUtil.getInstance().checkComponentExist(ComponentUtil.COMPONENT_TYPE_SFTP); |
||||
} |
||||
|
||||
public SFtpOption charSet(String charSet) { |
||||
if (TextUtils.isEmpty(charSet)) { |
||||
throw new NullPointerException("字符编码为空"); |
||||
} |
||||
this.charSet = charSet; |
||||
return this; |
||||
} |
||||
|
||||
public SFtpOption login(String userName, String password) { |
||||
if (TextUtils.isEmpty(userName)) { |
||||
ALog.e(TAG, "用户名不能为null"); |
||||
return this; |
||||
} else if (TextUtils.isEmpty(password)) { |
||||
ALog.e(TAG, "密码不能为null"); |
||||
return this; |
||||
} |
||||
this.userName = userName; |
||||
this.password = password; |
||||
isNeedLogin = true; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 设置协议类型 |
||||
* |
||||
* @param protocol {@link ProtocolType} |
||||
*/ |
||||
public SFtpOption setProtocol(String protocol) { |
||||
if (TextUtils.isEmpty(protocol)) { |
||||
ALog.e(TAG, "设置协议失败,协议信息为空"); |
||||
return this; |
||||
} |
||||
this.protocol = protocol; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 设置私钥证书路径 |
||||
* |
||||
* @param prvKey 证书路径 |
||||
*/ |
||||
public SFtpOption setPrvKey(String prvKey) { |
||||
if (TextUtils.isEmpty(prvKey)) { |
||||
ALog.e(TAG, "设置私钥证书失败,证书内容为空"); |
||||
return this; |
||||
} |
||||
idEntity.prvKey = prvKey; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 设置私钥密码 |
||||
* |
||||
* @param prvKeyPass 私钥密码 |
||||
*/ |
||||
public SFtpOption setPrvKeyPass(String prvKeyPass) { |
||||
if (TextUtils.isEmpty(prvKeyPass)) { |
||||
ALog.e(TAG, "设置证书密码失败,证书密码为空"); |
||||
return this; |
||||
} |
||||
idEntity.prvPass = prvKeyPass; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 设置公钥证书 |
||||
* |
||||
* @param pubKey 公钥证书内容 |
||||
*/ |
||||
public SFtpOption setPubKey(String pubKey) { |
||||
if (TextUtils.isEmpty(pubKey)) { |
||||
ALog.e(TAG, "设置公钥失败,证书内容为空"); |
||||
return this; |
||||
} |
||||
idEntity.pubKey = pubKey; |
||||
return this; |
||||
} |
||||
|
||||
public SFtpOption setKnowHostPath(String knowHostPath) { |
||||
if (TextUtils.isEmpty(knowHostPath)) { |
||||
ALog.e(TAG, "knowhost 文件路径为空"); |
||||
return this; |
||||
} |
||||
idEntity.knowHost = knowHostPath; |
||||
return this; |
||||
} |
||||
|
||||
public void setUrlEntity(FtpUrlEntity urlEntity) { |
||||
this.urlEntity = urlEntity; |
||||
urlEntity.needLogin = isNeedLogin; |
||||
urlEntity.user = userName; |
||||
urlEntity.password = password; |
||||
urlEntity.idEntity = idEntity; |
||||
} |
||||
} |
@ -1,219 +0,0 @@ |
||||
/* |
||||
* 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.core.common; |
||||
|
||||
import android.os.Bundle; |
||||
import android.os.Looper; |
||||
import android.os.Message; |
||||
import com.arialyy.aria.core.inf.IEventListener; |
||||
import com.arialyy.aria.exception.BaseException; |
||||
import com.arialyy.aria.util.ALog; |
||||
import com.arialyy.aria.util.FileUtil; |
||||
import java.io.File; |
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
/** |
||||
* 线程任务管理器,用于处理多线程下载时任务的状态回调 |
||||
*/ |
||||
public class ThreadStateManager implements IThreadState { |
||||
private final String TAG = "ThreadTaskStateManager"; |
||||
|
||||
/** |
||||
* 任务状态回调 |
||||
*/ |
||||
private IEventListener mListener; |
||||
private int mThreadNum; // 启动的线程总数
|
||||
private int mCancelNum = 0; // 已经取消的线程的数
|
||||
private int mStopNum = 0; // 已经停止的线程数
|
||||
private int mFailNum = 0; // 失败的线程数
|
||||
private int mCompleteNum = 0; // 完成的线程数
|
||||
private long mProgress; //当前总进度
|
||||
private TaskRecord mTaskRecord; // 任务记录
|
||||
private Looper mLooper; |
||||
|
||||
/** |
||||
* @param taskRecord 任务记录 |
||||
* @param listener 任务事件 |
||||
*/ |
||||
ThreadStateManager(Looper looper, TaskRecord taskRecord, IEventListener listener) { |
||||
mLooper = looper; |
||||
mTaskRecord = taskRecord; |
||||
mThreadNum = mTaskRecord.threadNum; |
||||
mListener = listener; |
||||
} |
||||
|
||||
@Override public boolean handleMessage(Message msg) { |
||||
switch (msg.what) { |
||||
case STATE_STOP: |
||||
mStopNum++; |
||||
if (isStop()) { |
||||
quitLooper(); |
||||
} |
||||
break; |
||||
case STATE_CANCEL: |
||||
mCancelNum++; |
||||
if (isCancel()) { |
||||
quitLooper(); |
||||
} |
||||
break; |
||||
case STATE_FAIL: |
||||
mFailNum++; |
||||
if (isFail()) { |
||||
Bundle b = msg.getData(); |
||||
mListener.onFail(b.getBoolean(KEY_RETRY, true), |
||||
(BaseException) b.getSerializable(KEY_ERROR_INFO)); |
||||
quitLooper(); |
||||
} |
||||
break; |
||||
case STATE_COMPLETE: |
||||
mCompleteNum++; |
||||
if (isComplete()) { |
||||
ALog.d(TAG, "isComplete, completeNum = " + mCompleteNum); |
||||
if (mTaskRecord.isBlock) { |
||||
if (mergeFile()) { |
||||
mListener.onComplete(); |
||||
} else { |
||||
mListener.onFail(false, null); |
||||
} |
||||
} else { |
||||
mListener.onComplete(); |
||||
} |
||||
quitLooper(); |
||||
} |
||||
break; |
||||
case STATE_RUNNING: |
||||
if (msg.obj instanceof Long) { |
||||
mProgress += (long) msg.obj; |
||||
} |
||||
break; |
||||
case STATE_UPDATE_PROGRESS: |
||||
if (msg.obj == null) { |
||||
mProgress = updateBlockProgress(); |
||||
} else if (msg.obj instanceof Long) { |
||||
mProgress = (long) msg.obj; |
||||
} |
||||
break; |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
/** |
||||
* 退出looper循环 |
||||
*/ |
||||
private void quitLooper() { |
||||
mLooper.quit(); |
||||
} |
||||
|
||||
/** |
||||
* 获取当前任务下载进度 |
||||
* |
||||
* @return 当前任务下载进度 |
||||
*/ |
||||
@Override |
||||
public long getCurrentProgress() { |
||||
return mProgress; |
||||
} |
||||
|
||||
/** |
||||
* 所有子线程是否都已经停止 |
||||
*/ |
||||
public boolean isStop() { |
||||
//ALog.d(TAG,
|
||||
// String.format("isStop; stopNum: %s, cancelNum: %s, failNum: %s, completeNum: %s", mStopNum,
|
||||
// mCancelNum, mFailNum, mCompleteNum));
|
||||
return mStopNum == mThreadNum || mStopNum + mCompleteNum == mThreadNum; |
||||
} |
||||
|
||||
/** |
||||
* 所有子线程是否都已经失败 |
||||
*/ |
||||
@Override |
||||
public boolean isFail() { |
||||
//ALog.d(TAG,
|
||||
// String.format("isFail; stopNum: %s, cancelNum: %s, failNum: %s, completeNum: %s", mStopNum,
|
||||
// mCancelNum, mFailNum, mCompleteNum));
|
||||
return mCompleteNum != mThreadNum |
||||
&& (mFailNum == mThreadNum || mFailNum + mCompleteNum == mThreadNum); |
||||
} |
||||
|
||||
/** |
||||
* 所有子线程是否都已经完成 |
||||
*/ |
||||
@Override |
||||
public boolean isComplete() { |
||||
//ALog.d(TAG,
|
||||
// String.format("isComplete; stopNum: %s, cancelNum: %s, failNum: %s, completeNum: %s",
|
||||
// mStopNum,
|
||||
// mCancelNum, mFailNum, mCompleteNum));
|
||||
return mCompleteNum == mThreadNum; |
||||
} |
||||
|
||||
/** |
||||
* 所有子线程是否都已经取消 |
||||
*/ |
||||
public boolean isCancel() { |
||||
//ALog.d(TAG, String.format("isCancel; stopNum: %s, cancelNum: %s, failNum: %s, completeNum: %s",
|
||||
// mStopNum,
|
||||
// mCancelNum, mFailNum, mCompleteNum));
|
||||
return mCancelNum == mThreadNum; |
||||
} |
||||
|
||||
/** |
||||
* 更新分块任务s的真实进度 |
||||
*/ |
||||
private long updateBlockProgress() { |
||||
long size = 0; |
||||
for (int i = 0, len = mTaskRecord.threadRecords.size(); i < len; i++) { |
||||
File temp = new File(String.format(RecordHandler.SUB_PATH, mTaskRecord.filePath, i)); |
||||
if (temp.exists()) { |
||||
size += temp.length(); |
||||
} |
||||
} |
||||
return size; |
||||
} |
||||
|
||||
/** |
||||
* 合并文件 |
||||
* |
||||
* @return {@code true} 合并成功,{@code false}合并失败 |
||||
*/ |
||||
private boolean mergeFile() { |
||||
List<String> partPath = new ArrayList<>(); |
||||
for (int i = 0, len = mTaskRecord.threadNum; i < len; i++) { |
||||
partPath.add(String.format(RecordHandler.SUB_PATH, mTaskRecord.filePath, i)); |
||||
} |
||||
boolean isSuccess = FileUtil.mergeFile(mTaskRecord.filePath, partPath); |
||||
if (isSuccess) { |
||||
for (String pp : partPath) { |
||||
File f = new File(pp); |
||||
if (f.exists()) { |
||||
f.delete(); |
||||
} |
||||
} |
||||
File targetFile = new File(mTaskRecord.filePath); |
||||
if (targetFile.exists() && targetFile.length() > mTaskRecord.fileLength) { |
||||
ALog.e(TAG, String.format("任务【%s】分块文件合并失败,下载长度超出文件真实长度,downloadLen: %s,fileSize: %s", |
||||
targetFile.getName(), targetFile.length(), mTaskRecord.fileLength)); |
||||
return false; |
||||
} |
||||
return true; |
||||
} else { |
||||
ALog.e(TAG, "合并失败"); |
||||
return false; |
||||
} |
||||
} |
||||
} |
@ -1,421 +0,0 @@ |
||||
/* |
||||
* 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.core.common.ftp; |
||||
|
||||
import android.net.TrafficStats; |
||||
import android.os.Process; |
||||
import android.text.TextUtils; |
||||
import aria.apache.commons.net.ftp.FTP; |
||||
import aria.apache.commons.net.ftp.FTPClient; |
||||
import aria.apache.commons.net.ftp.FTPClientConfig; |
||||
import aria.apache.commons.net.ftp.FTPFile; |
||||
import aria.apache.commons.net.ftp.FTPReply; |
||||
import aria.apache.commons.net.ftp.FTPSClient; |
||||
import com.arialyy.aria.core.AriaManager; |
||||
import com.arialyy.aria.core.common.OnFileInfoCallback; |
||||
import com.arialyy.aria.core.inf.AbsEntity; |
||||
import com.arialyy.aria.core.inf.AbsTaskWrapper; |
||||
import com.arialyy.aria.core.upload.UploadEntity; |
||||
import com.arialyy.aria.exception.AriaIOException; |
||||
import com.arialyy.aria.exception.BaseException; |
||||
import com.arialyy.aria.exception.FileNotFoundException; |
||||
import com.arialyy.aria.exception.TaskException; |
||||
import com.arialyy.aria.util.ALog; |
||||
import com.arialyy.aria.util.CommonUtil; |
||||
import com.arialyy.aria.util.Regular; |
||||
import com.arialyy.aria.util.SSLContextUtil; |
||||
import java.io.File; |
||||
import java.io.IOException; |
||||
import java.net.InetAddress; |
||||
import java.net.UnknownHostException; |
||||
import java.util.UUID; |
||||
import java.util.regex.Matcher; |
||||
import java.util.regex.Pattern; |
||||
import javax.net.ssl.SSLContext; |
||||
|
||||
/** |
||||
* Created by Aria.Lao on 2017/7/25. 获取ftp文件夹信息 |
||||
*/ |
||||
public abstract class AbsFtpInfoThread<ENTITY extends AbsEntity, TASK_WRAPPER extends AbsTaskWrapper<ENTITY>> |
||||
implements Runnable { |
||||
|
||||
private final String TAG = "AbsFtpInfoThread"; |
||||
protected ENTITY mEntity; |
||||
protected TASK_WRAPPER mTaskWrapper; |
||||
private FtpTaskConfig mTaskDelegate; |
||||
private int mConnectTimeOut; |
||||
protected OnFileInfoCallback mCallback; |
||||
protected long mSize = 0; |
||||
protected String charSet = "UTF-8"; |
||||
private boolean isUpload = false; |
||||
|
||||
public AbsFtpInfoThread(TASK_WRAPPER taskWrapper, OnFileInfoCallback callback) { |
||||
mTaskWrapper = taskWrapper; |
||||
mEntity = taskWrapper.getEntity(); |
||||
mTaskDelegate = taskWrapper.asFtp(); |
||||
mConnectTimeOut = |
||||
AriaManager.getInstance().getDownloadConfig().getConnectTimeOut(); |
||||
mCallback = callback; |
||||
if (mEntity instanceof UploadEntity) { |
||||
isUpload = true; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 获取请求的远程文件路径 |
||||
* |
||||
* @return 远程文件路径 |
||||
*/ |
||||
protected abstract String getRemotePath(); |
||||
|
||||
@Override |
||||
public void run() { |
||||
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); |
||||
TrafficStats.setThreadStatsTag(UUID.randomUUID().toString().hashCode()); |
||||
FTPClient client = null; |
||||
try { |
||||
client = createFtpClient(); |
||||
if (client == null) { |
||||
ALog.e(TAG, String.format("任务【%s】失败", mTaskDelegate.getUrlEntity().url)); |
||||
return; |
||||
} |
||||
String remotePath = CommonUtil.convertFtpChar(charSet, getRemotePath()); |
||||
|
||||
FTPFile[] files = client.listFiles(remotePath); |
||||
boolean isExist = files.length != 0; |
||||
if (!isExist && !isUpload) { |
||||
int i = remotePath.lastIndexOf(File.separator); |
||||
FTPFile[] files1; |
||||
if (i == -1) { |
||||
files1 = client.listFiles(); |
||||
} else { |
||||
files1 = client.listFiles(remotePath.substring(0, i + 1)); |
||||
} |
||||
if (files1.length > 0) { |
||||
ALog.i(TAG, |
||||
String.format("路径【%s】下的文件列表 ===================================", getRemotePath())); |
||||
for (FTPFile file : files1) { |
||||
ALog.d(TAG, file.toString()); |
||||
} |
||||
ALog.i(TAG, |
||||
"================================= --end-- ==================================="); |
||||
} else { |
||||
ALog.w(TAG, String.format("获取文件列表失败,msg:%s", client.getReplyString())); |
||||
} |
||||
closeClient(client); |
||||
|
||||
failDownload(new FileNotFoundException(TAG, |
||||
String.format("文件不存在,url: %s, remotePath:%s", mTaskDelegate.getUrlEntity().url, |
||||
remotePath)), false); |
||||
return; |
||||
} |
||||
|
||||
// 处理拦截功能
|
||||
if (!onInterceptor(client, files)) { |
||||
closeClient(client); |
||||
ALog.d(TAG, "拦截器处理完成任务,任务将不再执行"); |
||||
return; |
||||
} |
||||
|
||||
//为了防止编码错乱,需要使用原始字符串
|
||||
mSize = getFileSize(files, client, getRemotePath()); |
||||
int reply = client.getReplyCode(); |
||||
if (!FTPReply.isPositiveCompletion(reply)) { |
||||
if (isUpload) { |
||||
//服务器上没有该文件路径,表示该任务为新的上传任务
|
||||
mTaskWrapper.setNewTask(true); |
||||
} else { |
||||
closeClient(client); |
||||
failDownload(new AriaIOException(TAG, |
||||
String.format("获取文件信息错误,url: %s, errorCode:%s, errorMsg:%s", |
||||
mTaskDelegate.getUrlEntity().url, reply, client.getReplyString())), true); |
||||
return; |
||||
} |
||||
} |
||||
mTaskWrapper.setCode(reply); |
||||
if (mSize != 0 && !isUpload) { |
||||
mEntity.setFileSize(mSize); |
||||
} |
||||
onPreComplete(reply); |
||||
mEntity.update(); |
||||
} catch (IOException e) { |
||||
failDownload(new AriaIOException(TAG, |
||||
String.format("FTP错误信息,code:%s,msg:%s", client.getReplyCode(), client.getReplyString()), |
||||
e), true); |
||||
} finally { |
||||
closeClient(client); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 处理拦截 |
||||
* |
||||
* @param ftpFiles remotePath路径下的所有文件 |
||||
* @return {@code false} 拦截器处理完成任务,任务将不再执行,{@code true} 拦截器处理任务完成任务,任务继续执行 |
||||
*/ |
||||
protected boolean onInterceptor(FTPClient client, FTPFile[] ftpFiles) { |
||||
return true; |
||||
} |
||||
|
||||
/** |
||||
* 检查文件是否存在 |
||||
* |
||||
* @return {@code true}存在 |
||||
*/ |
||||
private boolean checkFileExist(FTPFile[] ftpFiles, String fileName) { |
||||
for (FTPFile ff : ftpFiles) { |
||||
if (ff.getName().equals(fileName)) { |
||||
return true; |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
protected void onPreComplete(int code) { |
||||
|
||||
} |
||||
|
||||
/** |
||||
* 创建FTP客户端 |
||||
*/ |
||||
private FTPClient createFtpClient() { |
||||
FTPClient client = null; |
||||
final FtpUrlEntity urlEntity = mTaskDelegate.getUrlEntity(); |
||||
try { |
||||
Pattern p = Pattern.compile(Regular.REG_IP_V4); |
||||
Matcher m = p.matcher(urlEntity.hostName); |
||||
if (m.find() && m.groupCount() > 0) { |
||||
client = newInstanceClient(urlEntity); |
||||
client.setConnectTimeout(mConnectTimeOut); // 连接10s超时
|
||||
InetAddress ip = InetAddress.getByName(urlEntity.hostName); |
||||
|
||||
client = connect(client, new InetAddress[] { ip }, 0, Integer.parseInt(urlEntity.port)); |
||||
mTaskDelegate.getUrlEntity().validAddr = ip; |
||||
} else { |
||||
DNSQueryThread dnsThread = new DNSQueryThread(urlEntity.hostName); |
||||
dnsThread.start(); |
||||
dnsThread.join(mConnectTimeOut); |
||||
InetAddress[] ips = dnsThread.getIps(); |
||||
client = connect(newInstanceClient(urlEntity), ips, 0, Integer.parseInt(urlEntity.port)); |
||||
} |
||||
|
||||
if (client == null) { |
||||
failDownload(new AriaIOException(TAG, |
||||
String.format("链接失败, url: %s", mTaskDelegate.getUrlEntity().url)), false); |
||||
return null; |
||||
} |
||||
|
||||
boolean loginSuccess = true; |
||||
if (urlEntity.needLogin) { |
||||
try { |
||||
if (TextUtils.isEmpty(urlEntity.account)) { |
||||
loginSuccess = client.login(urlEntity.user, urlEntity.password); |
||||
} else { |
||||
loginSuccess = client.login(urlEntity.user, urlEntity.password, urlEntity.account); |
||||
} |
||||
} catch (IOException e) { |
||||
ALog.e(TAG, |
||||
new TaskException(TAG, String.format("登录失败,错误码为:%s, msg:%s", client.getReplyCode(), |
||||
client.getReplyString()), e)); |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
if (!loginSuccess) { |
||||
failDownload( |
||||
new TaskException(TAG, String.format("登录失败,错误码为:%s, msg:%s", client.getReplyCode(), |
||||
client.getReplyString())), |
||||
false); |
||||
client.disconnect(); |
||||
return null; |
||||
} |
||||
|
||||
int reply = client.getReplyCode(); |
||||
if (!FTPReply.isPositiveCompletion(reply)) { |
||||
client.disconnect(); |
||||
failDownload(new AriaIOException(TAG, |
||||
String.format("无法连接到ftp服务器,filePath: %s, url: %s, errorCode: %s, errorMsg:%s", |
||||
mEntity.getKey(), mTaskDelegate.getUrlEntity().url, reply, |
||||
client.getReplyString())), |
||||
true); |
||||
return null; |
||||
} |
||||
// 开启服务器对UTF-8的支持,如果服务器支持就用UTF-8编码
|
||||
charSet = "UTF-8"; |
||||
reply = client.sendCommand("OPTS UTF8", "ON"); |
||||
if (reply != FTPReply.COMMAND_IS_SUPERFLUOUS) { |
||||
ALog.i(TAG, "D_FTP 服务器不支持开启UTF8编码,尝试使用Aria手动设置的编码"); |
||||
if (!TextUtils.isEmpty(mTaskWrapper.asFtp().getCharSet())) { |
||||
charSet = mTaskWrapper.asFtp().getCharSet(); |
||||
} |
||||
} |
||||
client.setControlEncoding(charSet); |
||||
client.setDataTimeout(10 * 1000); |
||||
client.enterLocalPassiveMode(); |
||||
client.setFileType(FTP.BINARY_FILE_TYPE); |
||||
} catch (IOException e) { |
||||
closeClient(client); |
||||
e.printStackTrace(); |
||||
} catch (InterruptedException e) { |
||||
closeClient(client); |
||||
e.printStackTrace(); |
||||
} |
||||
return client; |
||||
} |
||||
|
||||
protected void closeClient(FTPClient client) { |
||||
try { |
||||
if (client != null && client.isConnected()) { |
||||
client.logout(); |
||||
client.disconnect(); |
||||
} |
||||
} catch (IOException e) { |
||||
e.printStackTrace(); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 创建FTP/FTPS客户端 |
||||
*/ |
||||
private FTPClient newInstanceClient(FtpUrlEntity urlEntity) { |
||||
FTPClient temp; |
||||
if (urlEntity.isFtps) { |
||||
FTPSClient sClient; |
||||
SSLContext sslContext = SSLContextUtil.getSSLContext(urlEntity.keyAlias, urlEntity.storePath, |
||||
urlEntity.protocol); |
||||
if (sslContext == null) { |
||||
sClient = new FTPSClient(urlEntity.protocol, urlEntity.isImplicit); |
||||
} else { |
||||
sClient = new FTPSClient(true, sslContext); |
||||
} |
||||
|
||||
temp = sClient; |
||||
} else { |
||||
temp = new FTPClient(); |
||||
} |
||||
|
||||
FTPClientConfig clientConfig; |
||||
if (mTaskWrapper.asFtp().getClientConfig() != null) { |
||||
clientConfig = mTaskWrapper.asFtp().getClientConfig(); |
||||
} else { |
||||
clientConfig = new FTPClientConfig(FTPClientConfig.SYST_UNIX); |
||||
clientConfig.setServerLanguageCode("en"); |
||||
} |
||||
temp.configure(clientConfig); |
||||
|
||||
return temp; |
||||
} |
||||
|
||||
/** |
||||
* 连接到ftp服务器 |
||||
*/ |
||||
private FTPClient connect(FTPClient client, InetAddress[] ips, int index, int port) { |
||||
if (ips == null || ips.length == 0) { |
||||
ALog.w(TAG, "无可用ip"); |
||||
return null; |
||||
} |
||||
try { |
||||
client.setConnectTimeout(mConnectTimeOut); //需要先设置超时,这样才不会出现阻塞
|
||||
client.connect(ips[index], port); |
||||
mTaskDelegate.getUrlEntity().validAddr = ips[index]; |
||||
|
||||
FtpUrlEntity urlEntity = mTaskWrapper.asFtp().getUrlEntity(); |
||||
if (urlEntity.isFtps) { |
||||
FTPSClient sClient = (FTPSClient) client; |
||||
sClient.execPBSZ(0); |
||||
sClient.execPROT("P"); |
||||
} |
||||
|
||||
return client; |
||||
} catch (IOException e) { |
||||
e.printStackTrace(); |
||||
closeClient(client); |
||||
if (index + 1 >= ips.length) { |
||||
ALog.w(TAG, "遇到[ECONNREFUSED-连接被服务器拒绝]错误,已没有其他地址,链接失败;如果是ftps,请检查端口是否使用了ftp的端口而不是ftps的端口"); |
||||
return null; |
||||
} |
||||
try { |
||||
Thread.sleep(1000); |
||||
} catch (InterruptedException e1) { |
||||
e1.printStackTrace(); |
||||
} |
||||
ALog.w(TAG, "遇到[ECONNREFUSED-连接被服务器拒绝]错误,正在尝试下一个地址"); |
||||
return connect(newInstanceClient(mTaskDelegate.getUrlEntity()), ips, index + 1, port); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 遍历FTP服务器上对应文件或文件夹大小 |
||||
* |
||||
* @throws IOException 字符串编码转换错误 |
||||
*/ |
||||
private long getFileSize(FTPFile[] files, FTPClient client, String dirName) throws IOException { |
||||
long size = 0; |
||||
String path = dirName + "/"; |
||||
for (FTPFile file : files) { |
||||
if (file.isFile()) { |
||||
size += file.getSize(); |
||||
ALog.d(TAG, "isValid = " + file.isValid()); |
||||
handleFile(path + file.getName(), file); |
||||
} else { |
||||
String remotePath = CommonUtil.convertFtpChar(charSet, path + file.getName()); |
||||
size += getFileSize(client.listFiles(remotePath), client, path + file.getName()); |
||||
} |
||||
} |
||||
return size; |
||||
} |
||||
|
||||
/** |
||||
* 处理FTP文件信息 |
||||
* |
||||
* @param remotePath ftp服务器文件夹路径 |
||||
* @param ftpFile ftp服务器上对应的文件 |
||||
*/ |
||||
protected void handleFile(String remotePath, FTPFile ftpFile) { |
||||
} |
||||
|
||||
protected void failDownload(BaseException e, boolean needRetry) { |
||||
if (mCallback != null) { |
||||
mCallback.onFail(mEntity, e, needRetry); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 获取可用IP的超时线程,InetAddress.getByName没有超时功能,需要自己处理超时 |
||||
*/ |
||||
private static class DNSQueryThread extends Thread { |
||||
|
||||
private String hostName; |
||||
private InetAddress[] ips; |
||||
|
||||
DNSQueryThread(String hostName) { |
||||
this.hostName = hostName; |
||||
} |
||||
|
||||
@Override |
||||
public void run() { |
||||
try { |
||||
ips = InetAddress.getAllByName(hostName); |
||||
} catch (UnknownHostException e) { |
||||
e.printStackTrace(); |
||||
} |
||||
} |
||||
|
||||
synchronized InetAddress[] getIps() { |
||||
return ips; |
||||
} |
||||
} |
||||
} |
@ -1,105 +0,0 @@ |
||||
/* |
||||
* 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.core.common.ftp; |
||||
|
||||
import android.text.TextUtils; |
||||
import androidx.annotation.CheckResult; |
||||
import com.arialyy.aria.core.common.BaseDelegate; |
||||
import com.arialyy.aria.core.common.ProtocolType; |
||||
import com.arialyy.aria.core.common.Suggest; |
||||
import com.arialyy.aria.core.inf.AbsTarget; |
||||
import com.arialyy.aria.core.inf.AbsTaskWrapper; |
||||
|
||||
/** |
||||
* D_FTP SSL/TSL 参数委托 |
||||
*/ |
||||
public class FTPSDelegate<TARGET extends AbsTarget> extends BaseDelegate<TARGET> { |
||||
|
||||
private FtpUrlEntity mUrlEntity; |
||||
|
||||
public FTPSDelegate(TARGET target, AbsTaskWrapper wrapper) { |
||||
super(target, wrapper); |
||||
mUrlEntity = getTaskWrapper().asFtp().getUrlEntity(); |
||||
mUrlEntity.isFtps = true; |
||||
} |
||||
|
||||
/** |
||||
* 设置协议类型 |
||||
* |
||||
* @param protocol {@link ProtocolType} |
||||
*/ |
||||
@CheckResult(suggest = Suggest.TO_CONTROLLER) |
||||
public FTPSDelegate<TARGET> setProtocol(@ProtocolType String protocol) { |
||||
if (TextUtils.isEmpty(protocol)) { |
||||
throw new NullPointerException("协议为空"); |
||||
} |
||||
mUrlEntity.protocol = protocol; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 设置证书别名 |
||||
* |
||||
* @param keyAlias 别名 |
||||
*/ |
||||
@CheckResult(suggest = Suggest.TO_CONTROLLER) |
||||
public FTPSDelegate<TARGET> setAlias(String keyAlias) { |
||||
if (TextUtils.isEmpty(keyAlias)) { |
||||
throw new NullPointerException("别名为空"); |
||||
} |
||||
mUrlEntity.keyAlias = keyAlias; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 设置证书密码 |
||||
* |
||||
* @param storePass 私钥密码 |
||||
*/ |
||||
@CheckResult(suggest = Suggest.TO_CONTROLLER) |
||||
public FTPSDelegate<TARGET> setStorePass(String storePass) { |
||||
if (TextUtils.isEmpty(storePass)) { |
||||
throw new NullPointerException("证书密码为空"); |
||||
} |
||||
mUrlEntity.storePass = storePass; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 设置证书路径 |
||||
* |
||||
* @param storePath 证书路径 |
||||
*/ |
||||
@CheckResult(suggest = Suggest.TO_CONTROLLER) |
||||
public FTPSDelegate<TARGET> setStorePath(String storePath) { |
||||
if (TextUtils.isEmpty(storePath)) { |
||||
throw new NullPointerException("证书路径为空"); |
||||
} |
||||
mUrlEntity.storePath = storePath; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 设置安全模式,默认true |
||||
* |
||||
* @param isImplicit true 隐式,false 显式 |
||||
*/ |
||||
@CheckResult(suggest = Suggest.TO_CONTROLLER) |
||||
public FTPSDelegate<TARGET> setImplicit(boolean isImplicit) { |
||||
mUrlEntity.isImplicit = isImplicit; |
||||
return this; |
||||
} |
||||
} |
@ -1,92 +0,0 @@ |
||||
/* |
||||
* 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.core.common.ftp; |
||||
|
||||
import android.text.TextUtils; |
||||
import androidx.annotation.CheckResult; |
||||
import aria.apache.commons.net.ftp.FTPClientConfig; |
||||
import com.arialyy.aria.core.common.BaseDelegate; |
||||
import com.arialyy.aria.core.common.Suggest; |
||||
import com.arialyy.aria.core.inf.AbsTarget; |
||||
import com.arialyy.aria.core.inf.AbsTaskWrapper; |
||||
import com.arialyy.aria.util.ALog; |
||||
|
||||
/** |
||||
* Created by laoyuyu on 2018/3/9. |
||||
*/ |
||||
public class FtpDelegate<TARGET extends AbsTarget> extends BaseDelegate<TARGET> { |
||||
private static final String TAG = "FtpDelegate"; |
||||
|
||||
public FtpDelegate(TARGET target, AbsTaskWrapper wrapper) { |
||||
super(target, wrapper); |
||||
} |
||||
|
||||
@CheckResult(suggest = Suggest.TO_CONTROLLER) |
||||
public FtpDelegate<TARGET> charSet(String charSet) { |
||||
if (TextUtils.isEmpty(charSet)) { |
||||
throw new NullPointerException("字符编码为空"); |
||||
} |
||||
getTaskWrapper().asFtp().setCharSet(charSet); |
||||
return this; |
||||
} |
||||
|
||||
@CheckResult(suggest = Suggest.TO_CONTROLLER) |
||||
public FtpDelegate<TARGET> login(String userName, String password) { |
||||
return login(userName, password, null); |
||||
} |
||||
|
||||
@CheckResult(suggest = Suggest.TO_CONTROLLER) |
||||
public FtpDelegate<TARGET> login(String userName, String password, String account) { |
||||
if (TextUtils.isEmpty(userName)) { |
||||
ALog.e(TAG, "用户名不能为null"); |
||||
return this; |
||||
} else if (TextUtils.isEmpty(password)) { |
||||
ALog.e(TAG, "密码不能为null"); |
||||
return this; |
||||
} |
||||
// urlEntity 不能在构造函数中获取,因为ftp上传时url是后于构造函数的
|
||||
FtpUrlEntity urlEntity = getTaskWrapper().asFtp().getUrlEntity(); |
||||
urlEntity.needLogin = true; |
||||
urlEntity.user = userName; |
||||
urlEntity.password = password; |
||||
urlEntity.account = account; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 是否是FTPS协议 |
||||
* 如果是FTPS协议,需要使用{@link FTPSDelegate#setStorePath(String)} 、{@link FTPSDelegate#setAlias(String)} |
||||
* 设置证书信息 |
||||
*/ |
||||
@CheckResult(suggest = Suggest.TO_CONTROLLER) |
||||
public FTPSDelegate<TARGET> asFtps() { |
||||
return new FTPSDelegate<>(mTarget, mWrapper); |
||||
} |
||||
|
||||
/** |
||||
* 配置ftp客户端信息 |
||||
*/ |
||||
@CheckResult(suggest = Suggest.TO_CONTROLLER) |
||||
public FtpDelegate<TARGET> setFtpClentConfig(FTPClientConfig config) { |
||||
getTaskWrapper().asFtp().setClientConfig(config); |
||||
return this; |
||||
} |
||||
|
||||
//@Override public TARGET setProxy(Proxy proxy) {
|
||||
// mTarget.getTaskWrapper().asFtp().setProxy(proxy);
|
||||
// return mTarget;
|
||||
//}
|
||||
} |
@ -1,102 +0,0 @@ |
||||
/* |
||||
* 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.core.common.ftp; |
||||
|
||||
import aria.apache.commons.net.ftp.FTPClientConfig; |
||||
import com.arialyy.aria.core.inf.ITaskConfig; |
||||
import java.lang.ref.SoftReference; |
||||
import java.net.Proxy; |
||||
|
||||
/** |
||||
* fTP任务设置的信息,如:用户名、密码、端口等信息 |
||||
*/ |
||||
public class FtpTaskConfig implements ITaskConfig { |
||||
|
||||
/** |
||||
* 账号和密码 |
||||
*/ |
||||
private FtpUrlEntity urlEntity; |
||||
|
||||
private Proxy proxy; |
||||
|
||||
/** |
||||
* 字符编码,默认为"utf-8" |
||||
*/ |
||||
private String charSet = "utf-8"; |
||||
|
||||
/** |
||||
* 上传拦截器 |
||||
*/ |
||||
private SoftReference<IFtpUploadInterceptor> uploadInterceptor; |
||||
|
||||
/** |
||||
* 上传到服务器文件的新文件名{@link FtpInterceptHandler#getNewFileName()} |
||||
*/ |
||||
private String newFileName; |
||||
|
||||
/** |
||||
* client配置信息 |
||||
*/ |
||||
private FTPClientConfig clientConfig; |
||||
|
||||
public FTPClientConfig getClientConfig() { |
||||
return clientConfig; |
||||
} |
||||
|
||||
public void setClientConfig(FTPClientConfig clientConfig) { |
||||
this.clientConfig = clientConfig; |
||||
} |
||||
|
||||
public String getNewFileName() { |
||||
return newFileName; |
||||
} |
||||
|
||||
public void setNewFileName(String newFileName) { |
||||
this.newFileName = newFileName; |
||||
} |
||||
|
||||
public IFtpUploadInterceptor getUploadInterceptor() { |
||||
return uploadInterceptor.get(); |
||||
} |
||||
|
||||
public void setUploadInterceptor(IFtpUploadInterceptor uploadInterceptor) { |
||||
this.uploadInterceptor = new SoftReference<>(uploadInterceptor); |
||||
} |
||||
|
||||
public FtpUrlEntity getUrlEntity() { |
||||
return urlEntity; |
||||
} |
||||
|
||||
public void setUrlEntity(FtpUrlEntity urlEntity) { |
||||
this.urlEntity = urlEntity; |
||||
} |
||||
|
||||
public void setProxy(Proxy proxy) { |
||||
this.proxy = proxy; |
||||
} |
||||
|
||||
public Proxy getProxy() { |
||||
return proxy; |
||||
} |
||||
|
||||
public String getCharSet() { |
||||
return charSet; |
||||
} |
||||
|
||||
public void setCharSet(String charSet) { |
||||
this.charSet = charSet; |
||||
} |
||||
} |
@ -1,52 +0,0 @@ |
||||
package com.arialyy.aria.core.common.ftp; |
||||
|
||||
import aria.apache.commons.net.ftp.FTPSClient; |
||||
import java.io.IOException; |
||||
import java.lang.reflect.Field; |
||||
import java.lang.reflect.Method; |
||||
import java.net.Socket; |
||||
import java.util.Locale; |
||||
import javax.net.ssl.SSLContext; |
||||
import javax.net.ssl.SSLSession; |
||||
import javax.net.ssl.SSLSessionContext; |
||||
import javax.net.ssl.SSLSocket; |
||||
|
||||
public class SSLSessionReuseFTPSClient extends FTPSClient { |
||||
|
||||
SSLSessionReuseFTPSClient(boolean b, SSLContext context) { |
||||
super(b, context); |
||||
} |
||||
|
||||
// adapted from:
|
||||
// https://trac.cyberduck.io/browser/trunk/ftp/src/main/java/ch/cyberduck/core/ftp/FTPClient.java
|
||||
@Override |
||||
protected void _prepareDataSocket_(final Socket socket) throws IOException { |
||||
if (socket instanceof SSLSocket) { |
||||
// Control socket is SSL
|
||||
final SSLSession session = ((SSLSocket) _socket_).getSession(); |
||||
if (session.isValid()) { |
||||
final SSLSessionContext context = session.getSessionContext(); |
||||
try { |
||||
//final Field sessionHostPortCache = context.getClass().getDeclaredField("sessionHostPortCache");
|
||||
final Field sessionHostPortCache = |
||||
context.getClass().getDeclaredField("sessionsByHostAndPort"); |
||||
sessionHostPortCache.setAccessible(true); |
||||
final Object cache = sessionHostPortCache.get(context); |
||||
final Method method = |
||||
cache.getClass().getDeclaredMethod("put", Object.class, Object.class); |
||||
method.setAccessible(true); |
||||
method.invoke(cache, String.format("%s:%s", socket.getInetAddress().getHostName(), |
||||
String.valueOf(socket.getPort())).toLowerCase(Locale.ROOT), session); |
||||
method.invoke(cache, String.format("%s:%s", socket.getInetAddress().getHostAddress(), |
||||
String.valueOf(socket.getPort())).toLowerCase(Locale.ROOT), session); |
||||
} catch (NoSuchFieldException e) { |
||||
throw new IOException(e); |
||||
} catch (Exception e) { |
||||
throw new IOException(e); |
||||
} |
||||
} else { |
||||
throw new IOException("Invalid SSL Session"); |
||||
} |
||||
} |
||||
} |
||||
} |
@ -1,186 +0,0 @@ |
||||
/* |
||||
* 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.core.common.http; |
||||
|
||||
import android.text.TextUtils; |
||||
import androidx.annotation.CheckResult; |
||||
import androidx.annotation.NonNull; |
||||
import com.arialyy.aria.core.common.BaseDelegate; |
||||
import com.arialyy.aria.core.common.RequestEnum; |
||||
import com.arialyy.aria.core.common.Suggest; |
||||
import com.arialyy.aria.core.inf.AbsTarget; |
||||
import com.arialyy.aria.core.inf.AbsTaskWrapper; |
||||
import com.arialyy.aria.util.ALog; |
||||
import java.net.Proxy; |
||||
import java.util.Collection; |
||||
import java.util.HashMap; |
||||
import java.util.Map; |
||||
import java.util.Set; |
||||
|
||||
/** |
||||
* HTTP协议处理 |
||||
*/ |
||||
public class HttpDelegate<TARGET extends AbsTarget> extends BaseDelegate<TARGET> { |
||||
|
||||
public HttpDelegate(TARGET target, AbsTaskWrapper wrapper) { |
||||
super(target, wrapper); |
||||
} |
||||
|
||||
/** |
||||
* 设置请求类型 |
||||
* |
||||
* @param requestEnum {@link RequestEnum} |
||||
*/ |
||||
@CheckResult(suggest = Suggest.TO_CONTROLLER) |
||||
public HttpDelegate<TARGET> setRequestType(RequestEnum requestEnum) { |
||||
getTaskWrapper().asHttp().setRequestEnum(requestEnum); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 设置http请求参数 |
||||
*/ |
||||
@CheckResult(suggest = Suggest.TO_CONTROLLER) |
||||
public HttpDelegate<TARGET> setParams(Map<String, String> params) { |
||||
getTaskWrapper().asHttp().setParams(params); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 设置http请求参数 |
||||
*/ |
||||
@CheckResult(suggest = Suggest.TO_CONTROLLER) |
||||
public HttpDelegate<TARGET> setParam(String key, String value) { |
||||
if (TextUtils.isEmpty(key) || TextUtils.isEmpty(value)) { |
||||
ALog.d(TAG, "key 或value 为空"); |
||||
return this; |
||||
} |
||||
Map<String, String> params = getTaskWrapper().asHttp().getParams(); |
||||
if (params == null) { |
||||
params = new HashMap<>(); |
||||
getTaskWrapper().asHttp().setParams(params); |
||||
} |
||||
params.put(key, value); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 设置http表单字段 |
||||
*/ |
||||
@CheckResult(suggest = Suggest.TO_CONTROLLER) |
||||
public HttpDelegate<TARGET> setFormFields(Map<String, String> params) { |
||||
getTaskWrapper().asHttp().setFormFields(params); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 给url请求添加Header数据 |
||||
* 如果新的header数据和数据保存的不一致,则更新数据库中对应的header数据 |
||||
* |
||||
* @param key header对应的key |
||||
* @param value header对应的value |
||||
*/ |
||||
@CheckResult(suggest = Suggest.TO_CONTROLLER) |
||||
public HttpDelegate<TARGET> addHeader(@NonNull String key, @NonNull String value) { |
||||
if (TextUtils.isEmpty(key)) { |
||||
ALog.w(TAG, "设置header失败,header对应的key不能为null"); |
||||
return this; |
||||
} else if (TextUtils.isEmpty(value)) { |
||||
ALog.w(TAG, "设置header失败,header对应的value不能为null"); |
||||
return this; |
||||
} |
||||
addHeader(getTaskWrapper(), key, value); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 给url请求添加一组header数据 |
||||
* 如果新的header数据和数据保存的不一致,则更新数据库中对应的header数据 |
||||
* |
||||
* @param headers 一组http header数据 |
||||
*/ |
||||
@CheckResult(suggest = Suggest.TO_CONTROLLER) |
||||
public HttpDelegate<TARGET> addHeaders(@NonNull Map<String, String> headers) { |
||||
if (headers.size() == 0) { |
||||
ALog.w(TAG, "设置header失败,map没有header数据"); |
||||
return this; |
||||
} |
||||
addHeaders(getTaskWrapper(), headers); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 设置代理 |
||||
*/ |
||||
@CheckResult(suggest = Suggest.TO_CONTROLLER) |
||||
public HttpDelegate<TARGET> setUrlProxy(Proxy proxy) { |
||||
getTaskWrapper().asHttp().setProxy(proxy); |
||||
return this; |
||||
} |
||||
|
||||
private void addHeader(AbsTaskWrapper taskWrapper, String key, String value) { |
||||
HttpTaskConfig taskDelegate = taskWrapper.asHttp(); |
||||
if (taskDelegate.getHeaders().get(key) == null) { |
||||
taskDelegate.getHeaders().put(key, value); |
||||
} else if (!taskDelegate.getHeaders().get(key).equals(value)) { |
||||
taskDelegate.getHeaders().put(key, value); |
||||
} |
||||
} |
||||
|
||||
private void addHeaders(AbsTaskWrapper taskWrapper, Map<String, String> headers) { |
||||
HttpTaskConfig taskDelegate = taskWrapper.asHttp(); |
||||
/* |
||||
两个map比较逻辑 |
||||
1、比对key是否相同 |
||||
2、如果key相同,比对value是否相同 |
||||
3、只有当上面两个步骤中key 和 value都相同时才能任务两个map数据一致 |
||||
*/ |
||||
boolean mapEquals = false; |
||||
if (taskDelegate.getHeaders().size() == headers.size()) { |
||||
int i = 0; |
||||
Set<String> keys = taskDelegate.getHeaders().keySet(); |
||||
for (String key : keys) { |
||||
if (headers.containsKey(key)) { |
||||
i++; |
||||
} else { |
||||
break; |
||||
} |
||||
} |
||||
if (i == taskDelegate.getHeaders().size()) { |
||||
int j = 0; |
||||
Collection<String> values = taskDelegate.getHeaders().values(); |
||||
for (String value : values) { |
||||
if (headers.containsValue(value)) { |
||||
j++; |
||||
} else { |
||||
break; |
||||
} |
||||
} |
||||
if (j == taskDelegate.getHeaders().size()) { |
||||
mapEquals = true; |
||||
} |
||||
} |
||||
} |
||||
|
||||
if (!mapEquals) { |
||||
taskDelegate.getHeaders().clear(); |
||||
Set<String> keys = headers.keySet(); |
||||
for (String key : keys) { |
||||
taskDelegate.getHeaders().put(key, headers.get(key)); |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,26 @@ |
||||
/* |
||||
* 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.core.download; |
||||
|
||||
import com.arialyy.aria.core.scheduler.NormalTaskListenerInterface; |
||||
import com.arialyy.aria.core.task.DownloadGroupTask; |
||||
|
||||
/** |
||||
* @author ChenFei(chenfei0928 @ gmail.com) |
||||
* Date: 2020-07-07 14:12 |
||||
*/ |
||||
public interface DownloadGroupTaskListener extends NormalTaskListenerInterface<DownloadGroupTask> { |
||||
} |
@ -0,0 +1,26 @@ |
||||
/* |
||||
* 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.core.download; |
||||
|
||||
import com.arialyy.aria.core.scheduler.NormalTaskListenerInterface; |
||||
import com.arialyy.aria.core.task.DownloadTask; |
||||
|
||||
/** |
||||
* @author ChenFei(chenfei0928@gmail.com) |
||||
* Date: 2020-07-07 14:12 |
||||
*/ |
||||
public interface DownloadTaskListener extends NormalTaskListenerInterface<DownloadTask> { |
||||
} |
@ -1,129 +0,0 @@ |
||||
/* |
||||
* 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.core.download.downloader; |
||||
|
||||
import com.arialyy.aria.core.common.AbsThreadTask; |
||||
import com.arialyy.aria.core.common.NormalFileer; |
||||
import com.arialyy.aria.core.common.RecordHandler; |
||||
import com.arialyy.aria.core.common.SubThreadConfig; |
||||
import com.arialyy.aria.core.download.DTaskWrapper; |
||||
import com.arialyy.aria.core.download.DownloadEntity; |
||||
import com.arialyy.aria.core.event.Event; |
||||
import com.arialyy.aria.core.event.SpeedEvent; |
||||
import com.arialyy.aria.core.inf.IDownloadListener; |
||||
import com.arialyy.aria.core.inf.ITaskWrapper; |
||||
import com.arialyy.aria.exception.BaseException; |
||||
import com.arialyy.aria.exception.TaskException; |
||||
import com.arialyy.aria.util.ALog; |
||||
import com.arialyy.aria.util.BufferedRandomAccessFile; |
||||
import java.io.File; |
||||
import java.io.IOException; |
||||
|
||||
/** |
||||
* Created by AriaL on 2017/7/1. 文件下载器 |
||||
*/ |
||||
public class Downloader extends NormalFileer<DownloadEntity, DTaskWrapper> { |
||||
private String TAG = "Downloader"; |
||||
|
||||
public Downloader(IDownloadListener listener, DTaskWrapper taskWrapper) { |
||||
super(listener, taskWrapper); |
||||
mTempFile = new File(mEntity.getFilePath()); |
||||
setUpdateInterval(taskWrapper.getConfig().getUpdateInterval()); |
||||
} |
||||
|
||||
@Override protected boolean handleNewTask() { |
||||
if (!mRecord.isBlock) { |
||||
if (mTempFile.exists()) { |
||||
mTempFile.delete(); |
||||
} |
||||
//CommonUtil.createFile(mTempFile.getPath());
|
||||
} else { |
||||
for (int i = 0; i < mTotalThreadNum; i++) { |
||||
File blockFile = new File(String.format(RecordHandler.SUB_PATH, mTempFile.getPath(), i)); |
||||
if (blockFile.exists()) { |
||||
ALog.d(TAG, String.format("分块【%s】已经存在,将删除该分块", i)); |
||||
blockFile.delete(); |
||||
} |
||||
} |
||||
} |
||||
BufferedRandomAccessFile file = null; |
||||
try { |
||||
if (mTotalThreadNum > 1 && !mRecord.isBlock) { |
||||
file = new BufferedRandomAccessFile(new File(mTempFile.getPath()), "rwd", 8192); |
||||
//设置文件长度
|
||||
file.setLength(mEntity.getFileSize()); |
||||
} |
||||
return true; |
||||
} catch (IOException e) { |
||||
failDownload(new TaskException(TAG, |
||||
String.format("下载失败,filePath: %s, url: %s", mEntity.getDownloadPath(), |
||||
mEntity.getUrl()), e)); |
||||
} finally { |
||||
if (file != null) { |
||||
try { |
||||
file.close(); |
||||
} catch (IOException e) { |
||||
e.printStackTrace(); |
||||
} |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
/** |
||||
* 如果使用"Content-Disposition"中的文件名,需要更新{@link #mTempFile}的路径 |
||||
*/ |
||||
void updateTempFile() { |
||||
if (!mTempFile.getPath().equals(mEntity.getFilePath())) { |
||||
if (!mTempFile.exists()) { |
||||
mTempFile = new File(mEntity.getFilePath()); |
||||
} else { |
||||
boolean b = mTempFile.renameTo(new File(mEntity.getDownloadPath())); |
||||
ALog.d(TAG, String.format("更新tempFile文件名%s", b ? "成功" : "失败")); |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Event |
||||
public void setMaxSpeed(SpeedEvent event) { |
||||
setMaxSpeed(event.speed); |
||||
} |
||||
|
||||
@Override protected void onPostPre() { |
||||
super.onPostPre(); |
||||
((IDownloadListener) mListener).onPostPre(mEntity.getFileSize()); |
||||
File file = new File(mEntity.getDownloadPath()); |
||||
if (!file.getParentFile().exists()) { |
||||
file.getParentFile().mkdirs(); |
||||
} |
||||
} |
||||
|
||||
@Override protected AbsThreadTask selectThreadTask(SubThreadConfig<DTaskWrapper> config) { |
||||
switch (mTaskWrapper.getRequestType()) { |
||||
case ITaskWrapper.D_FTP: |
||||
case ITaskWrapper.D_FTP_DIR: |
||||
return new FtpThreadTask(config); |
||||
case ITaskWrapper.D_HTTP: |
||||
return new HttpThreadTask(config); |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
private void failDownload(BaseException e) { |
||||
closeTimer(); |
||||
mListener.onFail(false, e); |
||||
} |
||||
} |
@ -1,61 +0,0 @@ |
||||
/* |
||||
* 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.core.download.downloader; |
||||
|
||||
import aria.apache.commons.net.ftp.FTPFile; |
||||
import com.arialyy.aria.core.common.CompleteInfo; |
||||
import com.arialyy.aria.core.common.OnFileInfoCallback; |
||||
import com.arialyy.aria.core.common.ftp.AbsFtpInfoThread; |
||||
import com.arialyy.aria.core.download.DTaskWrapper; |
||||
import com.arialyy.aria.core.download.DownloadEntity; |
||||
import com.arialyy.aria.exception.AriaIOException; |
||||
import com.arialyy.aria.util.ALog; |
||||
import com.arialyy.aria.util.FileUtil; |
||||
|
||||
/** |
||||
* Created by Aria.Lao on 2017/7/25. |
||||
* 获取ftp文件信息 |
||||
*/ |
||||
class FtpFileInfoThread extends AbsFtpInfoThread<DownloadEntity, DTaskWrapper> { |
||||
private final String TAG = "FtpFileInfoThread"; |
||||
|
||||
FtpFileInfoThread(DTaskWrapper taskEntity, OnFileInfoCallback callback) { |
||||
super(taskEntity, callback); |
||||
} |
||||
|
||||
@Override protected void handleFile(String remotePath, FTPFile ftpFile) { |
||||
super.handleFile(remotePath, ftpFile); |
||||
if (!FileUtil.checkSDMemorySpace(mEntity.getFilePath(), ftpFile.getSize())) { |
||||
mCallback.onFail(mEntity, new AriaIOException(TAG, |
||||
String.format("获取ftp文件信息失败,内存空间不足, filePath: %s", mEntity.getFilePath())), |
||||
false); |
||||
} |
||||
} |
||||
|
||||
@Override protected String getRemotePath() { |
||||
return mTaskWrapper.asFtp().getUrlEntity().remotePath; |
||||
} |
||||
|
||||
@Override protected void onPreComplete(int code) { |
||||
ALog.i(TAG, "FTP下载预处理完成"); |
||||
super.onPreComplete(code); |
||||
if (mSize != mTaskWrapper.getEntity().getFileSize()) { |
||||
mTaskWrapper.setNewTask(true); |
||||
} |
||||
mEntity.setFileSize(mSize); |
||||
mCallback.onComplete(mEntity.getUrl(), new CompleteInfo(code, mTaskWrapper)); |
||||
} |
||||
} |
@ -1,140 +0,0 @@ |
||||
/* |
||||
* 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.core.download.downloader; |
||||
|
||||
import com.arialyy.aria.core.common.CompleteInfo; |
||||
import com.arialyy.aria.core.common.IUtil; |
||||
import com.arialyy.aria.core.common.OnFileInfoCallback; |
||||
import com.arialyy.aria.core.download.DTaskWrapper; |
||||
import com.arialyy.aria.core.inf.AbsEntity; |
||||
import com.arialyy.aria.core.inf.IDownloadListener; |
||||
import com.arialyy.aria.core.inf.ITaskWrapper; |
||||
import com.arialyy.aria.exception.BaseException; |
||||
|
||||
/** |
||||
* Created by lyy on 2015/8/25. |
||||
* D_HTTP\FTP单任务下载工具 |
||||
*/ |
||||
public class SimpleDownloadUtil implements IUtil { |
||||
private String TAG = "SimpleDownloadUtil"; |
||||
private IDownloadListener mListener; |
||||
private Downloader mDownloader; |
||||
private DTaskWrapper mTaskWrapper; |
||||
private boolean isStop = false, isCancel = false; |
||||
|
||||
public SimpleDownloadUtil(DTaskWrapper wrapper, IDownloadListener downloadListener) { |
||||
mTaskWrapper = wrapper; |
||||
mListener = downloadListener; |
||||
mDownloader = new Downloader(downloadListener, wrapper); |
||||
} |
||||
|
||||
@Override public String getKey() { |
||||
return mTaskWrapper.getKey(); |
||||
} |
||||
|
||||
@Override public long getFileSize() { |
||||
return mDownloader.getFileSize(); |
||||
} |
||||
|
||||
/** |
||||
* 获取当前下载位置 |
||||
*/ |
||||
@Override public long getCurrentLocation() { |
||||
return mDownloader.getCurrentLocation(); |
||||
} |
||||
|
||||
@Override public boolean isRunning() { |
||||
return mDownloader.isRunning(); |
||||
} |
||||
|
||||
/** |
||||
* 取消下载 |
||||
*/ |
||||
@Override public void cancel() { |
||||
isCancel = true; |
||||
mDownloader.cancel(); |
||||
} |
||||
|
||||
/** |
||||
* 停止下载 |
||||
*/ |
||||
@Override public void stop() { |
||||
isStop = true; |
||||
mDownloader.stop(); |
||||
} |
||||
|
||||
/** |
||||
* 多线程断点续传下载文件,开始下载 |
||||
*/ |
||||
@Override public void start() { |
||||
if (isStop || isCancel) { |
||||
return; |
||||
} |
||||
mListener.onPre(); |
||||
// 如果网址没有变,而服务器端端文件改变,以下代码就没有用了
|
||||
//if (mTaskWrapper.getEntity().getFileSize() <= 1
|
||||
// || mTaskWrapper.isRefreshInfo()
|
||||
// || mTaskWrapper.getRequestType() == AbsTaskWrapper.D_FTP
|
||||
// || mTaskWrapper.getState() == IEntity.STATE_FAIL) {
|
||||
// new Thread(createInfoThread()).create();
|
||||
//} else {
|
||||
// mDownloader.create();
|
||||
//}
|
||||
new Thread(createInfoThread()).start(); |
||||
} |
||||
|
||||
private void failDownload(BaseException e, boolean needRetry) { |
||||
if (isStop || isCancel) { |
||||
return; |
||||
} |
||||
mListener.onFail(needRetry, e); |
||||
mDownloader.onDestroy(); |
||||
} |
||||
|
||||
/** |
||||
* 通过链接类型创建不同的获取文件信息的线程 |
||||
*/ |
||||
private Runnable createInfoThread() { |
||||
switch (mTaskWrapper.getRequestType()) { |
||||
case ITaskWrapper.D_FTP: |
||||
return new FtpFileInfoThread(mTaskWrapper, new OnFileInfoCallback() { |
||||
@Override public void onComplete(String url, CompleteInfo info) { |
||||
mDownloader.updateTempFile(); |
||||
mDownloader.start(); |
||||
} |
||||
|
||||
@Override public void onFail(AbsEntity entity, BaseException e, boolean needRetry) { |
||||
failDownload(e, needRetry); |
||||
mDownloader.closeTimer(); |
||||
} |
||||
}); |
||||
case ITaskWrapper.D_HTTP: |
||||
return new HttpFileInfoThread(mTaskWrapper, new OnFileInfoCallback() { |
||||
@Override public void onComplete(String url, CompleteInfo info) { |
||||
mDownloader.updateTempFile(); |
||||
mDownloader.start(); |
||||
} |
||||
|
||||
@Override public void onFail(AbsEntity entity, BaseException e, boolean needRetry) { |
||||
failDownload(e, needRetry); |
||||
mDownloader.closeTimer(); |
||||
} |
||||
}); |
||||
} |
||||
return null; |
||||
} |
||||
} |
@ -1,286 +0,0 @@ |
||||
/* |
||||
* 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.core.download.group; |
||||
|
||||
import android.os.Handler; |
||||
import android.os.Looper; |
||||
import com.arialyy.aria.core.common.IUtil; |
||||
import com.arialyy.aria.core.config.Configuration; |
||||
import com.arialyy.aria.core.download.DGTaskWrapper; |
||||
import com.arialyy.aria.core.download.DTaskWrapper; |
||||
import com.arialyy.aria.core.inf.IEntity; |
||||
import com.arialyy.aria.util.ALog; |
||||
import java.util.Map; |
||||
import java.util.WeakHashMap; |
||||
import java.util.concurrent.ScheduledThreadPoolExecutor; |
||||
import java.util.concurrent.TimeUnit; |
||||
|
||||
/** |
||||
* Created by AriaL on 2017/6/30. |
||||
* 任务组核心逻辑 |
||||
*/ |
||||
public abstract class AbsGroupUtil implements IUtil { |
||||
private final String TAG = "AbsGroupUtil"; |
||||
/** |
||||
* FTP文件夹 |
||||
*/ |
||||
int FTP_DIR = 0xa1; |
||||
/** |
||||
* D_HTTP 任务组 |
||||
*/ |
||||
int HTTP_GROUP = 0xa2; |
||||
|
||||
private long mCurrentLocation = 0; |
||||
protected IDGroupListener mListener; |
||||
private ScheduledThreadPoolExecutor mTimer; |
||||
private long mUpdateInterval; |
||||
private boolean isStop = false, isCancel = false; |
||||
private Handler mScheduler; |
||||
private SimpleSubQueue mSubQueue = SimpleSubQueue.newInstance(); |
||||
private Map<String, SubDLoadUtil> mExeLoader = new WeakHashMap<>(); |
||||
private Map<String, DTaskWrapper> mCache = new WeakHashMap<>(); |
||||
DGTaskWrapper mGTWrapper; |
||||
GroupRunState mState; |
||||
|
||||
AbsGroupUtil(IDGroupListener listener, DGTaskWrapper groupWrapper) { |
||||
mListener = listener; |
||||
mGTWrapper = groupWrapper; |
||||
mUpdateInterval = Configuration.getInstance().downloadCfg.getUpdateInterval(); |
||||
} |
||||
|
||||
/** |
||||
* 初始化组合任务状态 |
||||
*/ |
||||
void initState() { |
||||
mState = |
||||
new GroupRunState(mGTWrapper.getKey(), mListener, mGTWrapper.getSubTaskWrapper().size(), |
||||
mSubQueue); |
||||
mScheduler = new Handler(Looper.getMainLooper(), SimpleSchedulers.newInstance(mState)); |
||||
for (DTaskWrapper wrapper : mGTWrapper.getSubTaskWrapper()) { |
||||
if (wrapper.getEntity().getState() == IEntity.STATE_COMPLETE) { |
||||
mState.updateCompleteNum(); |
||||
mCurrentLocation += wrapper.getEntity().getFileSize(); |
||||
} else { |
||||
mCache.put(wrapper.getKey(), wrapper); |
||||
mCurrentLocation += wrapper.getEntity().getCurrentProgress(); |
||||
} |
||||
} |
||||
mState.updateProgress(mCurrentLocation); |
||||
} |
||||
|
||||
@Override public String getKey() { |
||||
return mGTWrapper.getKey(); |
||||
} |
||||
|
||||
/** |
||||
* 获取任务类型 |
||||
* |
||||
* @return {@link #FTP_DIR}、{@link #HTTP_GROUP} |
||||
*/ |
||||
abstract int getTaskType(); |
||||
|
||||
/** |
||||
* 启动子任务下载 |
||||
* |
||||
* @param url 子任务下载地址 |
||||
*/ |
||||
public void startSubTask(String url) { |
||||
if (!checkSubTask(url, "开始")) return; |
||||
if (!mState.isRunning) { |
||||
startTimer(); |
||||
} |
||||
SubDLoadUtil d = getDownloader(url); |
||||
if (d != null && !d.isRunning()) { |
||||
mSubQueue.startTask(d); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 停止子任务下载 |
||||
* |
||||
* @param url 子任务下载地址 |
||||
*/ |
||||
public void stopSubTask(String url) { |
||||
if (!checkSubTask(url, "停止")) return; |
||||
SubDLoadUtil d = getDownloader(url); |
||||
if (d != null && d.isRunning()) { |
||||
mSubQueue.stopTask(d); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 检查子任务 |
||||
* |
||||
* @param url 子任务url |
||||
* @param type 任务类型 |
||||
* @return {@code true} 任务可以下载 |
||||
*/ |
||||
private boolean checkSubTask(String url, String type) { |
||||
DTaskWrapper wrapper = mCache.get(url); |
||||
if (wrapper != null) { |
||||
if (wrapper.getState() == IEntity.STATE_COMPLETE) { |
||||
ALog.w(TAG, "任务【" + url + "】已完成," + type + "失败"); |
||||
return false; |
||||
} |
||||
} else { |
||||
ALog.w(TAG, "任务组中没有该任务【" + url + "】," + type + "失败"); |
||||
return false; |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
/** |
||||
* 通过地址获取下载器 |
||||
* |
||||
* @param url 子任务下载地址 |
||||
*/ |
||||
private SubDLoadUtil getDownloader(String url) { |
||||
SubDLoadUtil d = mExeLoader.get(url); |
||||
if (d == null) { |
||||
return createAndStartSubLoader(mCache.get(url)); |
||||
} |
||||
return d; |
||||
} |
||||
|
||||
@Override public long getFileSize() { |
||||
return mGTWrapper.getEntity().getFileSize(); |
||||
} |
||||
|
||||
@Override public long getCurrentLocation() { |
||||
return mCurrentLocation; |
||||
} |
||||
|
||||
@Override public boolean isRunning() { |
||||
return mState != null && mState.isRunning; |
||||
} |
||||
|
||||
@Override public void cancel() { |
||||
isCancel = true; |
||||
closeTimer(); |
||||
onPreCancel(); |
||||
|
||||
mSubQueue.removeAllTask(); |
||||
mListener.onCancel(); |
||||
} |
||||
|
||||
/** |
||||
* onCancel前的操作 |
||||
*/ |
||||
public void onPreCancel() { |
||||
|
||||
} |
||||
|
||||
@Override public void stop() { |
||||
isStop = true; |
||||
closeTimer(); |
||||
if (onPreStop()) { |
||||
return; |
||||
} |
||||
mSubQueue.stopAllTask(); |
||||
} |
||||
|
||||
/** |
||||
* onStop前的操作 |
||||
* |
||||
* @return 返回{@code true},直接回调{@link IDGroupListener#onStop(long)} |
||||
*/ |
||||
protected boolean onPreStop() { |
||||
|
||||
return false; |
||||
} |
||||
|
||||
@Override public void start() { |
||||
if (isStop || isCancel) { |
||||
closeTimer(); |
||||
return; |
||||
} |
||||
if (onPreStart()) { |
||||
startRunningFlow(); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 处理启动前的检查:获取组合任务大小 |
||||
* |
||||
* @return {@code false} 将不再走后续流程,任务介绍 |
||||
*/ |
||||
protected boolean onPreStart() { |
||||
return false; |
||||
} |
||||
|
||||
synchronized void closeTimer() { |
||||
if (mTimer != null && !mTimer.isShutdown()) { |
||||
mTimer.shutdown(); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 开始进度流程 |
||||
*/ |
||||
private void startRunningFlow() { |
||||
closeTimer(); |
||||
mListener.onPostPre(mGTWrapper.getEntity().getFileSize()); |
||||
if (mCurrentLocation > 0) { |
||||
mListener.onResume(mCurrentLocation); |
||||
} else { |
||||
mListener.onStart(mCurrentLocation); |
||||
} |
||||
startTimer(); |
||||
} |
||||
|
||||
private synchronized void startTimer() { |
||||
mState.isRunning = true; |
||||
mTimer = new ScheduledThreadPoolExecutor(1); |
||||
mTimer.scheduleWithFixedDelay(new Runnable() { |
||||
@Override public void run() { |
||||
if (!mState.isRunning) { |
||||
closeTimer(); |
||||
} else if (mCurrentLocation >= 0) { |
||||
long t = 0; |
||||
for (DTaskWrapper te : mGTWrapper.getSubTaskWrapper()) { |
||||
if (te.getState() == IEntity.STATE_COMPLETE) { |
||||
t += te.getEntity().getFileSize(); |
||||
} else { |
||||
t += te.getEntity().getCurrentProgress(); |
||||
} |
||||
} |
||||
mCurrentLocation = t; |
||||
mState.updateProgress(mCurrentLocation); |
||||
mListener.onProgress(t); |
||||
} |
||||
} |
||||
}, 0, mUpdateInterval, TimeUnit.MILLISECONDS); |
||||
} |
||||
|
||||
/** |
||||
* 创建并启动子任务下载器 |
||||
*/ |
||||
SubDLoadUtil createAndStartSubLoader(DTaskWrapper taskWrapper) { |
||||
return createAndStartSubLoader(taskWrapper, true); |
||||
} |
||||
|
||||
/** |
||||
* 创建并启动子任务下载器 |
||||
* |
||||
* @param needGetFileInfo {@code true} 需要获取文件信息。{@code false} 不需要获取文件信息 |
||||
*/ |
||||
SubDLoadUtil createAndStartSubLoader(DTaskWrapper taskWrapper, boolean needGetFileInfo) { |
||||
SubDLoadUtil loader = new SubDLoadUtil(mScheduler, taskWrapper, needGetFileInfo); |
||||
mExeLoader.put(loader.getKey(), loader); |
||||
mSubQueue.startTask(loader); |
||||
return loader; |
||||
} |
||||
} |
@ -1,147 +0,0 @@ |
||||
/* |
||||
* 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.core.download.group; |
||||
|
||||
import android.os.Handler; |
||||
import com.arialyy.aria.core.download.DownloadEntity; |
||||
import com.arialyy.aria.core.inf.IDownloadListener; |
||||
import com.arialyy.aria.core.inf.IEntity; |
||||
import com.arialyy.aria.core.scheduler.ISchedulers; |
||||
import com.arialyy.aria.exception.BaseException; |
||||
import com.arialyy.aria.util.ALog; |
||||
import com.arialyy.aria.util.CommonUtil; |
||||
|
||||
/** |
||||
* 子任务事件监听 |
||||
*/ |
||||
class ChildDLoadListener implements IDownloadListener { |
||||
private DownloadEntity subEntity; |
||||
private int RUN_SAVE_INTERVAL = 5 * 1000; //5s保存一次下载中的进度
|
||||
private long lastSaveTime; |
||||
private long lastLen; |
||||
private Handler schedulers; |
||||
private SubDLoadUtil loader; |
||||
|
||||
ChildDLoadListener(Handler schedulers, SubDLoadUtil loader) { |
||||
this.loader = loader; |
||||
this.schedulers = schedulers; |
||||
subEntity = loader.getEntity(); |
||||
subEntity.setFailNum(0); |
||||
lastLen = subEntity.getCurrentProgress(); |
||||
lastSaveTime = System.currentTimeMillis(); |
||||
} |
||||
|
||||
@Override public void supportBreakpoint(boolean support) { |
||||
|
||||
} |
||||
|
||||
@Override public void onPre() { |
||||
saveData(IEntity.STATE_PRE, subEntity.getCurrentProgress()); |
||||
} |
||||
|
||||
@Override public void onPostPre(long fileSize) { |
||||
subEntity.setFileSize(fileSize); |
||||
subEntity.setConvertFileSize(CommonUtil.formatFileSize(fileSize)); |
||||
saveData(IEntity.STATE_POST_PRE, subEntity.getCurrentProgress()); |
||||
sendToTarget(ISchedulers.POST_PRE, loader); |
||||
} |
||||
|
||||
@Override public void onResume(long resumeLocation) { |
||||
lastLen = resumeLocation; |
||||
saveData(IEntity.STATE_POST_PRE, subEntity.getCurrentProgress()); |
||||
sendToTarget(ISchedulers.START, loader); |
||||
} |
||||
|
||||
@Override public void onStart(long startLocation) { |
||||
lastLen = startLocation; |
||||
saveData(IEntity.STATE_POST_PRE, subEntity.getCurrentProgress()); |
||||
sendToTarget(ISchedulers.START, loader); |
||||
} |
||||
|
||||
@Override public void onProgress(long currentLocation) { |
||||
long diff = currentLocation - lastLen; |
||||
//mCurrentLocation += speed;
|
||||
subEntity.setCurrentProgress(currentLocation); |
||||
handleSpeed(diff); |
||||
sendToTarget(ISchedulers.RUNNING, loader); |
||||
if (System.currentTimeMillis() - lastSaveTime >= RUN_SAVE_INTERVAL) { |
||||
saveData(IEntity.STATE_RUNNING, currentLocation); |
||||
lastSaveTime = System.currentTimeMillis(); |
||||
} |
||||
lastLen = currentLocation; |
||||
} |
||||
|
||||
@Override public void onStop(long stopLocation) { |
||||
handleSpeed(0); |
||||
saveData(IEntity.STATE_STOP, stopLocation); |
||||
sendToTarget(ISchedulers.STOP, loader); |
||||
} |
||||
|
||||
/** |
||||
* 组合任务子任务不允许删除 |
||||
*/ |
||||
@Deprecated |
||||
@Override public void onCancel() { |
||||
|
||||
} |
||||
|
||||
@Override public void onComplete() { |
||||
subEntity.setComplete(true); |
||||
saveData(IEntity.STATE_COMPLETE, subEntity.getFileSize()); |
||||
handleSpeed(0); |
||||
sendToTarget(ISchedulers.COMPLETE, loader); |
||||
} |
||||
|
||||
@Override public void onFail(boolean needRetry, BaseException e) { |
||||
subEntity.setFailNum(subEntity.getFailNum() + 1); |
||||
saveData(IEntity.STATE_FAIL, subEntity.getCurrentProgress()); |
||||
handleSpeed(0); |
||||
sendToTarget(ISchedulers.FAIL, loader); |
||||
} |
||||
|
||||
private void handleSpeed(long speed) { |
||||
subEntity.setSpeed(speed); |
||||
subEntity.setConvertSpeed( |
||||
speed <= 0 ? "" : String.format("%s/s", CommonUtil.formatFileSize(speed))); |
||||
subEntity.setPercent((int) (subEntity.getFileSize() <= 0 ? 0 |
||||
: subEntity.getCurrentProgress() * 100 / subEntity.getFileSize())); |
||||
} |
||||
|
||||
private void saveData(int state, long location) { |
||||
loader.getWrapper().setState(state); |
||||
subEntity.setState(state); |
||||
subEntity.setComplete(state == IEntity.STATE_COMPLETE); |
||||
if (state == IEntity.STATE_CANCEL) { |
||||
subEntity.deleteData(); |
||||
} else if (state == IEntity.STATE_STOP) { |
||||
subEntity.setStopTime(System.currentTimeMillis()); |
||||
} else if (subEntity.isComplete()) { |
||||
subEntity.setCompleteTime(System.currentTimeMillis()); |
||||
subEntity.setCurrentProgress(subEntity.getFileSize()); |
||||
} else if (location > 0) { |
||||
subEntity.setCurrentProgress(location); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 发送状态到子任务调度器{@link SimpleSchedulers},让调度器处理任务调度 |
||||
* |
||||
* @param state {@link ISchedulers} |
||||
*/ |
||||
private void sendToTarget(int state, SubDLoadUtil util) { |
||||
schedulers.obtainMessage(state, util).sendToTarget(); |
||||
} |
||||
} |
@ -1,188 +0,0 @@ |
||||
/* |
||||
* 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.core.download.group; |
||||
|
||||
import com.arialyy.aria.core.common.CompleteInfo; |
||||
import com.arialyy.aria.core.common.IUtil; |
||||
import com.arialyy.aria.core.common.OnFileInfoCallback; |
||||
import com.arialyy.aria.core.common.http.HttpTaskConfig; |
||||
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.download.downloader.HttpFileInfoThread; |
||||
import com.arialyy.aria.core.inf.AbsEntity; |
||||
import com.arialyy.aria.core.inf.IEntity; |
||||
import com.arialyy.aria.exception.AriaIOException; |
||||
import com.arialyy.aria.exception.BaseException; |
||||
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 DGroupUtil extends AbsGroupUtil implements IUtil { |
||||
private static final String TAG = "DownloadGroupUtil"; |
||||
private final Object LOCK = new Object(); |
||||
private ExecutorService mPool = null; |
||||
private boolean getLenComplete = false; |
||||
private List<DTaskWrapper> mTempWrapper = new ArrayList<>(); |
||||
|
||||
public DGroupUtil(IDGroupListener listener, DGTaskWrapper taskWrapper) { |
||||
super(listener, taskWrapper); |
||||
} |
||||
|
||||
@Override int getTaskType() { |
||||
return HTTP_GROUP; |
||||
} |
||||
|
||||
@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 onPreStart() { |
||||
super.onPreStart(); |
||||
initState(); |
||||
if (mState.getCompleteNum() == mState.getSubSize()) { |
||||
mListener.onComplete(); |
||||
} else { |
||||
// 处理组合任务大小未知的情况
|
||||
if (mGTWrapper.isUnknownSize() && mGTWrapper.getEntity().getFileSize() < 1) { |
||||
mPool = Executors.newCachedThreadPool(); |
||||
getGroupSize(); |
||||
try { |
||||
synchronized (LOCK) { |
||||
LOCK.wait(); |
||||
} |
||||
} catch (InterruptedException e) { |
||||
e.printStackTrace(); |
||||
} |
||||
return getLenComplete; |
||||
} else { |
||||
for (DTaskWrapper wrapper : mGTWrapper.getSubTaskWrapper()) { |
||||
if (wrapper.getState() != IEntity.STATE_COMPLETE) { |
||||
createAndStartSubLoader(wrapper); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
/** |
||||
* 获取组合任务大小,使用该方式获取到的组合任务大小,子任务不需要再重新获取文件大小 |
||||
*/ |
||||
private void getGroupSize() { |
||||
new Thread(new Runnable() { |
||||
int count; |
||||
int failCount; |
||||
|
||||
@Override public void run() { |
||||
for (DTaskWrapper dTaskWrapper : mGTWrapper.getSubTaskWrapper()) { |
||||
cloneHeader(dTaskWrapper); |
||||
mPool.submit(new HttpFileInfoThread(dTaskWrapper, new OnFileInfoCallback() { |
||||
@Override public void onComplete(String url, CompleteInfo info) { |
||||
if (!mGTWrapper.isUnknownSize()) { |
||||
createAndStartSubLoader((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); |
||||
mState.countFailNum(entity.getKey()); |
||||
} |
||||
})); |
||||
} |
||||
} |
||||
}).start(); |
||||
} |
||||
|
||||
/** |
||||
* 检查组合任务大小是否获取完成,获取完成后取消阻塞,并设置组合任务大小 |
||||
*/ |
||||
private void checkGetSizeComplete(int count, int failCount) { |
||||
if (failCount == mGTWrapper.getSubTaskWrapper().size()) { |
||||
mState.isRunning = false; |
||||
mListener.onFail(false, new AriaIOException(TAG, "获取子任务长度失败")); |
||||
notifyLock(); |
||||
return; |
||||
} |
||||
if (count == mGTWrapper.getSubTaskWrapper().size()) { |
||||
long size = 0; |
||||
for (DTaskWrapper wrapper : mGTWrapper.getSubTaskWrapper()) { |
||||
size += wrapper.getEntity().getFileSize(); |
||||
} |
||||
mGTWrapper.getEntity().setConvertFileSize(CommonUtil.formatFileSize(size)); |
||||
mGTWrapper.getEntity().setFileSize(size); |
||||
mGTWrapper.getEntity().update(); |
||||
getLenComplete = true; |
||||
ALog.d(TAG, String.format("获取组合任务长度完成,组合任务总长度:%s,失败的只任务数:%s", size, failCount)); |
||||
// 未知大小的组合任务,延迟下载
|
||||
if (mGTWrapper.isUnknownSize()) { |
||||
for (DTaskWrapper wrapper : mTempWrapper) { |
||||
createAndStartSubLoader(wrapper, false); |
||||
} |
||||
} |
||||
notifyLock(); |
||||
} |
||||
} |
||||
|
||||
private void notifyLock() { |
||||
synchronized (LOCK) { |
||||
LOCK.notifyAll(); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 子任务使用父包裹器的属性 |
||||
*/ |
||||
private void cloneHeader(DTaskWrapper taskWrapper) { |
||||
HttpTaskConfig groupDelegate = mGTWrapper.asHttp(); |
||||
HttpTaskConfig subDelegate = taskWrapper.asHttp(); |
||||
|
||||
// 设置属性
|
||||
subDelegate.setFileLenAdapter(groupDelegate.getFileLenAdapter()); |
||||
subDelegate.setRequestEnum(groupDelegate.getRequestEnum()); |
||||
subDelegate.setHeaders(groupDelegate.getHeaders()); |
||||
subDelegate.setProxy(groupDelegate.getProxy()); |
||||
subDelegate.setParams(groupDelegate.getParams()); |
||||
} |
||||
} |
@ -1,109 +0,0 @@ |
||||
/* |
||||
* 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.core.download.group; |
||||
|
||||
import android.net.Uri; |
||||
import android.text.TextUtils; |
||||
import com.arialyy.aria.core.common.ftp.FtpUrlEntity; |
||||
import com.arialyy.aria.core.common.CompleteInfo; |
||||
import com.arialyy.aria.core.common.OnFileInfoCallback; |
||||
import com.arialyy.aria.core.download.DGTaskWrapper; |
||||
import com.arialyy.aria.core.download.DTaskWrapper; |
||||
import com.arialyy.aria.core.inf.AbsEntity; |
||||
import com.arialyy.aria.core.inf.IEntity; |
||||
import com.arialyy.aria.exception.BaseException; |
||||
import java.util.concurrent.locks.Condition; |
||||
import java.util.concurrent.locks.ReentrantLock; |
||||
|
||||
/** |
||||
* Created by Aria.Lao on 2017/7/27. |
||||
* ftp文件夹下载工具 |
||||
*/ |
||||
public class FtpDirDownloadUtil extends AbsGroupUtil { |
||||
private String TAG = "FtpDirDownloadUtil"; |
||||
private ReentrantLock LOCK = new ReentrantLock(); |
||||
private Condition condition = LOCK.newCondition(); |
||||
|
||||
public FtpDirDownloadUtil(IDGroupListener listener, DGTaskWrapper taskEntity) { |
||||
super(listener, taskEntity); |
||||
} |
||||
|
||||
@Override int getTaskType() { |
||||
return FTP_DIR; |
||||
} |
||||
|
||||
@Override protected boolean onPreStart() { |
||||
super.onPreStart(); |
||||
|
||||
if (mGTWrapper.getEntity().getFileSize() > 1) { |
||||
startDownload(true); |
||||
} else { |
||||
FtpDirInfoThread infoThread = new FtpDirInfoThread(mGTWrapper, new OnFileInfoCallback() { |
||||
@Override public void onComplete(String url, CompleteInfo info) { |
||||
if (info.code >= 200 && info.code < 300) { |
||||
startDownload(false); |
||||
} |
||||
} |
||||
|
||||
@Override public void onFail(AbsEntity entity, BaseException e, boolean needRetry) { |
||||
mListener.onFail(needRetry, e); |
||||
} |
||||
}); |
||||
new Thread(infoThread).start(); |
||||
try { |
||||
LOCK.lock(); |
||||
condition.await(); |
||||
} catch (InterruptedException e) { |
||||
e.printStackTrace(); |
||||
} finally { |
||||
LOCK.unlock(); |
||||
} |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
/** |
||||
* @param needCloneInfo 第一次下载,信息已经在{@link FtpDirInfoThread}中clone了 |
||||
*/ |
||||
private void startDownload(boolean needCloneInfo) { |
||||
try { |
||||
LOCK.lock(); |
||||
condition.signalAll(); |
||||
} finally { |
||||
LOCK.unlock(); |
||||
} |
||||
initState(); |
||||
for (DTaskWrapper wrapper : mGTWrapper.getSubTaskWrapper()) { |
||||
if (needCloneInfo) { |
||||
cloneInfo(wrapper); |
||||
} |
||||
if (wrapper.getState() != IEntity.STATE_COMPLETE) { |
||||
createAndStartSubLoader(wrapper); |
||||
} |
||||
} |
||||
} |
||||
|
||||
private void cloneInfo(DTaskWrapper subWrapper) { |
||||
FtpUrlEntity urlEntity = mGTWrapper.asFtp().getUrlEntity().clone(); |
||||
Uri uri = Uri.parse(subWrapper.getEntity().getUrl()); |
||||
String remotePath = uri.getPath(); |
||||
urlEntity.remotePath = TextUtils.isEmpty(remotePath) ? "/" : remotePath; |
||||
|
||||
subWrapper.asFtp().setUrlEntity(urlEntity); |
||||
subWrapper.asFtp().setCharSet(mGTWrapper.asFtp().getCharSet()); |
||||
subWrapper.asFtp().setProxy(mGTWrapper.asFtp().getProxy()); |
||||
} |
||||
} |
@ -1,104 +0,0 @@ |
||||
/* |
||||
* 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.core.download.group; |
||||
|
||||
import aria.apache.commons.net.ftp.FTPFile; |
||||
import com.arialyy.aria.core.common.ftp.FtpUrlEntity; |
||||
import com.arialyy.aria.core.common.CompleteInfo; |
||||
import com.arialyy.aria.core.common.OnFileInfoCallback; |
||||
import com.arialyy.aria.core.common.ftp.AbsFtpInfoThread; |
||||
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.download.DownloadGroupEntity; |
||||
import com.arialyy.aria.core.inf.AbsTaskWrapper; |
||||
import com.arialyy.aria.exception.BaseException; |
||||
import com.arialyy.aria.util.CommonUtil; |
||||
import com.arialyy.aria.util.RecordUtil; |
||||
import java.nio.charset.Charset; |
||||
import java.util.ArrayList; |
||||
|
||||
/** |
||||
* Created by Aria.Lao on 2017/7/25. 获取ftp文件夹信息 |
||||
*/ |
||||
class FtpDirInfoThread extends AbsFtpInfoThread<DownloadGroupEntity, DGTaskWrapper> { |
||||
|
||||
FtpDirInfoThread(DGTaskWrapper taskEntity, OnFileInfoCallback callback) { |
||||
super(taskEntity, callback); |
||||
} |
||||
|
||||
@Override protected String getRemotePath() { |
||||
return mTaskWrapper.asFtp().getUrlEntity().remotePath; |
||||
} |
||||
|
||||
@Override protected void handleFile(String remotePath, FTPFile ftpFile) { |
||||
super.handleFile(remotePath, ftpFile); |
||||
addEntity(remotePath, ftpFile); |
||||
} |
||||
|
||||
@Override protected void onPreComplete(int code) { |
||||
super.onPreComplete(code); |
||||
mEntity.setFileSize(mSize); |
||||
mCallback.onComplete(mEntity.getKey(), new CompleteInfo(code, mTaskWrapper)); |
||||
} |
||||
|
||||
/** |
||||
* FTP文件夹的子任务实体 在这生成 |
||||
*/ |
||||
private void addEntity(String remotePath, FTPFile ftpFile) { |
||||
final FtpUrlEntity urlEntity = mTaskWrapper.asFtp().getUrlEntity().clone(); |
||||
DownloadEntity entity = new DownloadEntity(); |
||||
entity.setUrl( |
||||
urlEntity.scheme + "://" + urlEntity.hostName + ":" + urlEntity.port + "/" + remotePath); |
||||
entity.setFilePath(mEntity.getDirPath() + "/" + remotePath); |
||||
int lastIndex = remotePath.lastIndexOf("/"); |
||||
String fileName = lastIndex < 0 ? CommonUtil.keyToHashKey(remotePath) |
||||
: remotePath.substring(lastIndex + 1); |
||||
entity.setFileName( |
||||
new String(fileName.getBytes(), Charset.forName(mTaskWrapper.asHttp().getCharSet()))); |
||||
entity.setGroupHash(mEntity.getGroupHash()); |
||||
entity.setGroupChild(true); |
||||
entity.setConvertFileSize(CommonUtil.formatFileSize(ftpFile.getSize())); |
||||
entity.setFileSize(ftpFile.getSize()); |
||||
entity.insert(); |
||||
|
||||
DTaskWrapper subWrapper = new DTaskWrapper(entity); |
||||
subWrapper.setGroupTask(true); |
||||
subWrapper.setGroupHash(mEntity.getGroupHash()); |
||||
subWrapper.setRequestType(AbsTaskWrapper.D_FTP); |
||||
urlEntity.url = entity.getUrl(); |
||||
urlEntity.remotePath = remotePath; |
||||
|
||||
cloneInfo(subWrapper, urlEntity); |
||||
|
||||
if (mEntity.getUrls() == null) { |
||||
mEntity.setUrls(new ArrayList<String>()); |
||||
} |
||||
mEntity.getSubEntities().add(entity); |
||||
mTaskWrapper.getSubTaskWrapper().add(subWrapper); |
||||
} |
||||
|
||||
private void cloneInfo(DTaskWrapper subWrapper, FtpUrlEntity urlEntity) { |
||||
subWrapper.asFtp().setUrlEntity(urlEntity); |
||||
subWrapper.asFtp().setCharSet(mTaskWrapper.asFtp().getCharSet()); |
||||
subWrapper.asFtp().setProxy(mTaskWrapper.asFtp().getProxy()); |
||||
} |
||||
|
||||
@Override protected void failDownload(BaseException e, boolean needRetry) { |
||||
super.failDownload(e, needRetry); |
||||
RecordUtil.delGroupTaskRecord(mTaskWrapper.getEntity(), true, true); |
||||
} |
||||
} |
@ -1,65 +0,0 @@ |
||||
/* |
||||
* 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.core.download.group; |
||||
|
||||
import com.arialyy.aria.core.common.AbsNormalTarget; |
||||
import java.util.List; |
||||
|
||||
/** |
||||
* @Author aria |
||||
* @Date 2019-09-05 |
||||
*/ |
||||
public class GroupTargetFactory { |
||||
|
||||
public static volatile GroupTargetFactory INSTANCE; |
||||
|
||||
private GroupTargetFactory() { |
||||
|
||||
} |
||||
|
||||
public static GroupTargetFactory getInstance() { |
||||
|
||||
if (INSTANCE == null) { |
||||
synchronized (GroupTargetFactory.class) { |
||||
if (INSTANCE == null) { |
||||
INSTANCE = new GroupTargetFactory(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
return INSTANCE; |
||||
} |
||||
|
||||
public <T extends AbsNormalTarget> T generateNormalTarget(Class<T> clazz, long taskId, |
||||
String targetName) { |
||||
T target = null; |
||||
if (clazz == GroupNormalTarget.class) { |
||||
target = (T) new GroupNormalTarget(taskId, targetName); |
||||
} else if (clazz == FtpDirNormalTarget.class) { |
||||
target = (T) new FtpDirNormalTarget(taskId, targetName); |
||||
} |
||||
|
||||
return target; |
||||
} |
||||
|
||||
public FtpDirBuilderTarget generateDirBuilderTarget(String url, String targetName) { |
||||
return new FtpDirBuilderTarget(url, targetName); |
||||
} |
||||
|
||||
public GroupBuilderTarget generateGroupBuilderTarget(List<String> urls, String targetName) { |
||||
return new GroupBuilderTarget(urls, targetName); |
||||
} |
||||
} |
@ -1,193 +0,0 @@ |
||||
/* |
||||
* 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.core.download.group; |
||||
|
||||
import android.os.Message; |
||||
import com.arialyy.aria.core.AriaManager; |
||||
import com.arialyy.aria.core.config.Configuration; |
||||
import com.arialyy.aria.core.inf.AbsEntity; |
||||
import com.arialyy.aria.core.manager.ThreadTaskManager; |
||||
import com.arialyy.aria.core.scheduler.ISchedulers; |
||||
import com.arialyy.aria.exception.TaskException; |
||||
import com.arialyy.aria.util.ALog; |
||||
import com.arialyy.aria.util.NetUtils; |
||||
import java.util.concurrent.ScheduledThreadPoolExecutor; |
||||
import java.util.concurrent.TimeUnit; |
||||
|
||||
/** |
||||
* 组合任务子任务调度器,用于调度任务的开始、停止、失败、完成等情况 |
||||
* 该调度器生命周期和{@link AbsGroupUtil}生命周期一致 |
||||
*/ |
||||
class SimpleSchedulers implements ISchedulers { |
||||
private static final String TAG = "SimpleSchedulers"; |
||||
private SimpleSubQueue mQueue; |
||||
private GroupRunState mGState; |
||||
|
||||
private SimpleSchedulers(GroupRunState state) { |
||||
mQueue = state.queue; |
||||
mGState = state; |
||||
} |
||||
|
||||
static SimpleSchedulers newInstance(GroupRunState state) { |
||||
|
||||
return new SimpleSchedulers(state); |
||||
} |
||||
|
||||
@Override public boolean handleMessage(Message msg) { |
||||
SubDLoadUtil loader = (SubDLoadUtil) msg.obj; |
||||
switch (msg.what) { |
||||
case RUNNING: |
||||
mGState.listener.onSubRunning(loader.getEntity()); |
||||
break; |
||||
case PRE: |
||||
mGState.listener.onSubPre(loader.getEntity()); |
||||
mGState.updateCount(loader.getKey()); |
||||
break; |
||||
case START: |
||||
mGState.listener.onSubStart(loader.getEntity()); |
||||
break; |
||||
case STOP: |
||||
handleStop(loader); |
||||
break; |
||||
case COMPLETE: |
||||
handleComplete(loader); |
||||
break; |
||||
case FAIL: |
||||
handleFail(loader); |
||||
break; |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
/** |
||||
* 处理子任务失败的情况 |
||||
* 1、子任务失败次数大于等于配置的重试次数,才能认为子任务停止 |
||||
* 2、stopNum + failNum + completeNum + cacheNum == subSize,则认为组合任务停止 |
||||
* 3、failNum == subSize,只有全部的子任务都失败了,才能任务组合任务失败 |
||||
*/ |
||||
private synchronized void handleFail(final SubDLoadUtil loaderUtil) { |
||||
Configuration config = Configuration.getInstance(); |
||||
|
||||
long interval = config.dGroupCfg.getSubReTryInterval(); |
||||
int num = config.dGroupCfg.getSubReTryNum(); |
||||
boolean isNotNetRetry = config.appCfg.isNotNetRetry(); |
||||
|
||||
final int reTryNum = num; |
||||
if ((!NetUtils.isConnected(AriaManager.getInstance().getAPP()) && !isNotNetRetry) |
||||
|| loaderUtil.getDownloader() == null // 如果获取不到文件信息,loader为空
|
||||
|| loaderUtil.getEntity().getFailNum() > reTryNum) { |
||||
mQueue.removeTaskFromExecQ(loaderUtil); |
||||
mGState.listener.onSubFail(loaderUtil.getEntity(), new TaskException(TAG, |
||||
String.format("任务组子任务【%s】下载失败,下载地址【%s】", loaderUtil.getEntity().getFileName(), |
||||
loaderUtil.getEntity().getUrl()))); |
||||
mGState.countFailNum(loaderUtil.getKey()); |
||||
if (mGState.getFailNum() == mGState.getSubSize() |
||||
|| mGState.getStopNum() + mGState.getFailNum() + mGState.getCompleteNum() |
||||
== mGState.getSubSize()) { |
||||
mQueue.clear(); |
||||
mGState.isRunning = false; |
||||
mGState.listener.onFail(false, new TaskException(TAG, |
||||
String.format("任务组【%s】下载失败", mGState.getGroupHash()))); |
||||
} else { |
||||
startNext(); |
||||
} |
||||
return; |
||||
} |
||||
|
||||
// 如果获取不到文件信息,loader为空
|
||||
final ScheduledThreadPoolExecutor timer = new ScheduledThreadPoolExecutor(1); |
||||
timer.schedule(new Runnable() { |
||||
@Override public void run() { |
||||
AbsEntity entity = loaderUtil.getEntity(); |
||||
if (entity.getFailNum() <= reTryNum) { |
||||
ALog.d(TAG, String.format("任务【%s】开始重试", loaderUtil.getEntity().getFileName())); |
||||
loaderUtil.reStart(); |
||||
} else { |
||||
startNext(); |
||||
} |
||||
} |
||||
}, interval, TimeUnit.MILLISECONDS); |
||||
} |
||||
|
||||
/** |
||||
* 处理子任务停止的情况 |
||||
* 1、所有的子任务已经停止,则认为组合任务停止 |
||||
* 2、completeNum + failNum + stopNum = subSize,则认为组合任务停止 |
||||
*/ |
||||
private synchronized void handleStop(SubDLoadUtil loader) { |
||||
mGState.listener.onSubStop(loader.getEntity()); |
||||
mGState.countStopNum(loader.getKey()); |
||||
if (mGState.getStopNum() == mGState.getSubSize() |
||||
|| mGState.getStopNum() |
||||
+ mGState.getCompleteNum() |
||||
+ mGState.getFailNum() |
||||
+ mQueue.getCacheSize() |
||||
== mGState.getSubSize()) { |
||||
mQueue.clear(); |
||||
mGState.isRunning = false; |
||||
mGState.listener.onStop(mGState.getProgress()); |
||||
} else { |
||||
startNext(); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 处理子任务完成的情况,有以下三种情况 |
||||
* 1、已经没有缓存的子任务,并且停止的子任务是数{@link GroupRunState#getStopNum()} ()}为0,失败的子任数{@link |
||||
* GroupRunState#getFailNum()}为0,则认为组合任务已经完成 |
||||
* 2、已经没有缓存的子任务,并且停止的子任务是数{@link GroupRunState#getCompleteNum()}不为0,或者失败的子任数{@link |
||||
* GroupRunState#getFailNum()}不为0,则认为组合任务被停止 |
||||
* 3、只有有缓存的子任务,则任务组合任务没有完成 |
||||
*/ |
||||
private synchronized void handleComplete(SubDLoadUtil loader) { |
||||
ALog.d(TAG, String.format("子任务【%s】完成", loader.getEntity().getFileName())); |
||||
ThreadTaskManager.getInstance().removeTaskThread(loader.getKey()); |
||||
mGState.listener.onSubComplete(loader.getEntity()); |
||||
mQueue.removeTaskFromExecQ(loader); |
||||
mGState.updateCompleteNum(); |
||||
ALog.d(TAG, String.format("总任务数:%s,完成的任务数:%s,失败的任务数:%s,停止的任务数:%s", mGState.getSubSize(), |
||||
mGState.getCompleteNum(), mGState.getFailNum(), mGState.getStopNum())); |
||||
if (mGState.getCompleteNum() + mGState.getFailNum() + mGState.getStopNum() |
||||
== mGState.getSubSize()) { |
||||
if (mGState.getStopNum() == 0 && mGState.getFailNum() == 0) { |
||||
mGState.listener.onComplete(); |
||||
} else { |
||||
mGState.listener.onStop(mGState.getProgress()); |
||||
} |
||||
mQueue.clear(); |
||||
mGState.isRunning = false; |
||||
} else { |
||||
startNext(); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 如果有等待中的任务,则启动下一任务 |
||||
*/ |
||||
private void startNext() { |
||||
if (mQueue.isStopAll()) { |
||||
return; |
||||
} |
||||
SubDLoadUtil next = mQueue.getNextTask(); |
||||
if (next != null) { |
||||
ALog.d(TAG, String.format("启动任务:%s", next.getEntity().getFileName())); |
||||
mQueue.startTask(next); |
||||
} else { |
||||
ALog.i(TAG, "没有下一子任务"); |
||||
} |
||||
} |
||||
} |
@ -1,133 +0,0 @@ |
||||
/* |
||||
* 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.core.download.group; |
||||
|
||||
import android.os.Handler; |
||||
import com.arialyy.aria.core.common.CompleteInfo; |
||||
import com.arialyy.aria.core.common.IUtil; |
||||
import com.arialyy.aria.core.common.OnFileInfoCallback; |
||||
import com.arialyy.aria.core.download.DTaskWrapper; |
||||
import com.arialyy.aria.core.download.DownloadEntity; |
||||
import com.arialyy.aria.core.download.downloader.Downloader; |
||||
import com.arialyy.aria.core.download.downloader.HttpFileInfoThread; |
||||
import com.arialyy.aria.core.inf.AbsEntity; |
||||
import com.arialyy.aria.core.inf.ITaskWrapper; |
||||
import com.arialyy.aria.core.scheduler.ISchedulers; |
||||
import com.arialyy.aria.exception.BaseException; |
||||
import com.arialyy.aria.util.ALog; |
||||
|
||||
/** |
||||
* 子任务下载器,负责创建{@link Downloader} |
||||
*/ |
||||
class SubDLoadUtil implements IUtil { |
||||
private final String TAG = "SubDownloadLoader"; |
||||
|
||||
private Downloader mDownloader; |
||||
private DTaskWrapper mWrapper; |
||||
private Handler mSchedulers; |
||||
private ChildDLoadListener mListener; |
||||
private boolean needGetInfo; |
||||
|
||||
/** |
||||
* @param schedulers 调度器 |
||||
* @param needGetInfo {@code true} 需要获取文件信息。{@code false} 不需要获取文件信息 |
||||
*/ |
||||
SubDLoadUtil(Handler schedulers, DTaskWrapper taskWrapper, boolean needGetInfo) { |
||||
mWrapper = taskWrapper; |
||||
mSchedulers = schedulers; |
||||
this.needGetInfo = needGetInfo; |
||||
mListener = new ChildDLoadListener(mSchedulers, SubDLoadUtil.this); |
||||
} |
||||
|
||||
@Override public String getKey() { |
||||
return mWrapper.getKey(); |
||||
} |
||||
|
||||
public DTaskWrapper getWrapper() { |
||||
return mWrapper; |
||||
} |
||||
|
||||
public DownloadEntity getEntity() { |
||||
return mWrapper.getEntity(); |
||||
} |
||||
|
||||
/** |
||||
* 重新开始任务 |
||||
*/ |
||||
void reStart() { |
||||
if (mDownloader != null) { |
||||
mDownloader.retryTask(); |
||||
} |
||||
} |
||||
|
||||
public Downloader getDownloader() { |
||||
return mDownloader; |
||||
} |
||||
|
||||
@Override public long getFileSize() { |
||||
return mDownloader == null ? -1 : mDownloader.getFileSize(); |
||||
} |
||||
|
||||
@Override public long getCurrentLocation() { |
||||
return mDownloader == null ? -1 : mDownloader.getCurrentLocation(); |
||||
} |
||||
|
||||
@Override public boolean isRunning() { |
||||
return mDownloader != null && mDownloader.isRunning(); |
||||
} |
||||
|
||||
@Override public void cancel() { |
||||
if (mDownloader != null && isRunning()) { |
||||
mDownloader.cancel(); |
||||
} else { |
||||
mSchedulers.obtainMessage(ISchedulers.CANCEL, this).sendToTarget(); |
||||
} |
||||
} |
||||
|
||||
@Override public void stop() { |
||||
if (mDownloader != null && isRunning()) { |
||||
mDownloader.stop(); |
||||
} else { |
||||
mSchedulers.obtainMessage(ISchedulers.STOP, this).sendToTarget(); |
||||
} |
||||
} |
||||
|
||||
@Override public void start() { |
||||
if (mWrapper.getRequestType() == ITaskWrapper.D_HTTP) { |
||||
if (needGetInfo) { |
||||
new Thread(new HttpFileInfoThread(mWrapper, new OnFileInfoCallback() { |
||||
|
||||
@Override public void onComplete(String url, CompleteInfo info) { |
||||
mDownloader = new Downloader(mListener, mWrapper); |
||||
mDownloader.start(); |
||||
} |
||||
|
||||
@Override public void onFail(AbsEntity entity, BaseException e, boolean needRetry) { |
||||
mSchedulers.obtainMessage(ISchedulers.FAIL, SubDLoadUtil.this).sendToTarget(); |
||||
} |
||||
})).start(); |
||||
} else { |
||||
mDownloader = new Downloader(mListener, mWrapper); |
||||
mDownloader.start(); |
||||
} |
||||
} else if (mWrapper.getRequestType() == ITaskWrapper.D_FTP) { |
||||
mDownloader = new Downloader(mListener, mWrapper); |
||||
mDownloader.start(); |
||||
} else { |
||||
ALog.w(TAG, String.format("不识别的类型,requestType:%s", mWrapper.getRequestType())); |
||||
} |
||||
} |
||||
} |
@ -1,118 +0,0 @@ |
||||
/* |
||||
* 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.core.download.m3u8; |
||||
|
||||
import androidx.annotation.CheckResult; |
||||
import com.arialyy.aria.core.common.BaseDelegate; |
||||
import com.arialyy.aria.core.common.Suggest; |
||||
import com.arialyy.aria.core.download.DTaskWrapper; |
||||
import com.arialyy.aria.core.inf.AbsTarget; |
||||
import com.arialyy.aria.core.inf.AbsTaskWrapper; |
||||
|
||||
/** |
||||
* m3u8 委托 |
||||
*/ |
||||
public class M3U8Delegate<TARGET extends AbsTarget> extends BaseDelegate<TARGET> { |
||||
private DTaskWrapper mTaskWrapper; |
||||
|
||||
public M3U8Delegate(TARGET target, AbsTaskWrapper wrapper) { |
||||
super(target, wrapper); |
||||
mTaskWrapper = (DTaskWrapper) getTaskWrapper(); |
||||
mTaskWrapper.setRequestType(AbsTaskWrapper.M3U8_VOD); |
||||
} |
||||
|
||||
/** |
||||
* 生成m3u8索引文件 |
||||
* 注意:创建索引文件,{@link #merge(boolean)}方法设置与否都不再合并文件 |
||||
*/ |
||||
@CheckResult(suggest = Suggest.TO_CONTROLLER) |
||||
public M3U8Delegate<TARGET> generateIndexFile() { |
||||
mTaskWrapper.asM3U8().setGenerateIndexFileTemp(true); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 是否合并ts文件,默认合并ts |
||||
* |
||||
* @param merge {@code true}合并所有ts文件为一个 |
||||
*/ |
||||
@CheckResult(suggest = Suggest.TO_CONTROLLER) |
||||
public M3U8Delegate<TARGET> merge(boolean merge) { |
||||
mTaskWrapper.asM3U8().setMergeFile(merge); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 如果你希望使用自行处理ts文件的合并,可以使用{@link ITsMergeHandler}处理ts文件的合并 |
||||
* 需要注意的是:只有{@link #merge(boolean)}设置合并ts文件,该方法才会生效 |
||||
*/ |
||||
@CheckResult(suggest = Suggest.TO_CONTROLLER) |
||||
public M3U8Delegate<TARGET> setMergeHandler(ITsMergeHandler handler) { |
||||
mTaskWrapper.asM3U8().setMergeHandler(handler); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* M3U8 ts 文件url转换器,对于某些服务器,返回的ts地址可以是相对地址,也可能是处理过的 |
||||
* 对于这种情况,你需要使用url转换器将地址转换为可正常访问的http地址 |
||||
* |
||||
* @param converter {@link IVodTsUrlConverter} |
||||
*/ |
||||
@CheckResult(suggest = Suggest.TO_CONTROLLER) |
||||
public M3U8Delegate<TARGET> setTsUrlConvert(IVodTsUrlConverter converter) { |
||||
mTaskWrapper.asM3U8().setVodUrlConverter(converter); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 选择需要下载的码率,默认下载的码率 |
||||
* |
||||
* @param bandWidth 指定的码率 |
||||
*/ |
||||
@CheckResult(suggest = Suggest.TO_CONTROLLER) |
||||
public M3U8Delegate<TARGET> setBandWidth(int bandWidth) { |
||||
mTaskWrapper.asM3U8().setBandWidth(bandWidth); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* M3U8 bandWidth 码率url转换器,对于某些服务器,返回的ts地址可以是相对地址,也可能是处理过的, |
||||
* 对于这种情况,你需要使用url转换器将地址转换为可正常访问的http地址 |
||||
* |
||||
* @param converter {@link IBandWidthUrlConverter} |
||||
*/ |
||||
@CheckResult(suggest = Suggest.TO_CONTROLLER) |
||||
public M3U8Delegate<TARGET> setBandWidthUrlConverter(IBandWidthUrlConverter converter) { |
||||
mTaskWrapper.asM3U8().setBandWidthUrlConverter(converter); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 处理点播文件的下载参数 |
||||
*/ |
||||
@CheckResult(suggest = Suggest.TO_CONTROLLER) |
||||
public M3U8VodDelegate<TARGET> asVod() { |
||||
return new M3U8VodDelegate<>(mTarget, mTaskWrapper); |
||||
} |
||||
|
||||
/** |
||||
* 处理直播类的下载 |
||||
*/ |
||||
@CheckResult(suggest = Suggest.TO_CONTROLLER) |
||||
public M3U8LiveDelegate<TARGET> asLive() { |
||||
return new M3U8LiveDelegate<>(mTarget, mTaskWrapper); |
||||
} |
||||
} |
@ -1,261 +0,0 @@ |
||||
/* |
||||
* 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.core.download.m3u8; |
||||
|
||||
import android.os.Handler; |
||||
import android.os.Looper; |
||||
import android.os.Message; |
||||
import com.arialyy.aria.core.common.IThreadState; |
||||
import com.arialyy.aria.core.common.SubThreadConfig; |
||||
import com.arialyy.aria.core.common.TaskRecord; |
||||
import com.arialyy.aria.core.common.ThreadRecord; |
||||
import com.arialyy.aria.core.download.DTaskWrapper; |
||||
import com.arialyy.aria.core.inf.IEventListener; |
||||
import com.arialyy.aria.core.manager.ThreadTaskManager; |
||||
import com.arialyy.aria.util.ALog; |
||||
import com.arialyy.aria.util.FileUtil; |
||||
import com.arialyy.aria.util.IdGenerator; |
||||
import java.io.File; |
||||
import java.io.FilenameFilter; |
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
import java.util.concurrent.ArrayBlockingQueue; |
||||
import java.util.concurrent.LinkedBlockingQueue; |
||||
import java.util.concurrent.locks.Condition; |
||||
import java.util.concurrent.locks.ReentrantLock; |
||||
|
||||
/** |
||||
* M3U8点播文件下载器 |
||||
*/ |
||||
public class M3U8LiveLoader extends BaseM3U8Loader { |
||||
/** |
||||
* 最大执行数 |
||||
*/ |
||||
private static final int EXEC_MAX_NUM = 4; |
||||
private Handler mStateHandler; |
||||
private ArrayBlockingQueue<Long> mFlagQueue = new ArrayBlockingQueue<>(EXEC_MAX_NUM); |
||||
private LiveStateManager mManager; |
||||
private ReentrantLock LOCK = new ReentrantLock(); |
||||
private Condition mCondition = LOCK.newCondition(); |
||||
private LinkedBlockingQueue<String> mPeerQueue = new LinkedBlockingQueue<>(); |
||||
|
||||
M3U8LiveLoader(IEventListener listener, DTaskWrapper wrapper) { |
||||
super(listener, wrapper); |
||||
} |
||||
|
||||
@Override protected IThreadState getStateManager(Looper looper) { |
||||
mManager = new LiveStateManager(looper, mListener); |
||||
mStateHandler = new Handler(looper, mManager); |
||||
return mManager; |
||||
} |
||||
|
||||
void offerPeer(String peerUrl) { |
||||
mPeerQueue.offer(peerUrl); |
||||
} |
||||
|
||||
@Override protected void handleTask() { |
||||
|
||||
new Thread(new Runnable() { |
||||
@Override public void run() { |
||||
String cacheDir = getCacheDir(); |
||||
int index = 0; |
||||
while (!isBreak()) { |
||||
try { |
||||
LOCK.lock(); |
||||
while (mFlagQueue.size() < EXEC_MAX_NUM) { |
||||
String url = mPeerQueue.poll(); |
||||
if (url == null) { |
||||
break; |
||||
} |
||||
M3U8ThreadTask task = createThreadTask(cacheDir, index, url); |
||||
getTaskList().put(index, task); |
||||
mFlagQueue.offer(startThreadTask(task)); |
||||
index++; |
||||
} |
||||
if (mFlagQueue.size() > 0) { |
||||
mCondition.await(); |
||||
} |
||||
} catch (InterruptedException e) { |
||||
e.printStackTrace(); |
||||
} finally { |
||||
LOCK.unlock(); |
||||
} |
||||
} |
||||
} |
||||
}).start(); |
||||
} |
||||
|
||||
private void notifyLock() { |
||||
try { |
||||
LOCK.lock(); |
||||
long id = mFlagQueue.take(); |
||||
ALog.d(TAG, String.format("线程【%s】完成", id)); |
||||
mCondition.signalAll(); |
||||
} catch (InterruptedException e) { |
||||
e.printStackTrace(); |
||||
} finally { |
||||
LOCK.unlock(); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 启动线程任务 |
||||
* |
||||
* @return 线程唯一id标志 |
||||
*/ |
||||
private long startThreadTask(M3U8ThreadTask task) { |
||||
ThreadTaskManager.getInstance().startThread(mTaskWrapper.getKey(), task); |
||||
return IdGenerator.getInstance().nextId(); |
||||
} |
||||
|
||||
/** |
||||
* 配置config |
||||
*/ |
||||
private M3U8ThreadTask createThreadTask(String cacheDir, int indexId, String tsUrl) { |
||||
ThreadRecord record = new ThreadRecord(); |
||||
record.taskKey = mRecord.filePath; |
||||
record.isComplete = false; |
||||
record.tsUrl = tsUrl; |
||||
record.threadType = TaskRecord.TYPE_M3U8_LIVE; |
||||
record.threadId = indexId; |
||||
|
||||
SubThreadConfig<DTaskWrapper> config = new SubThreadConfig<>(); |
||||
config.url = tsUrl; |
||||
config.tempFile = new File(getTsFilePath(cacheDir, indexId)); |
||||
config.isBlock = mRecord.isBlock; |
||||
config.isOpenDynamicFile = mRecord.isOpenDynamicFile; |
||||
config.taskWrapper = mTaskWrapper; |
||||
config.record = record; |
||||
config.stateHandler = mStateHandler; |
||||
|
||||
if (!config.tempFile.exists()) { |
||||
FileUtil.createFile(config.tempFile.getPath()); |
||||
} |
||||
return new M3U8ThreadTask(config); |
||||
} |
||||
|
||||
/** |
||||
* 合并文件 |
||||
* |
||||
* @return {@code true} 合并成功,{@code false}合并失败 |
||||
*/ |
||||
public boolean mergeFile() { |
||||
if (getEntity().getM3U8Entity().isGenerateIndexFile()) { |
||||
return generateIndexFile(); |
||||
} |
||||
ITsMergeHandler mergeHandler = mTaskWrapper.asM3U8().getMergeHandler(); |
||||
String cacheDir = getCacheDir(); |
||||
List<String> partPath = new ArrayList<>(); |
||||
String[] tsNames = new File(cacheDir).list(new FilenameFilter() { |
||||
@Override public boolean accept(File dir, String name) { |
||||
return name.endsWith(".ts"); |
||||
} |
||||
}); |
||||
for (String tsName : tsNames) { |
||||
partPath.add(cacheDir + "/" + tsName); |
||||
} |
||||
|
||||
boolean isSuccess; |
||||
if (mergeHandler != null) { |
||||
isSuccess = mergeHandler.merge(getEntity().getM3U8Entity(), partPath); |
||||
} else { |
||||
isSuccess = FileUtil.mergeFile(mEntity.getFilePath(), partPath); |
||||
} |
||||
if (isSuccess) { |
||||
// 合并成功,删除缓存文件
|
||||
for (String pp : partPath) { |
||||
File f = new File(pp); |
||||
if (f.exists()) { |
||||
f.delete(); |
||||
} |
||||
} |
||||
File cDir = new File(cacheDir); |
||||
if (cDir.exists()) { |
||||
cDir.delete(); |
||||
} |
||||
return true; |
||||
} else { |
||||
ALog.e(TAG, "合并失败"); |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* M3U8线程状态管理,直播不处理停止状态、删除、失败的状态 |
||||
*/ |
||||
private class LiveStateManager implements IThreadState { |
||||
private final String TAG = "M3U8ThreadStateManager"; |
||||
|
||||
/** |
||||
* 任务状态回调 |
||||
*/ |
||||
private IEventListener mListener; |
||||
private long mProgress; //当前总进度
|
||||
private Looper mLooper; |
||||
|
||||
/** |
||||
* @param listener 任务事件 |
||||
*/ |
||||
LiveStateManager(Looper looper, IEventListener listener) { |
||||
mLooper = looper; |
||||
mListener = listener; |
||||
} |
||||
|
||||
/** |
||||
* 退出looper循环 |
||||
*/ |
||||
private void quitLooper() { |
||||
ALog.d(TAG, "quitLooper"); |
||||
mLooper.quit(); |
||||
} |
||||
|
||||
@Override public boolean handleMessage(Message msg) { |
||||
switch (msg.what) { |
||||
case STATE_STOP: |
||||
if (isBreak()) { |
||||
ALog.d(TAG, "任务停止"); |
||||
quitLooper(); |
||||
} |
||||
break; |
||||
case STATE_CANCEL: |
||||
if (isBreak()) { |
||||
ALog.d(TAG, "任务取消"); |
||||
quitLooper(); |
||||
} |
||||
break; |
||||
case STATE_COMPLETE: |
||||
notifyLock(); |
||||
break; |
||||
case STATE_RUNNING: |
||||
mProgress += (long) msg.obj; |
||||
break; |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
@Override public boolean isFail() { |
||||
return false; |
||||
} |
||||
|
||||
@Override public boolean isComplete() { |
||||
return false; |
||||
} |
||||
|
||||
@Override public long getCurrentProgress() { |
||||
return mProgress; |
||||
} |
||||
} |
||||
} |
@ -1,180 +0,0 @@ |
||||
/* |
||||
* 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.core.download.m3u8; |
||||
|
||||
import android.text.TextUtils; |
||||
import com.arialyy.aria.core.common.CompleteInfo; |
||||
import com.arialyy.aria.core.common.IUtil; |
||||
import com.arialyy.aria.core.common.OnFileInfoCallback; |
||||
import com.arialyy.aria.core.download.DTaskWrapper; |
||||
import com.arialyy.aria.core.inf.AbsEntity; |
||||
import com.arialyy.aria.core.inf.IDownloadListener; |
||||
import com.arialyy.aria.exception.BaseException; |
||||
import com.arialyy.aria.exception.M3U8Exception; |
||||
import com.arialyy.aria.util.ALog; |
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
import java.util.concurrent.ExecutorService; |
||||
import java.util.concurrent.Executors; |
||||
import java.util.concurrent.ScheduledThreadPoolExecutor; |
||||
import java.util.concurrent.TimeUnit; |
||||
|
||||
/** |
||||
* M3U8直播文件下载工具,对于直播来说,需要定时更新m3u8文件 |
||||
* 工作流程: |
||||
* 1、持续获取切片信息,直到调用停止|取消才停止获取切片信息 |
||||
* 2、完成所有分片下载后,合并ts文件 |
||||
* 3、删除该隐藏文件夹 |
||||
* 4、对于直播来说是没有停止的,停止就代表完成 |
||||
* 5、不处理直播切片下载失败的状态 |
||||
*/ |
||||
public class M3U8LiveUtil implements IUtil { |
||||
private final String TAG = "M3U8LiveDownloadUtil"; |
||||
private DTaskWrapper mWrapper; |
||||
private IDownloadListener mListener; |
||||
private boolean isStop = false, isCancel = false; |
||||
private M3U8LiveLoader mLoader; |
||||
private M3U8InfoThread mInfoThread; |
||||
private ScheduledThreadPoolExecutor mTimer; |
||||
private ExecutorService mInfoPool = Executors.newCachedThreadPool(); |
||||
private List<String> mPeerUrls = new ArrayList<>(); |
||||
|
||||
public M3U8LiveUtil(DTaskWrapper wrapper, IDownloadListener listener) { |
||||
mWrapper = wrapper; |
||||
mListener = listener; |
||||
mLoader = new M3U8LiveLoader(mListener, mWrapper); |
||||
} |
||||
|
||||
@Override public String getKey() { |
||||
return mWrapper.getKey(); |
||||
} |
||||
|
||||
@Override public long getFileSize() { |
||||
return 0; |
||||
} |
||||
|
||||
@Override public long getCurrentLocation() { |
||||
return 0; |
||||
} |
||||
|
||||
@Override public boolean isRunning() { |
||||
return mLoader.isRunning(); |
||||
} |
||||
|
||||
@Override public void cancel() { |
||||
isCancel = true; |
||||
mLoader.cancel(); |
||||
if (mInfoThread != null) { |
||||
mInfoThread.setStop(true); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 对于直播来说是没有停止的,停止就代表完成 |
||||
*/ |
||||
@Override public void stop() { |
||||
isStop = true; |
||||
mLoader.stop(); |
||||
handleComplete(); |
||||
} |
||||
|
||||
private void handleComplete() { |
||||
if (mInfoThread != null) { |
||||
mInfoThread.setStop(true); |
||||
closeTimer(); |
||||
if (mWrapper.asM3U8().isMergeFile()) { |
||||
if (mLoader.mergeFile()) { |
||||
mListener.onComplete(); |
||||
} else { |
||||
mListener.onFail(false, new M3U8Exception(TAG, "合并文件失败")); |
||||
} |
||||
} else { |
||||
mListener.onComplete(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Override public void start() { |
||||
if (isStop || isCancel) { |
||||
return; |
||||
} |
||||
mListener.onPre(); |
||||
getLiveInfo(); |
||||
mLoader.start(); |
||||
startTimer(); |
||||
} |
||||
|
||||
private void startTimer() { |
||||
mTimer = new ScheduledThreadPoolExecutor(1); |
||||
mTimer.scheduleWithFixedDelay(new Runnable() { |
||||
@Override public void run() { |
||||
mInfoThread = (M3U8InfoThread) getLiveInfo(); |
||||
mInfoPool.execute(mInfoThread); |
||||
} |
||||
}, 0, mWrapper.asM3U8().getLiveUpdateInterval(), TimeUnit.MILLISECONDS); |
||||
} |
||||
|
||||
private void closeTimer() { |
||||
if (mTimer != null && !mTimer.isShutdown()) { |
||||
mTimer.shutdown(); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 获取直播文件信息 |
||||
*/ |
||||
private Runnable getLiveInfo() { |
||||
M3U8InfoThread infoThread = new M3U8InfoThread(mWrapper, new OnFileInfoCallback() { |
||||
@Override public void onComplete(String key, CompleteInfo info) { |
||||
ALog.d(TAG, "更新直播的m3u8文件"); |
||||
} |
||||
|
||||
@Override public void onFail(AbsEntity entity, BaseException e, boolean needRetry) { |
||||
failDownload(e, needRetry); |
||||
} |
||||
}); |
||||
infoThread.setOnGetPeerCallback(new M3U8InfoThread.OnGetLivePeerCallback() { |
||||
@Override public void onGetPeer(String url) { |
||||
if (mPeerUrls.contains(url)) { |
||||
return; |
||||
} |
||||
mPeerUrls.add(url); |
||||
ILiveTsUrlConverter converter = mWrapper.asM3U8().getLiveTsUrlConverter(); |
||||
if (converter != null) { |
||||
if (TextUtils.isEmpty(mWrapper.asM3U8().getBandWidthUrl())) { |
||||
url = converter.convert(mWrapper.getEntity().getUrl(), url); |
||||
} else { |
||||
url = converter.convert(mWrapper.asM3U8().getBandWidthUrl(), url); |
||||
} |
||||
} |
||||
if (TextUtils.isEmpty(url) || !url.startsWith("http")) { |
||||
failDownload(new M3U8Exception(TAG, String.format("ts地址错误,url:%s", url)), false); |
||||
return; |
||||
} |
||||
mLoader.offerPeer(url); |
||||
} |
||||
}); |
||||
return infoThread; |
||||
} |
||||
|
||||
private void failDownload(BaseException e, boolean needRetry) { |
||||
if (isStop || isCancel) { |
||||
return; |
||||
} |
||||
handleComplete(); |
||||
mListener.onFail(needRetry, e); |
||||
} |
||||
} |
@ -0,0 +1,141 @@ |
||||
/* |
||||
* 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.core.download.m3u8; |
||||
|
||||
import android.text.TextUtils; |
||||
import com.arialyy.aria.core.common.BaseOption; |
||||
import com.arialyy.aria.core.processor.IBandWidthUrlConverter; |
||||
import com.arialyy.aria.core.processor.IKeyUrlConverter; |
||||
import com.arialyy.aria.core.processor.ITsMergeHandler; |
||||
import com.arialyy.aria.util.ALog; |
||||
import com.arialyy.aria.util.CheckUtil; |
||||
import com.arialyy.aria.util.ComponentUtil; |
||||
|
||||
/** |
||||
* m3u8任务设置 |
||||
*/ |
||||
public class M3U8Option<OP extends M3U8Option> extends BaseOption { |
||||
|
||||
private boolean generateIndexFile = false; |
||||
private boolean mergeFile = true; |
||||
private int bandWidth; |
||||
private ITsMergeHandler mergeHandler; |
||||
private IBandWidthUrlConverter bandWidthUrlConverter; |
||||
private IKeyUrlConverter keyUrlConverter; |
||||
private boolean ignoreFailureTs = false; |
||||
private String keyPath; |
||||
private boolean useDefConvert = true; |
||||
|
||||
M3U8Option() { |
||||
super(); |
||||
ComponentUtil.getInstance().checkComponentExist(ComponentUtil.COMPONENT_TYPE_M3U8); |
||||
} |
||||
|
||||
/** |
||||
* 设置使用默认的码率转换器和TS转换器,默认打开 |
||||
* @param useDefConvert true 使用默认的转换器,false 关闭默认的转换器 |
||||
*/ |
||||
public OP setUseDefConvert(boolean useDefConvert) { |
||||
this.useDefConvert = useDefConvert; |
||||
ALog.d(TAG, "使用默认的码率转换器和TS转换器,如果无法下载,请参考:https://github.com/AriaLyy/Aria/issues/597 定制转换器"); |
||||
return (OP) this; |
||||
} |
||||
|
||||
/** |
||||
* 设置密钥文件的保存路径 |
||||
* |
||||
* @param keyPath 密钥文件的保存路径 |
||||
*/ |
||||
public OP setKeyPath(String keyPath) { |
||||
if (TextUtils.isEmpty(keyPath)) { |
||||
ALog.e(TAG, "密钥文件保存路径为空"); |
||||
return (OP) this; |
||||
} |
||||
this.keyPath = keyPath; |
||||
return (OP) this; |
||||
} |
||||
|
||||
/** |
||||
* 忽略下载失败的ts切片,即使有失败的切片,下载完成后也要合并所有切片,并进入complete回调 |
||||
*/ |
||||
public OP ignoreFailureTs() { |
||||
this.ignoreFailureTs = true; |
||||
return (OP) this; |
||||
} |
||||
|
||||
/** |
||||
* 生成m3u8索引文件 |
||||
* 注意:创建索引文件,{@link #merge(boolean)}方法设置与否都不再合并文件 |
||||
* 如果是直播文件下载,创建索引文件的操作将导致只能同时下载一个切片!! |
||||
*/ |
||||
public OP generateIndexFile() { |
||||
this.generateIndexFile = true; |
||||
return (OP) this; |
||||
} |
||||
|
||||
/** |
||||
* 下载完成后,将所有的切片合并为一个文件 |
||||
* |
||||
* @param mergeFile {@code true}合并所有ts文件为一个 |
||||
*/ |
||||
public OP merge(boolean mergeFile) { |
||||
this.mergeFile = mergeFile; |
||||
return (OP) this; |
||||
} |
||||
|
||||
/** |
||||
* 如果你希望使用自行处理ts文件的合并,可以使用{@link ITsMergeHandler}处理ts文件的合并 |
||||
* 需要注意的是:只有{@link #merge(boolean)}设置合并ts文件,该方法才会生效 |
||||
*/ |
||||
public OP setMergeHandler(ITsMergeHandler mergeHandler) { |
||||
CheckUtil.checkMemberClass(mergeHandler.getClass()); |
||||
this.mergeHandler = mergeHandler; |
||||
return (OP) this; |
||||
} |
||||
|
||||
/** |
||||
* 选择需要下载的码率,默认下载的码率 |
||||
* |
||||
* @param bandWidth 指定的码率 |
||||
*/ |
||||
public OP setBandWidth(int bandWidth) { |
||||
this.bandWidth = bandWidth; |
||||
return (OP) this; |
||||
} |
||||
|
||||
/** |
||||
* M3U8 bandWidth 码率url转换器,对于某些服务器,返回的ts地址可以是相对地址,也可能是处理过的, |
||||
* 对于这种情况,你需要使用url转换器将地址转换为可正常访问的http地址 |
||||
* |
||||
* @param bandWidthUrlConverter {@link IBandWidthUrlConverter} |
||||
*/ |
||||
public OP setBandWidthUrlConverter(IBandWidthUrlConverter bandWidthUrlConverter) { |
||||
CheckUtil.checkMemberClass(bandWidthUrlConverter.getClass()); |
||||
this.bandWidthUrlConverter = bandWidthUrlConverter; |
||||
return (OP) this; |
||||
} |
||||
|
||||
/** |
||||
* M3U8 密钥url转换器,对于某些服务器,密钥的下载地址是被加密的,因此需要使用该方法将被加密的密钥解密成可被识别的http地址 |
||||
* |
||||
* @param keyUrlConverter {@link IKeyUrlConverter} |
||||
*/ |
||||
public OP setKeyUrlConverter(IKeyUrlConverter keyUrlConverter) { |
||||
CheckUtil.checkMemberClass(keyUrlConverter.getClass()); |
||||
this.keyUrlConverter = keyUrlConverter; |
||||
return (OP) this; |
||||
} |
||||
} |
@ -1,115 +0,0 @@ |
||||
/* |
||||
* 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.core.download.m3u8; |
||||
|
||||
import androidx.annotation.CheckResult; |
||||
import com.arialyy.aria.core.common.BaseDelegate; |
||||
import com.arialyy.aria.core.common.Suggest; |
||||
import com.arialyy.aria.core.download.DTaskWrapper; |
||||
import com.arialyy.aria.core.event.EventMsgUtil; |
||||
import com.arialyy.aria.core.event.PeerIndexEvent; |
||||
import com.arialyy.aria.core.inf.AbsTarget; |
||||
import com.arialyy.aria.core.inf.AbsTaskWrapper; |
||||
import com.arialyy.aria.core.queue.DownloadTaskQueue; |
||||
import com.arialyy.aria.util.ALog; |
||||
|
||||
/** |
||||
* m3u8点播文件参数设置 |
||||
*/ |
||||
public class M3U8VodDelegate<TARGET extends AbsTarget> extends BaseDelegate<TARGET> { |
||||
private DTaskWrapper mTaskWrapper; |
||||
|
||||
M3U8VodDelegate(TARGET target, AbsTaskWrapper wrapper) { |
||||
super(target, wrapper); |
||||
mTaskWrapper = (DTaskWrapper) getTaskWrapper(); |
||||
mTaskWrapper.setRequestType(AbsTaskWrapper.M3U8_VOD); |
||||
} |
||||
|
||||
/** |
||||
* 由于m3u8协议的特殊性质,无法有效快速获取到正确到文件长度,如果你需要显示文件中长度,你需要自行设置文件长度 |
||||
* |
||||
* @param fileSize 文件长度 |
||||
*/ |
||||
@CheckResult(suggest = Suggest.TO_CONTROLLER) |
||||
public M3U8VodDelegate<TARGET> setFileSize(long fileSize) { |
||||
if (fileSize <= 0) { |
||||
ALog.e(TAG, "文件长度错误"); |
||||
return this; |
||||
} |
||||
mTaskWrapper.getEntity().setFileSize(fileSize); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 默认情况下,对于同一点播文件的下载,最多同时下载4个ts分片,如果你希望增加或减少同时下载的ts分片数量,可以使用该方法设置同时下载的ts分片数量 |
||||
* |
||||
* @param num 同时下载的ts分片数量 |
||||
*/ |
||||
@CheckResult(suggest = Suggest.TO_CONTROLLER) |
||||
public M3U8VodDelegate<TARGET> setMaxTsQueueNum(int num) { |
||||
if (num < 1) { |
||||
ALog.e(TAG, "同时下载的分片数量不能小于1"); |
||||
return this; |
||||
} |
||||
mTaskWrapper.asM3U8().setMaxTsQueueNum(num); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 启动任务时初始化索引位置 |
||||
* |
||||
* 优先下载指定索引后的切片 |
||||
* 如果指定的切片索引大于切片总数,则此操作无效 |
||||
* 如果指定的切片索引小于当前正在下载的切片索引,并且指定索引和当前索引区间内有未下载的切片,则优先下载该区间的切片;否则此操作无效 |
||||
* 如果指定索引后的切片已经全部下载完成,但是索引前有未下载的切片,间会自动下载未下载的切片 |
||||
* |
||||
* @param index 指定的切片位置 |
||||
*/ |
||||
@CheckResult(suggest = Suggest.TO_CONTROLLER) |
||||
public M3U8VodDelegate<TARGET> setPeerIndex(int index) { |
||||
if (index < 1) { |
||||
ALog.e(TAG, "切片索引不能小于1"); |
||||
return this; |
||||
} |
||||
mTaskWrapper.asM3U8().setJumpIndex(index); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 任务执行中,跳转索引位置 |
||||
* 优先下载指定索引后的切片 |
||||
* 如果指定的切片索引大于切片总数,则此操作无效 |
||||
* 如果指定的切片索引小于当前正在下载的切片索引,并且指定索引和当前索引区间内有未下载的切片,则优先下载该区间的切片;否则此操作无效 |
||||
* 如果指定索引后的切片已经全部下载完成,但是索引前有未下载的切片,间会自动下载未下载的切片 |
||||
* |
||||
* @param index 指定的切片位置 |
||||
*/ |
||||
public void jumPeerIndex(int index) { |
||||
if (index < 1) { |
||||
ALog.e(TAG, "切片索引不能小于1"); |
||||
return; |
||||
} |
||||
|
||||
if (!DownloadTaskQueue.getInstance().taskIsRunning(mTaskWrapper.getKey())) { |
||||
ALog.e(TAG, |
||||
String.format("任务【%s】没有运行,如果你希望在启动任务时初始化索引位置,请调用setPeerIndex(xxx)", |
||||
mTaskWrapper.getKey())); |
||||
return; |
||||
} |
||||
|
||||
EventMsgUtil.getDefault().post(new PeerIndexEvent(mTaskWrapper.getKey(), index)); |
||||
} |
||||
} |
@ -0,0 +1,102 @@ |
||||
/* |
||||
* 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.core.download.m3u8; |
||||
|
||||
import com.arialyy.aria.core.processor.IVodTsUrlConverter; |
||||
import com.arialyy.aria.util.ALog; |
||||
import com.arialyy.aria.util.CheckUtil; |
||||
|
||||
/** |
||||
* m3u8点播文件参数设置 |
||||
*/ |
||||
public class M3U8VodOption extends M3U8Option<M3U8VodOption> { |
||||
private long fileSize; |
||||
private int maxTsQueueNum; |
||||
private int jumpIndex; |
||||
private IVodTsUrlConverter vodUrlConverter; |
||||
|
||||
public M3U8VodOption() { |
||||
super(); |
||||
} |
||||
|
||||
/** |
||||
* M3U8 ts 文件url转换器,对于某些服务器,返回的ts地址可以是相对地址,也可能是处理过的 |
||||
* 对于这种情况,你需要使用url转换器将地址转换为可正常访问的http地址 |
||||
* |
||||
* @param vodUrlConverter {@link IVodTsUrlConverter} |
||||
*/ |
||||
public M3U8VodOption setVodTsUrlConvert(IVodTsUrlConverter vodUrlConverter) { |
||||
CheckUtil.checkMemberClass(vodUrlConverter.getClass()); |
||||
this.vodUrlConverter = vodUrlConverter; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 由于m3u8协议的特殊性质,无法有效快速获取到正确到文件长度,如果你需要显示文件中长度,你需要自行设置文件长度 |
||||
* |
||||
* @param fileSize 文件长度 |
||||
*/ |
||||
public M3U8VodOption setFileSize(long fileSize) { |
||||
if (fileSize <= 0) { |
||||
ALog.e(TAG, "文件长度错误"); |
||||
return this; |
||||
} |
||||
this.fileSize = fileSize; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 默认情况下,对于同一点播文件的下载,最多同时下载4个ts分片,如果你希望增加或减少同时下载的ts分片数量,可以使用该方法设置同时下载的ts分片数量 |
||||
* |
||||
* @param maxTsQueueNum 同时下载的ts分片数量 |
||||
*/ |
||||
public M3U8VodOption setMaxTsQueueNum(int maxTsQueueNum) { |
||||
if (maxTsQueueNum < 1) { |
||||
ALog.e(TAG, "同时下载的分片数量不能小于1"); |
||||
return this; |
||||
} |
||||
|
||||
this.maxTsQueueNum = maxTsQueueNum; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 启动任务时初始化索引位置 |
||||
* |
||||
* 优先下载指定索引后的切片 |
||||
* 如果指定的切片索引大于切片总数,则此操作无效 |
||||
* 如果指定的切片索引小于当前正在下载的切片索引,并且指定索引和当前索引区间内有未下载的切片,则优先下载该区间的切片;否则此操作无效 |
||||
* 如果指定索引后的切片已经全部下载完成,但是索引前有未下载的切片,间会自动下载未下载的切片 |
||||
* |
||||
* @param jumpIndex 指定的切片位置 |
||||
*/ |
||||
public M3U8VodOption setPeerIndex(int jumpIndex) { |
||||
if (jumpIndex < 1) { |
||||
ALog.e(TAG, "切片索引不能小于1"); |
||||
return this; |
||||
} |
||||
this.jumpIndex = jumpIndex; |
||||
return this; |
||||
} |
||||
|
||||
public long getFileSize() { |
||||
return fileSize; |
||||
} |
||||
|
||||
public int getJumpIndex() { |
||||
return jumpIndex; |
||||
} |
||||
} |
@ -1,145 +0,0 @@ |
||||
/* |
||||
* 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.core.download.m3u8; |
||||
|
||||
import android.text.TextUtils; |
||||
import com.arialyy.aria.core.common.CompleteInfo; |
||||
import com.arialyy.aria.core.common.IUtil; |
||||
import com.arialyy.aria.core.common.OnFileInfoCallback; |
||||
import com.arialyy.aria.core.download.DTaskWrapper; |
||||
import com.arialyy.aria.core.download.M3U8Listener; |
||||
import com.arialyy.aria.core.inf.AbsEntity; |
||||
import com.arialyy.aria.exception.BaseException; |
||||
import com.arialyy.aria.exception.M3U8Exception; |
||||
import java.io.File; |
||||
import java.util.ArrayList; |
||||
import java.util.Collection; |
||||
import java.util.List; |
||||
|
||||
/** |
||||
* M3U8点播文件下载工具 |
||||
* 工作流程: |
||||
* 1、创建一个和文件同父路径并且同名隐藏文件夹 |
||||
* 2、将所有m3u8的ts文件下载到该文件夹中 |
||||
* 3、完成所有分片下载后,合并ts文件 |
||||
* 4、删除该隐藏文件夹 |
||||
*/ |
||||
public class M3U8VodUtil implements IUtil { |
||||
private final String TAG = "M3U8DownloadUtil"; |
||||
|
||||
private DTaskWrapper mWrapper; |
||||
private M3U8Listener mListener; |
||||
private boolean isStop = false, isCancel = false; |
||||
private List<String> mUrls = new ArrayList<>(); |
||||
private M3U8VodLoader mLoader; |
||||
|
||||
public M3U8VodUtil(DTaskWrapper wrapper, M3U8Listener listener) { |
||||
mWrapper = wrapper; |
||||
mListener = listener; |
||||
mLoader = new M3U8VodLoader(mListener, mWrapper); |
||||
} |
||||
|
||||
@Override public String getKey() { |
||||
return mWrapper.getKey(); |
||||
} |
||||
|
||||
@Override public long getFileSize() { |
||||
return 0; |
||||
} |
||||
|
||||
@Override public long getCurrentLocation() { |
||||
return 0; |
||||
} |
||||
|
||||
@Override public boolean isRunning() { |
||||
return mLoader.isRunning(); |
||||
} |
||||
|
||||
@Override public void cancel() { |
||||
isCancel = true; |
||||
mLoader.cancel(); |
||||
} |
||||
|
||||
@Override public void stop() { |
||||
isStop = true; |
||||
mLoader.stop(); |
||||
} |
||||
|
||||
@Override public void start() { |
||||
if (isStop || isCancel) { |
||||
return; |
||||
} |
||||
mListener.onPre(); |
||||
// peer数量小于0,
|
||||
M3U8Entity m3U8Entity = mWrapper.getEntity().getM3U8Entity(); |
||||
if (m3U8Entity.getPeerNum() <= 0 || (m3U8Entity.isGenerateIndexFile() && !new File( |
||||
String.format(M3U8InfoThread.M3U8_INDEX_FORMAT, |
||||
mWrapper.getEntity().getFilePath())).exists())) { |
||||
getVodInfo(); |
||||
} else { |
||||
mLoader.start(); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 获取点播文件信息 |
||||
*/ |
||||
private void getVodInfo() { |
||||
M3U8InfoThread thread = new M3U8InfoThread(mWrapper, new OnFileInfoCallback() { |
||||
@Override public void onComplete(String key, CompleteInfo info) { |
||||
IVodTsUrlConverter converter = mWrapper.asM3U8().getVodUrlConverter(); |
||||
if (converter != null) { |
||||
if (TextUtils.isEmpty(mWrapper.asM3U8().getBandWidthUrl())) { |
||||
mUrls.addAll(converter.convert(mWrapper.getEntity().getUrl(), (List<String>) info.obj)); |
||||
} else { |
||||
mUrls.addAll( |
||||
converter.convert(mWrapper.asM3U8().getBandWidthUrl(), (List<String>) info.obj)); |
||||
} |
||||
} else { |
||||
mUrls.addAll((Collection<? extends String>) info.obj); |
||||
} |
||||
if (mUrls.isEmpty()) { |
||||
failDownload(new M3U8Exception(TAG, "获取地址失败"), false); |
||||
return; |
||||
} else if (!mUrls.get(0).startsWith("http")) { |
||||
failDownload(new M3U8Exception(TAG, "地址错误,请使用IM3U8UrlExtInfHandler处理你的url信息"), false); |
||||
return; |
||||
} |
||||
mWrapper.asM3U8().setUrls(mUrls); |
||||
if (isStop) { |
||||
mListener.onStop(mWrapper.getEntity().getCurrentProgress()); |
||||
} else if (isCancel) { |
||||
mListener.onCancel(); |
||||
} else { |
||||
mLoader.start(); |
||||
} |
||||
} |
||||
|
||||
@Override public void onFail(AbsEntity entity, BaseException e, boolean needRetry) { |
||||
failDownload(e, needRetry); |
||||
} |
||||
}); |
||||
new Thread(thread).start(); |
||||
} |
||||
|
||||
private void failDownload(BaseException e, boolean needRetry) { |
||||
if (isStop || isCancel) { |
||||
return; |
||||
} |
||||
mListener.onFail(needRetry, e); |
||||
mLoader.onDestroy(); |
||||
} |
||||
} |
@ -0,0 +1,59 @@ |
||||
|
||||
package com.arialyy.aria.core.download.target; |
||||
/* |
||||
* 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. |
||||
*/ |
||||
|
||||
import com.arialyy.aria.core.common.AbsNormalTarget; |
||||
import com.arialyy.aria.core.download.DTaskWrapper; |
||||
import com.arialyy.aria.core.event.EventMsgUtil; |
||||
import com.arialyy.aria.core.event.PeerIndexEvent; |
||||
import com.arialyy.aria.core.queue.DTaskQueue; |
||||
import com.arialyy.aria.core.wrapper.ITaskWrapper; |
||||
import com.arialyy.aria.util.ALog; |
||||
|
||||
public class M3U8NormalTarget extends AbsNormalTarget<M3U8NormalTarget> { |
||||
|
||||
M3U8NormalTarget(DTaskWrapper wrapper) { |
||||
setTaskWrapper(wrapper); |
||||
getTaskWrapper().setNewTask(false); |
||||
getTaskWrapper().setRequestType(ITaskWrapper.M3U8_VOD); |
||||
} |
||||
|
||||
/** |
||||
* 任务执行中,跳转索引位置 |
||||
* 优先下载指定索引后的切片 |
||||
* 如果指定的切片索引大于切片总数,则此操作无效 |
||||
* 如果指定的切片索引小于当前正在下载的切片索引,并且指定索引和当前索引区间内有未下载的切片,则优先下载该区间的切片;否则此操作无效 |
||||
* 如果指定索引后的切片已经全部下载完成,但是索引前有未下载的切片,间会自动下载未下载的切片 |
||||
* |
||||
* @param index 指定的切片位置 |
||||
*/ |
||||
public void jumPeerIndex(int index) { |
||||
if (index < 1) { |
||||
ALog.e(TAG, "切片索引不能小于1"); |
||||
return; |
||||
} |
||||
|
||||
if (!DTaskQueue.getInstance().taskIsRunning(getTaskWrapper().getKey())) { |
||||
ALog.e(TAG, |
||||
String.format("任务【%s】没有运行,如果你希望在启动任务时初始化索引位置,请调用setPeerIndex(xxx)", |
||||
getTaskWrapper().getKey())); |
||||
return; |
||||
} |
||||
|
||||
EventMsgUtil.getDefault().post(new PeerIndexEvent(getTaskWrapper().getKey(), index)); |
||||
} |
||||
} |
@ -0,0 +1,56 @@ |
||||
/* |
||||
* 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.core.download.target; |
||||
|
||||
import com.arialyy.aria.core.common.AbsBuilderTarget; |
||||
import com.arialyy.aria.core.download.DownloadEntity; |
||||
import com.arialyy.aria.core.wrapper.ITaskWrapper; |
||||
|
||||
/** |
||||
* @author aria |
||||
* Date: 2019-09-06 |
||||
*/ |
||||
public class TcpBuilderTarget extends AbsBuilderTarget<TcpBuilderTarget> { |
||||
|
||||
private DNormalConfigHandler mConfigHandler; |
||||
|
||||
TcpBuilderTarget(String ip, int port) { |
||||
mConfigHandler = new DNormalConfigHandler<>(this, -1); |
||||
getTaskWrapper().setRequestType(ITaskWrapper.D_TCP); |
||||
((DownloadEntity) getEntity()).setTaskType(ITaskWrapper.D_TCP); |
||||
getTaskWrapper().setNewTask(true); |
||||
} |
||||
//
|
||||
///**
|
||||
// * 设置tcp相应信息
|
||||
// */
|
||||
//@CheckResult(suggest = Suggest.TASK_CONTROLLER)
|
||||
//public TcpBuilderTarget option(T) {
|
||||
// return new TcpDelegate<>(this, getTaskWrapper());
|
||||
//}
|
||||
|
||||
/** |
||||
* 设置文件存储路径,如果需要修改新的文件名,修改路径便可。 |
||||
* 如:原文件路径 /mnt/sdcard/test.zip |
||||
* 如果需要将test.zip改为game.zip,只需要重新设置文件路径为:/mnt/sdcard/game.zip |
||||
* |
||||
* @param filePath 路径必须为文件路径,不能为文件夹路径 |
||||
*/ |
||||
public TcpBuilderTarget setFilePath(String filePath) { |
||||
mConfigHandler.setTempFilePath(filePath); |
||||
return this; |
||||
} |
||||
} |
@ -0,0 +1,90 @@ |
||||
/* |
||||
* 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.core.download.tcp; |
||||
|
||||
import android.text.TextUtils; |
||||
import com.arialyy.aria.core.common.BaseOption; |
||||
import com.arialyy.aria.util.ALog; |
||||
import java.nio.charset.Charset; |
||||
|
||||
/** |
||||
* @author aria |
||||
* Date: 2019-09-06 |
||||
*/ |
||||
public class TcpDelegate extends BaseOption { |
||||
|
||||
private String params; |
||||
private String heartbeatInfo; |
||||
private long heartbeat; |
||||
private String charset; |
||||
|
||||
public TcpDelegate() { |
||||
super(); |
||||
} |
||||
|
||||
/** |
||||
* 上传给tcp服务的初始数据,一般是文件名、文件路径等信息 |
||||
*/ |
||||
public TcpDelegate setParam(String params) { |
||||
if (TextUtils.isEmpty(params)) { |
||||
ALog.w(TAG, "tcp传输的数据不能为空"); |
||||
return this; |
||||
} |
||||
this.params = params; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 设置心跳包传输的数据 |
||||
* |
||||
* @param heartbeatInfo 心跳包数据 |
||||
*/ |
||||
public TcpDelegate setHeartbeatInfo(String heartbeatInfo) { |
||||
if (TextUtils.isEmpty(heartbeatInfo)) { |
||||
ALog.w(TAG, "心跳包传输的数据不能为空"); |
||||
return this; |
||||
} |
||||
this.heartbeatInfo = heartbeatInfo; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 心跳间隔,默认1s |
||||
* |
||||
* @param heartbeat 单位毫秒 |
||||
*/ |
||||
public TcpDelegate setHeartbeatInterval(long heartbeat) { |
||||
if (heartbeat <= 0) { |
||||
ALog.w(TAG, "心跳间隔不能小于1毫秒"); |
||||
return this; |
||||
} |
||||
this.heartbeat = heartbeat; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 数据传输编码,默认"utf-8" |
||||
*/ |
||||
public TcpDelegate setCharset(String charset) { |
||||
if (!Charset.isSupported(charset)) { |
||||
ALog.w(TAG, "不支持的编码"); |
||||
return this; |
||||
} |
||||
|
||||
this.charset = charset; |
||||
return this; |
||||
} |
||||
} |
@ -0,0 +1,77 @@ |
||||
/* |
||||
* 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.core.download.tcp; |
||||
|
||||
/** |
||||
* tcp任务配置 |
||||
* |
||||
* @author aria |
||||
* Date: 2019-09-06 |
||||
*/ |
||||
public class TcpTaskConfig { |
||||
|
||||
/** |
||||
* 上传给tcp服务的初始数据,一般是文件名等信息 |
||||
*/ |
||||
private String params; |
||||
|
||||
/** |
||||
* 心跳包传输的数据 |
||||
*/ |
||||
private String heartbeat; |
||||
|
||||
/** |
||||
* 心跳间隔,单位毫秒,默认1s |
||||
*/ |
||||
private long heartbeatInterval = 1000; |
||||
|
||||
/** |
||||
* 数据传输编码,默认"utf-8" |
||||
*/ |
||||
private String charset = "utf-8"; |
||||
|
||||
public String getCharset() { |
||||
return charset; |
||||
} |
||||
|
||||
public void setCharset(String charset) { |
||||
this.charset = charset; |
||||
} |
||||
|
||||
public String getHeartbeat() { |
||||
return heartbeat; |
||||
} |
||||
|
||||
public void setHeartbeat(String heartbeat) { |
||||
this.heartbeat = heartbeat; |
||||
} |
||||
|
||||
public long getHeartbeatInterval() { |
||||
return heartbeatInterval; |
||||
} |
||||
|
||||
public void setHeartbeatInterval(long heartbeatInterval) { |
||||
this.heartbeatInterval = heartbeatInterval; |
||||
} |
||||
|
||||
public String getParams() { |
||||
return params; |
||||
} |
||||
|
||||
public void setParams(String params) { |
||||
this.params = params; |
||||
} |
||||
} |
@ -1,36 +0,0 @@ |
||||
/* |
||||
* 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.core.inf; |
||||
|
||||
/** |
||||
* Created by lyy on 2017/6/3. |
||||
*/ |
||||
public abstract class AbsNormalTask<TASK_WRAPPER extends AbsTaskWrapper> |
||||
extends AbsTask<TASK_WRAPPER> { |
||||
|
||||
/** |
||||
* 最高优先级命令,最高优先级命令有以下属性 |
||||
* 1、在下载队列中,有且只有一个最高优先级任务 |
||||
* 2、最高优先级任务会一直存在,直到用户手动暂停或任务完成 |
||||
* 3、任务调度器不会暂停最高优先级任务 |
||||
* 4、用户手动暂停或任务完成后,第二次重新执行该任务,该命令将失效 |
||||
* 5、如果下载队列中已经满了,则会停止队尾的任务 |
||||
* 6、把任务设置为最高优先级任务后,将自动执行任务,不需要重新调用start()启动任务 |
||||
*/ |
||||
public void setHighestPriority(boolean isHighestPriority) { |
||||
isHeighestTask = isHighestPriority; |
||||
} |
||||
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue