FTP 文件夹下载

pull/330/head
AriaLyy 7 years ago
parent a5b41c0d2e
commit d239ecc1e7
  1. 6
      Aria/src/main/java/com/arialyy/aria/core/download/BaseGroupTarget.java
  2. 4
      Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupEntity.java
  3. 11
      Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupTask.java
  4. 9
      Aria/src/main/java/com/arialyy/aria/core/download/DownloadReceiver.java
  5. 15
      Aria/src/main/java/com/arialyy/aria/core/download/FtpDirDownloadTarget.java
  6. 3
      Aria/src/main/java/com/arialyy/aria/core/download/FtpDownloadTarget.java
  7. 44
      Aria/src/main/java/com/arialyy/aria/core/download/downloader/AbsFtpInfoThread.java
  8. 397
      Aria/src/main/java/com/arialyy/aria/core/download/downloader/AbsGroupUtil.java
  9. 2
      Aria/src/main/java/com/arialyy/aria/core/download/downloader/AbsThreadTask.java
  10. 6
      Aria/src/main/java/com/arialyy/aria/core/download/downloader/ConnectionHelp.java
  11. 339
      Aria/src/main/java/com/arialyy/aria/core/download/downloader/DownloadGroupUtil.java
  12. 8
      Aria/src/main/java/com/arialyy/aria/core/download/downloader/Downloader.java
  13. 68
      Aria/src/main/java/com/arialyy/aria/core/download/downloader/FtpDirDownloadUtil.java
  14. 33
      Aria/src/main/java/com/arialyy/aria/core/download/downloader/FtpDirInfoThread.java
  15. 26
      Aria/src/main/java/com/arialyy/aria/core/download/downloader/FtpThreadTask.java
  16. 3
      Aria/src/main/java/com/arialyy/aria/core/download/downloader/SubThreadConfig.java
  17. 18
      Aria/src/main/java/com/arialyy/aria/core/inf/AbsTaskEntity.java
  18. 28
      Aria/src/main/java/com/arialyy/aria/core/inf/ITask.java
  19. 4
      app/src/main/java/com/arialyy/simple/download/FtpDownloadActivity.java
  20. 27
      app/src/main/java/com/arialyy/simple/download/group/FTPDirDownloadActivity.java

@ -32,15 +32,15 @@ abstract class BaseGroupTarget<TARGET extends AbsTarget, TASK_ENTITY extends Abs
extends AbsDownloadTarget<TARGET, DownloadGroupEntity, TASK_ENTITY> {
List<String> mUrls = new ArrayList<>();
String mGroupName;
/**
* 子任务文件名
*/
List<String> mSubTaskFileName = new ArrayList<>();
String mGroupName;
private List<String> mSubTaskFileName = new ArrayList<>();
/**
* 是否已经设置了文件路径
*/
boolean isSetDirPathed = false;
private boolean isSetDirPathed = false;
/**
* 查询任务组实体如果数据库不存在该实体则新创建一个新的任务组实体

@ -47,7 +47,7 @@ public class DownloadGroupEntity extends AbsGroupEntity {
return subtask;
}
void setSubTasks(List<DownloadEntity> subTasks) {
public void setSubTasks(List<DownloadEntity> subTasks) {
this.subtask = subTasks;
}
@ -63,7 +63,7 @@ public class DownloadGroupEntity extends AbsGroupEntity {
return urls;
}
void setUrls(List<String> urls) {
public void setUrls(List<String> urls) {
this.urls = urls;
}

@ -18,8 +18,10 @@ package com.arialyy.aria.core.download;
import android.os.Handler;
import com.arialyy.aria.core.AriaManager;
import com.arialyy.aria.core.download.downloader.DownloadGroupUtil;
import com.arialyy.aria.core.download.downloader.FtpDirDownloadUtil;
import com.arialyy.aria.core.download.downloader.IDownloadUtil;
import com.arialyy.aria.core.inf.AbsGroupTask;
import com.arialyy.aria.core.inf.AbsTaskEntity;
import com.arialyy.aria.core.scheduler.ISchedulers;
import com.arialyy.aria.util.CheckUtil;
@ -38,7 +40,14 @@ public class DownloadGroupTask extends AbsGroupTask<DownloadGroupTaskEntity, Dow
mOutHandler = outHandler;
mContext = AriaManager.APP;
mListener = new DownloadGroupListener(this, mOutHandler);
mUtil = new DownloadGroupUtil(mListener, mTaskEntity);
switch (taskEntity.requestType) {
case AbsTaskEntity.HTTP:
mUtil = new DownloadGroupUtil(mListener, mTaskEntity);
break;
case AbsTaskEntity.FTP_DIR:
mUtil = new FtpDirDownloadUtil(mListener, mTaskEntity);
break;
}
}
@Override public boolean isRunning() {

@ -185,6 +185,15 @@ public class DownloadReceiver extends AbsReceiver {
return DbEntity.findFirst(DownloadGroupTaskEntity.class, "key=?", hashCode);
}
/**
* 通过任务组key获取任务组实体
* 如果是httpkey为所有子任务下载地址拼接后取md5
* 如果是ftpkey为ftp服务器的文件夹路径
*/
public DownloadGroupTaskEntity getDownloadGroupTask(String key) {
return DbEntity.findFirst(DownloadGroupTaskEntity.class, "key=?", key);
}
/**
* 下载任务是否存在
*/

@ -27,23 +27,22 @@ import com.arialyy.aria.orm.DbEntity;
public class FtpDirDownloadTarget
extends BaseGroupTarget<FtpDirDownloadTarget, DownloadGroupTaskEntity> {
private final String TAG = "FtpDirDownloadTarget";
private String serverIp, remotePath, mGroupName;
private String serverIp, remotePath;
private int port;
FtpDirDownloadTarget(String url, String targetName) {
init(url);
String[] pp = url.split("/")[2].split(":");
this.serverIp = pp[0];
this.port = Integer.parseInt(pp[1]);
mTaskEntity.requestType = AbsTaskEntity.FTP;
mTargetName = targetName;
serverIp = pp[0];
port = Integer.parseInt(pp[1]);
mTaskEntity.requestType = AbsTaskEntity.FTP_DIR;
mTaskEntity.serverIp = serverIp;
mTaskEntity.port = port;
remotePath = url.substring(url.indexOf(pp[1]) + pp[1].length(), url.length());
if (TextUtils.isEmpty(remotePath)) {
throw new NullPointerException("ftp服务器地址不能为null");
}
mTargetName = targetName;
int lastIndex = url.lastIndexOf("/");
mTaskEntity.remotePath = remotePath;
mEntity.setDirPath(url.substring(lastIndex + 1, url.length()));
}
private void init(String key) {

@ -42,7 +42,8 @@ public class FtpDownloadTarget extends DownloadTarget {
throw new NullPointerException("ftp服务器地址不能为null");
}
int lastIndex = url.lastIndexOf("/");
mTaskEntity.remotePath = remotePath;
mTaskEntity.serverIp = serverIp;
mTaskEntity.port = port;
mEntity.setFileName(url.substring(lastIndex + 1, url.length()));
}

@ -19,7 +19,6 @@ import android.text.TextUtils;
import android.util.Log;
import com.arialyy.aria.core.AriaManager;
import com.arialyy.aria.core.inf.AbsEntity;
import com.arialyy.aria.core.inf.AbsGroupEntity;
import com.arialyy.aria.core.inf.AbsTaskEntity;
import com.arialyy.aria.util.CommonUtil;
import java.io.IOException;
@ -36,11 +35,10 @@ abstract class AbsFtpInfoThread<ENTITY extends AbsEntity, TASK_ENTITY extends Ab
implements Runnable {
private final String TAG = "HttpFileInfoThread";
private ENTITY mEntity;
private TASK_ENTITY mTaskEntity;
protected ENTITY mEntity;
protected TASK_ENTITY mTaskEntity;
private int mConnectTimeOut;
private OnFileInfoCallback mCallback;
private boolean isDir = false;
AbsFtpInfoThread(TASK_ENTITY taskEntity, OnFileInfoCallback callback) {
mTaskEntity = taskEntity;
@ -48,16 +46,18 @@ abstract class AbsFtpInfoThread<ENTITY extends AbsEntity, TASK_ENTITY extends Ab
mConnectTimeOut =
AriaManager.getInstance(AriaManager.APP).getDownloadConfig().getConnectTimeOut();
mCallback = callback;
isDir = mEntity instanceof AbsGroupEntity;
}
@Override public void run() {
FTPClient client = null;
try {
String url = mTaskEntity.getEntity().getKey();
String[] pp = url.split("/")[2].split(":");
String serverIp = pp[0];
int port = Integer.parseInt(pp[1]);
String remotePath = url.substring(url.indexOf(pp[1]) + pp[1].length(), url.length());
client = new FTPClient();
String[] pp = mEntity.getKey().split("/")[2].split(":");
String fileName = mTaskEntity.remotePath;
client.connect(pp[0], Integer.parseInt(pp[1]));
client.connect(serverIp, port);
if (!TextUtils.isEmpty(mTaskEntity.account)) {
client.login(mTaskEntity.userName, mTaskEntity.userPw);
} else {
@ -70,13 +70,27 @@ abstract class AbsFtpInfoThread<ENTITY extends AbsEntity, TASK_ENTITY extends Ab
return;
}
client.setDataTimeout(mConnectTimeOut);
String charSet = "UTF-8";
// 开启服务器对UTF-8的支持,如果服务器支持就用UTF-8编码
if (!TextUtils.isEmpty(mTaskEntity.charSet) || !FTPReply.isPositiveCompletion(
client.sendCommand("OPTS UTF8", "ON"))) {
charSet = mTaskEntity.charSet;
}
client.setControlEncoding(charSet);
client.enterLocalPassiveMode();
client.setFileType(FTP.BINARY_FILE_TYPE);
FTPFile[] files =
client.listFiles(CommonUtil.strCharSetConvert(fileName, mTaskEntity.charSet));
long size = getFileSize(files, client, fileName);
client.listFiles(new String(remotePath.getBytes(charSet), ConnectionHelp.SERVER_CHARSET));
long size = getFileSize(files, client, remotePath);
mEntity.setFileSize(size);
reply = client.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply)) {
client.disconnect();
failDownload("获取文件信息错误,错误码为:" + reply);
return;
}
mTaskEntity.code = reply;
onPreComplete();
mEntity.update();
mTaskEntity.update();
mCallback.onComplete(mEntity.getKey(), reply);
@ -85,7 +99,6 @@ abstract class AbsFtpInfoThread<ENTITY extends AbsEntity, TASK_ENTITY extends Ab
} finally {
if (client != null) {
try {
client.logout();
client.disconnect();
} catch (IOException e) {
e.printStackTrace();
@ -94,6 +107,14 @@ abstract class AbsFtpInfoThread<ENTITY extends AbsEntity, TASK_ENTITY extends Ab
}
}
void start() {
new Thread(this).start();
}
protected void onPreComplete() {
}
/**
* 遍历FTP服务器上对应文件或文件夹大小
*
@ -105,6 +126,7 @@ abstract class AbsFtpInfoThread<ENTITY extends AbsEntity, TASK_ENTITY extends Ab
for (FTPFile file : files) {
if (file.isFile()) {
size += file.getSize();
handleFile(path + file.getName(), file);
} else {
size += getFileSize(client.listFiles(
CommonUtil.strCharSetConvert(path + file.getName(), mTaskEntity.charSet)), client,

@ -0,0 +1,397 @@
/*
* 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.AriaManager;
import com.arialyy.aria.core.download.DownloadEntity;
import com.arialyy.aria.core.download.DownloadGroupTaskEntity;
import com.arialyy.aria.core.download.DownloadTaskEntity;
import com.arialyy.aria.core.inf.IEntity;
import com.arialyy.aria.orm.DbEntity;
import com.arialyy.aria.util.CommonUtil;
import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created by AriaL on 2017/6/30.
* 任务组核心逻辑
*/
abstract class AbsGroupUtil implements IDownloadUtil {
private final String TAG = "DownloadGroupUtil";
/**
* 任务组所有任务总大小
*/
long mTotalSize = 0;
private long mCurrentLocation = 0;
private ExecutorService mExePool;
protected IDownloadGroupListener mListener;
DownloadGroupTaskEntity mTaskEntity;
private boolean isRunning = true;
private Timer mTimer;
/**
* 初始化完成的任务书数
*/
int mInitNum = 0;
/**
* 初始化失败的任务数
*/
int mInitFailNum = 0;
/**
* 保存所有没有下载完成的任务key为下载地址
*/
Map<String, DownloadTaskEntity> mExeMap = new HashMap<>();
/**
* 下载失败的映射表key为下载地址
*/
Map<String, DownloadTaskEntity> mFailMap = new HashMap<>();
/**
* 下载器映射表key为下载地址
*/
private Map<String, Downloader> mDownloaderMap = new HashMap<>();
/**
* 该任务组对应的所有任务
*/
private Map<String, DownloadTaskEntity> mTasksMap = new HashMap<>();
//已经完成的任务数
private int mCompleteNum = 0;
//失败的任务数
private int mFailNum = 0;
//实际的下载任务数
int mActualTaskNum = 0;
AbsGroupUtil(IDownloadGroupListener listener, DownloadGroupTaskEntity taskEntity) {
mListener = listener;
mTaskEntity = taskEntity;
mExePool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
List<DownloadTaskEntity> tasks =
DbEntity.findDatas(DownloadTaskEntity.class, "groupName=?", mTaskEntity.key);
if (tasks != null && !tasks.isEmpty()) {
for (DownloadTaskEntity te : tasks) {
mTasksMap.put(te.getEntity().getDownloadUrl(), te);
}
}
for (DownloadEntity entity : mTaskEntity.entity.getSubTask()) {
File file = new File(entity.getDownloadPath());
if (entity.getState() == IEntity.STATE_COMPLETE && file.exists()) {
mCompleteNum++;
mInitNum++;
mCurrentLocation += entity.getFileSize();
} else {
mExeMap.put(entity.getDownloadUrl(), createChildDownloadTask(entity));
mCurrentLocation += entity.getCurrentProgress();
mActualTaskNum++;
}
mTotalSize += entity.getFileSize();
}
}
@Override public long getFileSize() {
return mTotalSize;
}
@Override public long getCurrentLocation() {
return mCurrentLocation;
}
@Override public boolean isDownloading() {
return isRunning;
}
@Override public void cancelDownload() {
closeTimer(false);
mListener.onCancel();
onCancel();
if (!mExePool.isShutdown()) {
mExePool.shutdown();
}
Set<String> keys = mDownloaderMap.keySet();
for (String key : keys) {
Downloader dt = mDownloaderMap.get(key);
if (dt != null) {
dt.cancelDownload();
}
}
delDownloadInfo();
mTaskEntity.deleteData();
}
public void onCancel() {
}
/**
* 删除所有子任务的下载信息
*/
private void delDownloadInfo() {
List<DownloadTaskEntity> tasks =
DbEntity.findDatas(DownloadTaskEntity.class, "groupName=?", mTaskEntity.key);
if (tasks == null || tasks.isEmpty()) return;
for (DownloadTaskEntity taskEntity : tasks) {
CommonUtil.delDownloadTaskConfig(taskEntity.removeFile, taskEntity);
}
}
@Override public void stopDownload() {
closeTimer(false);
mListener.onStop(mCurrentLocation);
onStop();
if (!mExePool.isShutdown()) {
mExePool.shutdown();
}
Set<String> keys = mDownloaderMap.keySet();
for (String key : keys) {
Downloader dt = mDownloaderMap.get(key);
if (dt != null) {
dt.stopDownload();
}
}
}
protected void onStop() {
}
@Override public void startDownload() {
isRunning = true;
mFailNum = 0;
mListener.onPre();
onStart();
}
protected void onStart() {
}
@Override public void resumeDownload() {
startDownload();
mListener.onResume(mCurrentLocation);
}
@Override public void setMaxSpeed(double maxSpeed) {
}
private void closeTimer(boolean isRunning) {
this.isRunning = isRunning;
if (mTimer != null) {
mTimer.purge();
mTimer.cancel();
}
}
/**
* 开始进度流程
*/
void startRunningFlow() {
closeTimer(true);
mListener.onPostPre(mTotalSize);
mListener.onStart(mCurrentLocation);
mTimer = new Timer(true);
mTimer.schedule(new TimerTask() {
@Override public void run() {
if (!isRunning) {
closeTimer(false);
} else if (mCurrentLocation >= 0) {
mListener.onProgress(mCurrentLocation);
}
}
}, 0, 1000);
}
/**
* 启动子任务下载器
*/
void startChildDownload(DownloadTaskEntity taskEntity) {
ChildDownloadListener listener = new ChildDownloadListener(taskEntity);
Downloader dt = new Downloader(listener, taskEntity);
mDownloaderMap.put(taskEntity.getEntity().getDownloadUrl(), dt);
if (mExePool.isShutdown()) return;
mExePool.execute(dt);
}
/**
* 创建子任务下载信息
*/
DownloadTaskEntity createChildDownloadTask(DownloadEntity entity) {
DownloadTaskEntity taskEntity = mTasksMap.get(entity.getDownloadUrl());
if (taskEntity != null) {
taskEntity.entity = entity;
//ftp登录的
taskEntity.userName = mTaskEntity.userName;
taskEntity.userPw = mTaskEntity.userPw;
taskEntity.account = mTaskEntity.account;
return taskEntity;
}
taskEntity = new DownloadTaskEntity();
taskEntity.entity = entity;
taskEntity.headers = mTaskEntity.headers;
taskEntity.requestEnum = mTaskEntity.requestEnum;
taskEntity.redirectUrlKey = mTaskEntity.redirectUrlKey;
taskEntity.removeFile = mTaskEntity.removeFile;
taskEntity.groupName = mTaskEntity.key;
taskEntity.isGroupTask = true;
taskEntity.requestType = mTaskEntity.requestType;
//ftp登录的
taskEntity.userName = mTaskEntity.userName;
taskEntity.userPw = mTaskEntity.userPw;
taskEntity.account = mTaskEntity.account;
taskEntity.key = entity.getDownloadPath();
taskEntity.save();
return taskEntity;
}
/**
* 子任务事件监听
*/
private class ChildDownloadListener implements IDownloadListener {
DownloadTaskEntity taskEntity;
DownloadEntity entity;
long lastLen = 0;
ChildDownloadListener(DownloadTaskEntity entity) {
this.taskEntity = entity;
this.entity = taskEntity.getEntity();
lastLen = this.entity.getCurrentProgress();
this.entity.setFailNum(0);
}
@Override public void onPre() {
saveData(IEntity.STATE_PRE, -1);
}
@Override public void onPostPre(long fileSize) {
entity.setFileSize(fileSize);
entity.setConvertFileSize(CommonUtil.formatFileSize(fileSize));
saveData(IEntity.STATE_POST_PRE, -1);
}
@Override public void onResume(long resumeLocation) {
saveData(IEntity.STATE_POST_PRE, IEntity.STATE_RUNNING);
lastLen = resumeLocation;
}
@Override public void onStart(long startLocation) {
saveData(IEntity.STATE_POST_PRE, IEntity.STATE_RUNNING);
lastLen = startLocation;
}
@Override public void onProgress(long currentLocation) {
long speed = currentLocation - lastLen;
mCurrentLocation += speed;
lastLen = currentLocation;
entity.setCurrentProgress(currentLocation);
handleSpeed(speed);
}
@Override public void onStop(long stopLocation) {
saveData(IEntity.STATE_STOP, stopLocation);
handleSpeed(0);
mListener.onSubStop(entity);
}
@Override public void onCancel() {
saveData(IEntity.STATE_CANCEL, -1);
handleSpeed(0);
mListener.onSubCancel(entity);
}
@Override public void onComplete() {
saveData(IEntity.STATE_COMPLETE, entity.getFileSize());
mCompleteNum++;
handleSpeed(0);
mListener.onSubComplete(entity);
//如果子任务完成的数量和总任务数一致,表示任务组任务已经完成
if (mCompleteNum >= mTaskEntity.getEntity().getSubTask().size()) {
closeTimer(false);
mListener.onComplete();
} else if (mCompleteNum + mFailNum >= mActualTaskNum) {
//如果子任务完成数量加上失败的数量和总任务数一致,则任务组停止下载
closeTimer(false);
}
}
@Override public void onFail() {
entity.setFailNum(entity.getFailNum() + 1);
saveData(IEntity.STATE_FAIL, lastLen);
handleSpeed(0);
reTry();
}
/**
* 失败后重试下载如果失败次数超过5次不再重试
*/
private void reTry() {
synchronized (AriaManager.LOCK) {
if (entity.getFailNum() < 5 && isRunning) {
reStartTask();
} else {
mFailNum++;
mListener.onSubFail(entity);
//如果失败的任务数大于实际的下载任务数,任务组停止下载
if (mFailNum >= mActualTaskNum) {
closeTimer(false);
mListener.onStop(mCurrentLocation);
}
}
}
}
private void reStartTask() {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override public void run() {
Downloader dt = mDownloaderMap.get(entity.getDownloadUrl());
dt.startDownload();
}
}, 3000);
}
private void handleSpeed(long speed) {
entity.setSpeed(speed);
entity.setConvertSpeed(speed <= 0 ? "" : CommonUtil.formatFileSize(speed) + "/s");
}
private void saveData(int state, long location) {
entity.setState(state);
entity.setComplete(state == IEntity.STATE_COMPLETE);
if (entity.isComplete()) {
entity.setCompleteTime(System.currentTimeMillis());
entity.setCurrentProgress(entity.getFileSize());
} else if (location > 0) {
entity.setCurrentProgress(location);
}
entity.update();
}
@Override public void supportBreakpoint(boolean support) {
}
}
}

@ -154,6 +154,8 @@ abstract class AbsThreadTask implements Runnable {
STATE.isStop = true;
if (ex != null) {
Log.e(TAG, msg + "\n" + CommonUtil.getPrintException(ex));
}else {
Log.e(TAG, msg);
}
if (mConfig.IS_SUPPORT_BREAK_POINT) {
writeConfig(false, currentLocation);

@ -26,12 +26,18 @@ import java.util.Set;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import org.apache.commons.net.ftp.FTPClient;
/**
* Created by lyy on 2017/1/18.
* 链接帮助类
*/
class ConnectionHelp {
/**
* FTP 服务器编码
*/
static String SERVER_CHARSET = "ISO-8859-1";
/**
* 处理链接
*

@ -16,19 +16,10 @@
package com.arialyy.aria.core.download.downloader;
import android.util.SparseArray;
import com.arialyy.aria.core.download.DownloadEntity;
import com.arialyy.aria.core.download.DownloadGroupTaskEntity;
import com.arialyy.aria.core.download.DownloadTaskEntity;
import com.arialyy.aria.core.inf.IEntity;
import com.arialyy.aria.orm.DbEntity;
import com.arialyy.aria.util.CommonUtil;
import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@ -36,153 +27,37 @@ import java.util.concurrent.Executors;
* Created by AriaL on 2017/6/30.
* 任务组下载工具
*/
public class DownloadGroupUtil implements IDownloadUtil {
public class DownloadGroupUtil extends AbsGroupUtil implements IDownloadUtil {
private final String TAG = "DownloadGroupUtil";
/**
* 任务组所有任务总大小
*/
private long mTotalSize = 0;
private long mCurrentLocation = 0;
private ExecutorService mInfoPool;
private ExecutorService mExePool;
private IDownloadGroupListener mListener;
private DownloadGroupTaskEntity mTaskEntity;
private boolean isRunning = true;
private Timer mTimer;
/**
* 初始化完成的任务书数
*/
private int mInitNum = 0;
/**
* 初始化失败的任务数
*/
private int mInitFailNum = 0;
/**
* 保存所有没有下载完成的任务key为下载地址
*/
private Map<String, DownloadTaskEntity> mExeMap = new HashMap<>();
/**
* 下载失败的映射表key为下载地址
*/
private Map<String, DownloadTaskEntity> mFailMap = new HashMap<>();
/**
* 下载器映射表key为下载地址
*/
private Map<String, Downloader> mDownloaderMap = new HashMap<>();
/**
* 文件信息回调组
*/
private SparseArray<OnFileInfoCallback> mFileInfoCallbacks = new SparseArray<>();
/**
* 该任务组对应的所有任务
*/
private Map<String, DownloadTaskEntity> mTasksMap = new HashMap<>();
//已经完成的任务数
private int mCompleteNum = 0;
//失败的任务数
private int mFailNum = 0;
//实际的下载任务数
private int mActualTaskNum = 0;
public DownloadGroupUtil(IDownloadGroupListener listener, DownloadGroupTaskEntity taskEntity) {
mListener = listener;
mTaskEntity = taskEntity;
super(listener, taskEntity);
mInfoPool = Executors.newCachedThreadPool();
mExePool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
mActualTaskNum = mTaskEntity.entity.getSubTask().size();
List<DownloadTaskEntity> tasks =
DbEntity.findDatas(DownloadTaskEntity.class, "groupName=?", mTaskEntity.key);
if (tasks != null && !tasks.isEmpty()) {
for (DownloadTaskEntity te : tasks) {
mTasksMap.put(te.getEntity().getDownloadUrl(), te);
}
}
for (DownloadEntity entity : mTaskEntity.entity.getSubTask()) {
File file = new File(entity.getDownloadPath());
if (entity.getState() == IEntity.STATE_COMPLETE && file.exists()) {
mCompleteNum++;
mInitNum++;
mCurrentLocation += entity.getFileSize();
} else {
mExeMap.put(entity.getDownloadUrl(), createChildDownloadTask(entity));
mCurrentLocation += entity.getCurrentProgress();
}
mTotalSize += entity.getFileSize();
}
}
@Override public long getFileSize() {
return mTotalSize;
}
@Override public long getCurrentLocation() {
return mCurrentLocation;
}
@Override public boolean isDownloading() {
return isRunning;
}
@Override public void cancelDownload() {
closeTimer(false);
mListener.onCancel();
@Override public void onCancel() {
super.onCancel();
if (!mInfoPool.isShutdown()) {
mInfoPool.shutdown();
}
if (!mExePool.isShutdown()) {
mExePool.shutdown();
}
Set<String> keys = mDownloaderMap.keySet();
for (String key : keys) {
Downloader dt = mDownloaderMap.get(key);
if (dt != null) {
dt.cancelDownload();
}
}
delDownloadInfo();
mTaskEntity.deleteData();
}
/**
* 删除所有子任务的下载信息
*/
private void delDownloadInfo() {
List<DownloadTaskEntity> tasks =
DbEntity.findDatas(DownloadTaskEntity.class, "groupName=?", mTaskEntity.key);
if (tasks == null || tasks.isEmpty()) return;
for (DownloadTaskEntity taskEntity : tasks) {
CommonUtil.delDownloadTaskConfig(taskEntity.removeFile, taskEntity);
}
}
@Override public void stopDownload() {
closeTimer(false);
mListener.onStop(mCurrentLocation);
@Override protected void onStop() {
super.onStop();
if (!mInfoPool.isShutdown()) {
mInfoPool.shutdown();
}
if (!mExePool.isShutdown()) {
mExePool.shutdown();
}
Set<String> keys = mDownloaderMap.keySet();
for (String key : keys) {
Downloader dt = mDownloaderMap.get(key);
if (dt != null) {
dt.stopDownload();
}
}
}
@Override public void startDownload() {
isRunning = true;
mFailNum = 0;
@Override protected void onStart() {
super.onStart();
Set<String> keys = mExeMap.keySet();
mListener.onPre();
int i = 0;
for (String key : keys) {
DownloadTaskEntity taskEntity = mExeMap.get(key);
@ -199,15 +74,6 @@ public class DownloadGroupUtil implements IDownloadUtil {
if (i == mExeMap.size()) startRunningFlow();
}
@Override public void resumeDownload() {
startDownload();
mListener.onResume(mCurrentLocation);
}
@Override public void setMaxSpeed(double maxSpeed) {
}
/**
* 创建文件信息获取线程
*/
@ -254,193 +120,4 @@ public class DownloadGroupUtil implements IDownloadUtil {
}
return new HttpFileInfoThread(taskEntity, callback);
}
private void closeTimer(boolean isRunning) {
this.isRunning = isRunning;
if (mTimer != null) {
mTimer.purge();
mTimer.cancel();
}
}
/**
* 开始进度流程
*/
private void startRunningFlow() {
closeTimer(true);
mListener.onPostPre(mTotalSize);
mListener.onStart(mCurrentLocation);
mTimer = new Timer(true);
mTimer.schedule(new TimerTask() {
@Override public void run() {
if (!isRunning) {
closeTimer(false);
} else if (mCurrentLocation >= 0) {
mListener.onProgress(mCurrentLocation);
}
}
}, 0, 1000);
}
/**
* 启动子任务下载器
*/
private void startChildDownload(DownloadTaskEntity taskEntity) {
ChildDownloadListener listener = new ChildDownloadListener(taskEntity);
Downloader dt = new Downloader(listener, taskEntity);
mDownloaderMap.put(taskEntity.getEntity().getDownloadUrl(), dt);
if (mExePool.isShutdown()) return;
mExePool.execute(dt);
}
/**
* 创建子任务下载信息
*/
private DownloadTaskEntity createChildDownloadTask(DownloadEntity entity) {
DownloadTaskEntity taskEntity = mTasksMap.get(entity.getDownloadUrl());
if (taskEntity != null) {
taskEntity.entity = entity;
return taskEntity;
}
taskEntity = new DownloadTaskEntity();
taskEntity.entity = entity;
taskEntity.headers = mTaskEntity.headers;
taskEntity.requestEnum = mTaskEntity.requestEnum;
taskEntity.redirectUrlKey = mTaskEntity.redirectUrlKey;
taskEntity.removeFile = mTaskEntity.removeFile;
taskEntity.groupName = mTaskEntity.key;
taskEntity.isGroupTask = true;
taskEntity.key = entity.getDownloadPath();
taskEntity.save();
return taskEntity;
}
/**
* 子任务事件监听
*/
private class ChildDownloadListener implements IDownloadListener {
DownloadTaskEntity taskEntity;
DownloadEntity entity;
long lastLen = 0;
ChildDownloadListener(DownloadTaskEntity entity) {
this.taskEntity = entity;
this.entity = taskEntity.getEntity();
lastLen = this.entity.getCurrentProgress();
this.entity.setFailNum(0);
}
@Override public void onPre() {
saveData(IEntity.STATE_PRE, -1);
}
@Override public void onPostPre(long fileSize) {
entity.setFileSize(fileSize);
entity.setConvertFileSize(CommonUtil.formatFileSize(fileSize));
saveData(IEntity.STATE_POST_PRE, -1);
}
@Override public void onResume(long resumeLocation) {
saveData(IEntity.STATE_POST_PRE, IEntity.STATE_RUNNING);
lastLen = resumeLocation;
}
@Override public void onStart(long startLocation) {
saveData(IEntity.STATE_POST_PRE, IEntity.STATE_RUNNING);
lastLen = startLocation;
}
@Override public void onProgress(long currentLocation) {
long speed = currentLocation - lastLen;
mCurrentLocation += speed;
lastLen = currentLocation;
entity.setCurrentProgress(currentLocation);
handleSpeed(speed);
}
@Override public void onStop(long stopLocation) {
saveData(IEntity.STATE_STOP, stopLocation);
handleSpeed(0);
mListener.onSubStop(entity);
}
@Override public void onCancel() {
saveData(IEntity.STATE_CANCEL, -1);
handleSpeed(0);
mListener.onSubCancel(entity);
}
@Override public void onComplete() {
saveData(IEntity.STATE_COMPLETE, entity.getFileSize());
mCompleteNum++;
handleSpeed(0);
mListener.onSubComplete(entity);
//如果子任务完成的数量和总任务数一致,表示任务组任务已经完成
if (mCompleteNum >= mTaskEntity.getEntity().getSubTask().size()) {
closeTimer(false);
mListener.onComplete();
} else if (mCompleteNum + mFailNum >= mActualTaskNum) {
//如果子任务完成数量加上失败的数量和总任务数一致,则任务组停止下载
closeTimer(false);
mListener.onComplete();
}
}
@Override public void onFail() {
entity.setFailNum(entity.getFailNum() + 1);
saveData(IEntity.STATE_FAIL, lastLen);
handleSpeed(0);
reTry();
}
/**
* 失败后重试下载如果失败次数超过5次不再重试
*/
private void reTry() {
if (entity.getFailNum() < 5) {
reStartTask();
} else {
mFailNum++;
mListener.onSubFail(entity);
//如果失败的任务数大于实际的下载任务数,任务组停止下载
if (mFailNum >= mActualTaskNum) {
closeTimer(false);
mListener.onStop(mCurrentLocation);
}
}
}
private void reStartTask() {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override public void run() {
Downloader dt = mDownloaderMap.get(entity.getDownloadUrl());
dt.startDownload();
}
}, 3000);
}
private void handleSpeed(long speed) {
entity.setSpeed(speed);
entity.setConvertSpeed(speed <= 0 ? "" : CommonUtil.formatFileSize(speed) + "/s");
}
private void saveData(int state, long location) {
entity.setState(state);
entity.setComplete(state == IEntity.STATE_COMPLETE);
if (entity.isComplete()) {
entity.setCompleteTime(System.currentTimeMillis());
entity.setCurrentProgress(entity.getFileSize());
} else if (location > 0) {
entity.setCurrentProgress(location);
}
entity.update();
}
@Override public void supportBreakpoint(boolean support) {
}
}
}

@ -96,8 +96,10 @@ class Downloader implements Runnable, IDownloadUtil {
mConstance.THREAD_NUM = mThreadNum;
handleNoSupportBreakpointDownload();
} else {
mThreadNum = isNewTask ? (mEntity.getFileSize() <= SUB_LEN ? 1
: AriaManager.getInstance(mContext).getDownloadConfig().getThreadNum()) : mRealThreadNum;
mThreadNum = isNewTask ? (
mEntity.getFileSize() <= SUB_LEN || mTaskEntity.requestType == AbsTaskEntity.FTP_DIR ? 1
: AriaManager.getInstance(mContext).getDownloadConfig().getThreadNum())
: mRealThreadNum;
mConstance.THREAD_NUM = mThreadNum;
mFixedThreadPool = Executors.newFixedThreadPool(mThreadNum);
handleBreakpoint();
@ -416,7 +418,7 @@ class Downloader implements Runnable, IDownloadUtil {
private AbsThreadTask createThreadTask(SubThreadConfig config) {
switch (mTaskEntity.requestType) {
case AbsTaskEntity.FTP:
config.remotePath = mTaskEntity.remotePath;
case AbsTaskEntity.FTP_DIR:
return new FtpThreadTask(mConstance, mListener, config);
case AbsTaskEntity.HTTP:
return new HttpThreadTask(mConstance, mListener, config);

@ -0,0 +1,68 @@
/*
* 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.download.DownloadEntity;
import com.arialyy.aria.core.download.DownloadGroupTaskEntity;
import com.arialyy.aria.core.download.DownloadTaskEntity;
import java.util.Set;
/**
* Created by Aria.Lao on 2017/7/27.
* ftp文件夹下载工具
*/
public class FtpDirDownloadUtil extends AbsGroupUtil {
public FtpDirDownloadUtil(IDownloadGroupListener listener, DownloadGroupTaskEntity taskEntity) {
super(listener, taskEntity);
}
@Override protected void onStart() {
super.onStart();
if (mTaskEntity.getEntity().getFileSize() > 1) {
start();
} else {
new FtpDirInfoThread(mTaskEntity, new OnFileInfoCallback() {
@Override public void onComplete(String url, int code) {
if (code >= 200 && code < 300) {
mTotalSize = mTaskEntity.getEntity().getFileSize();
for (DownloadEntity entity : mTaskEntity.entity.getSubTask()) {
mExeMap.put(entity.getDownloadUrl(), createChildDownloadTask(entity));
}
mActualTaskNum = mTaskEntity.entity.getSubTask().size();
start();
}
}
@Override public void onFail(String url, String errorMsg) {
mListener.onFail();
}
}).start();
}
}
private void start() {
int i = 0;
Set<String> keys = mExeMap.keySet();
for (String key : keys) {
DownloadTaskEntity taskEntity = mExeMap.get(key);
if (taskEntity != null) {
startChildDownload(taskEntity);
i++;
}
}
if (i == mExeMap.size()) startRunningFlow();
}
}

@ -15,8 +15,12 @@
*/
package com.arialyy.aria.core.download.downloader;
import com.arialyy.aria.core.download.DownloadEntity;
import com.arialyy.aria.core.download.DownloadGroupEntity;
import com.arialyy.aria.core.download.DownloadGroupTaskEntity;
import com.arialyy.aria.util.CommonUtil;
import java.nio.charset.Charset;
import java.util.ArrayList;
import org.apache.commons.net.ftp.FTPFile;
/**
@ -24,6 +28,7 @@ import org.apache.commons.net.ftp.FTPFile;
* 获取ftp文件夹信息
*/
class FtpDirInfoThread extends AbsFtpInfoThread<DownloadGroupEntity, DownloadGroupTaskEntity> {
private long mSize = 0;
FtpDirInfoThread(DownloadGroupTaskEntity taskEntity, OnFileInfoCallback callback) {
super(taskEntity, callback);
@ -31,5 +36,33 @@ class FtpDirInfoThread extends AbsFtpInfoThread<DownloadGroupEntity, DownloadGro
@Override void handleFile(String remotePath, FTPFile ftpFile) {
super.handleFile(remotePath, ftpFile);
mSize += ftpFile.getSize();
addEntity(remotePath, ftpFile);
}
@Override protected void onPreComplete() {
super.onPreComplete();
mEntity.setFileSize(mSize);
}
private void addEntity(String remotePath, FTPFile ftpFile) {
DownloadEntity entity = new DownloadEntity();
entity.setDownloadUrl("ftp://" + mTaskEntity.serverIp + ":" + mTaskEntity.port + remotePath);
entity.setDownloadPath(mEntity.getDirPath() + "/" + remotePath);
int lastIndex = remotePath.lastIndexOf("/");
String fileName = lastIndex < 0 ? CommonUtil.keyToHashKey(remotePath)
: remotePath.substring(lastIndex + 1, remotePath.length());
entity.setFileName(new String(fileName.getBytes(), Charset.forName(mTaskEntity.charSet)));
entity.setGroupName(mEntity.getGroupName());
entity.setGroupChild(true);
entity.setFileSize(ftpFile.getSize());
entity.insert();
if (mEntity.getUrls() == null) {
mEntity.setUrls(new ArrayList<String>());
}
if (mEntity.getSubTask() == null) {
mEntity.setSubTasks(new ArrayList<DownloadEntity>());
}
mEntity.getSubTask().add(entity);
}
}

@ -51,10 +51,13 @@ class FtpThreadTask extends AbsThreadTask {
+ ",结束位置:"
+ mConfig.END_LOCATION
+ "】");
String[] temp = mEntity.getDownloadUrl().split("/");
String[] pp = temp[2].split(":");
String url = mEntity.getDownloadUrl();
String[] pp = url.split("/")[2].split(":");
String serverIp = pp[0];
int port = Integer.parseInt(pp[1]);
String remotePath = url.substring(url.indexOf(pp[1]) + pp[1].length(), url.length());
client = new FTPClient();
client.connect(pp[0], Integer.parseInt(pp[1]));
client.connect(serverIp, port);
if (!TextUtils.isEmpty(mTaskEntity.account)) {
client.login(mTaskEntity.userName, mTaskEntity.userPw);
} else {
@ -66,12 +69,27 @@ class FtpThreadTask extends AbsThreadTask {
failDownload(STATE.CURRENT_LOCATION, "无法连接到ftp服务器,错误码为:" + reply, null);
return;
}
String charSet = "UTF-8";
// 开启服务器对UTF-8的支持,如果服务器支持就用UTF-8编码
if (!TextUtils.isEmpty(mTaskEntity.charSet) || !FTPReply.isPositiveCompletion(
client.sendCommand("OPTS UTF8", "ON"))) {
charSet = mTaskEntity.charSet;
}
client.setControlEncoding(charSet);
client.setDataTimeout(STATE.READ_TIME_OUT);
client.enterLocalPassiveMode();
client.setFileType(FTP.BINARY_FILE_TYPE);
client.setRestartOffset(mConfig.START_LOCATION);
client.allocate(mBufSize);
is = client.retrieveFileStream(mConfig.remotePath);
is = client.retrieveFileStream(
new String(remotePath.getBytes(charSet), ConnectionHelp.SERVER_CHARSET));
//发送第二次指令时,还需要再做一次判断
reply = client.getReplyCode();
if (!FTPReply.isPositivePreliminary(reply)) {
client.disconnect();
failDownload(mChildCurrentLocation, "获取文件信息错误,错误码为:" + reply, null);
return;
}
file = new BufferedRandomAccessFile(mConfig.TEMP_FILE, "rwd", mBufSize);
file.seek(mConfig.START_LOCATION);
byte[] buffer = new byte[mBufSize];

@ -22,7 +22,4 @@ class SubThreadConfig {
String CONFIG_FILE_PATH;
DownloadTaskEntity DOWNLOAD_TASK_ENTITY;
boolean IS_SUPPORT_BREAK_POINT = true;
FTPClient client;
//远程地址
String remotePath;
}

@ -26,23 +26,29 @@ import java.util.Map;
* Created by lyy on 2017/2/23.
*/
public abstract class AbsTaskEntity<ENTITY extends AbsEntity> extends DbEntity {
/**
* HTTP下载
*/
public static final int HTTP = 0x11;
/**
* FTP当文件下载
*/
public static final int FTP = 0x12;
/**
* Task实体对应的key
* FTP文件夹下载为避免登录过多子任务由单线程进行处理
*/
@Primary public String key = "";
public static final int FTP_DIR = 0x13;
/**
* FTP服务器文件或文件夹路径
* Task实体对应的key
*/
public String remotePath;
@Primary public String key = "";
/**
* 账号和密码
*/
@Ignore public String userName, userPw, account;
@Ignore public String userName, userPw, account, serverIp;
@Ignore public int port;
/**
* 请求类型

@ -33,63 +33,63 @@ public interface ITask<ENTITY extends AbsEntity> {
/**
* 唯一标识符DownloadTask 为下载地址UploadTask 为文件路径
*/
public String getKey();
String getKey();
/**
* 任务是否正在执行
*
* @return true正在执行
*/
public boolean isRunning();
boolean isRunning();
/**
* 获取信息实体
*/
public ENTITY getEntity();
ENTITY getEntity();
public void start();
void start();
public void stop();
void stop();
public void cancel();
void cancel();
/**
* 原始byte速度
*/
public long getSpeed();
long getSpeed();
/**
* 转换单位后的速度
*/
public String getConvertSpeed();
String getConvertSpeed();
/**
* 获取百分比进度
*/
public int getPercent();
int getPercent();
/**
* 原始文件byte长度
*/
public long getFileSize();
long getFileSize();
/**
* 转换单位后的文件长度
*/
public String getConvertFileSize();
String getConvertFileSize();
/**
* 获取当前进度
*/
public long getCurrentProgress();
long getCurrentProgress();
/**
* 获取单位转换后的进度
*
* @return 返回 3mb
*/
public String getConvertCurrentProgress();
String getConvertCurrentProgress();
public void setTargetName(String targetName);
void setTargetName(String targetName);
}

@ -36,7 +36,8 @@ import java.io.File;
*/
public class FtpDownloadActivity extends BaseActivity<ActivityFtpDownloadBinding> {
//private final String URL = "ftp://172.18.104.129:21/haha/large.rar";
private final String URL = "ftp://172.18.104.129:21/cd.mp3";
//private final String URL = "ftp://172.18.104.129:21/haha/large.rar";
private final String URL = "ftp://172.18.104.129:21/haha/很大的文件_v100.rar";
@Override protected void init(Bundle savedInstanceState) {
super.init(savedInstanceState);
@ -61,7 +62,6 @@ public class FtpDownloadActivity extends BaseActivity<ActivityFtpDownloadBinding
.loadFtp(URL)
.login("lao", "123456")
.setDownloadPath("/mnt/sdcard/")
.charSet("gbk")
.start();
break;
case R.id.stop:

@ -21,7 +21,9 @@ import android.view.View;
import butterknife.Bind;
import com.arialyy.annotations.DownloadGroup;
import com.arialyy.aria.core.Aria;
import com.arialyy.aria.core.download.DownloadGroupEntity;
import com.arialyy.aria.core.download.DownloadGroupTask;
import com.arialyy.aria.core.download.DownloadGroupTaskEntity;
import com.arialyy.frame.util.show.L;
import com.arialyy.frame.util.show.T;
import com.arialyy.simple.R;
@ -41,19 +43,18 @@ public class FTPDirDownloadActivity extends BaseActivity<ActivityDownloadGroupBi
super.init(savedInstanceState);
Aria.download(this).register();
setTitle("FTP文件夹下载");
//mUrls = getModule(GroupModule.class).getUrls();
//DownloadGroupTaskEntity entity = Aria.download(this).getDownloadGroupTask(mUrls);
//if (entity != null && entity.getEntity() != null) {
// DownloadGroupEntity groupEntity = entity.getEntity();
// mChildList.addData(groupEntity.getSubTask());
// getBinding().setFileSize(groupEntity.getConvertFileSize());
// if (groupEntity.getFileSize() == 0) {
// getBinding().setProgress(0);
// } else {
// getBinding().setProgress(groupEntity.isComplete() ? 100
// : (int) (groupEntity.getCurrentProgress() * 100 / groupEntity.getFileSize()));
// }
//}
DownloadGroupTaskEntity entity = Aria.download(this).getDownloadGroupTask(dir);
if (entity != null && entity.getEntity() != null) {
DownloadGroupEntity groupEntity = entity.getEntity();
mChildList.addData(groupEntity.getSubTask());
getBinding().setFileSize(groupEntity.getConvertFileSize());
if (groupEntity.getFileSize() == 0) {
getBinding().setProgress(0);
} else {
getBinding().setProgress(groupEntity.isComplete() ? 100
: (int) (groupEntity.getCurrentProgress() * 100 / groupEntity.getFileSize()));
}
}
}
@Override protected int setLayoutId() {

Loading…
Cancel
Save