pull/330/head
AriaLyy 7 years ago
commit 0d4f29d921
  1. 1
      .gitignore
  2. 2
      Aria/build.gradle
  3. 2
      Aria/src/main/java/com/arialyy/aria/core/Aria.java
  4. 9
      Aria/src/main/java/com/arialyy/aria/core/AriaManager.java
  5. 2
      Aria/src/main/java/com/arialyy/aria/core/ConfigHelper.java
  6. 2
      Aria/src/main/java/com/arialyy/aria/core/command/group/GroupStartCmd.java
  7. 2
      Aria/src/main/java/com/arialyy/aria/core/command/normal/StartCmd.java
  8. 398
      Aria/src/main/java/com/arialyy/aria/core/common/AbsFileer.java
  9. 204
      Aria/src/main/java/com/arialyy/aria/core/common/AbsThreadTask.java
  10. 19
      Aria/src/main/java/com/arialyy/aria/core/common/IUtil.java
  11. 3
      Aria/src/main/java/com/arialyy/aria/core/common/ProxyHelper.java
  12. 4
      Aria/src/main/java/com/arialyy/aria/core/common/QueueMod.java
  13. 2
      Aria/src/main/java/com/arialyy/aria/core/common/RequestEnum.java
  14. 40
      Aria/src/main/java/com/arialyy/aria/core/common/StateConstance.java
  15. 25
      Aria/src/main/java/com/arialyy/aria/core/common/SubThreadConfig.java
  16. 12
      Aria/src/main/java/com/arialyy/aria/core/download/BaseDListener.java
  17. 202
      Aria/src/main/java/com/arialyy/aria/core/download/BaseGroupTarget.java
  18. 87
      Aria/src/main/java/com/arialyy/aria/core/download/DownloadEntity.java
  19. 4
      Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupEntity.java
  20. 3
      Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupListener.java
  21. 175
      Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupTarget.java
  22. 25
      Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupTask.java
  23. 4
      Aria/src/main/java/com/arialyy/aria/core/download/DownloadListener.java
  24. 39
      Aria/src/main/java/com/arialyy/aria/core/download/DownloadReceiver.java
  25. 11
      Aria/src/main/java/com/arialyy/aria/core/download/DownloadTarget.java
  26. 23
      Aria/src/main/java/com/arialyy/aria/core/download/DownloadTask.java
  27. 1
      Aria/src/main/java/com/arialyy/aria/core/download/DownloadTaskEntity.java
  28. 102
      Aria/src/main/java/com/arialyy/aria/core/download/FtpDirDownloadTarget.java
  29. 120
      Aria/src/main/java/com/arialyy/aria/core/download/FtpDownloadTarget.java
  30. 155
      Aria/src/main/java/com/arialyy/aria/core/download/downloader/AbsFtpInfoThread.java
  31. 399
      Aria/src/main/java/com/arialyy/aria/core/download/downloader/AbsGroupUtil.java
  32. 24
      Aria/src/main/java/com/arialyy/aria/core/download/downloader/ChildThreadConfigEntity.java
  33. 2
      Aria/src/main/java/com/arialyy/aria/core/download/downloader/ConnectionHelp.java
  34. 346
      Aria/src/main/java/com/arialyy/aria/core/download/downloader/DownloadGroupUtil.java
  35. 359
      Aria/src/main/java/com/arialyy/aria/core/download/downloader/Downloader.java
  36. 118
      Aria/src/main/java/com/arialyy/aria/core/download/downloader/FtpClientHelp.java
  37. 68
      Aria/src/main/java/com/arialyy/aria/core/download/downloader/FtpDirDownloadUtil.java
  38. 68
      Aria/src/main/java/com/arialyy/aria/core/download/downloader/FtpDirInfoThread.java
  39. 40
      Aria/src/main/java/com/arialyy/aria/core/download/downloader/FtpDownloadUtil.java
  40. 30
      Aria/src/main/java/com/arialyy/aria/core/download/downloader/FtpFileInfoThread.java
  41. 151
      Aria/src/main/java/com/arialyy/aria/core/download/downloader/FtpThreadTask.java
  42. 42
      Aria/src/main/java/com/arialyy/aria/core/download/downloader/HttpFileInfoThread.java
  43. 128
      Aria/src/main/java/com/arialyy/aria/core/download/downloader/HttpThreadTask.java
  44. 3
      Aria/src/main/java/com/arialyy/aria/core/download/downloader/IDownloadGroupListener.java
  45. 17
      Aria/src/main/java/com/arialyy/aria/core/download/downloader/OnFileInfoCallback.java
  46. 73
      Aria/src/main/java/com/arialyy/aria/core/download/downloader/SimpleDownloadUtil.java
  47. 288
      Aria/src/main/java/com/arialyy/aria/core/download/downloader/SingleThreadTask.java
  48. 37
      Aria/src/main/java/com/arialyy/aria/core/inf/AbsNormalEntity.java
  49. 23
      Aria/src/main/java/com/arialyy/aria/core/inf/AbsTarget.java
  50. 31
      Aria/src/main/java/com/arialyy/aria/core/inf/AbsTaskEntity.java
  51. 56
      Aria/src/main/java/com/arialyy/aria/core/inf/AbsUploadTarget.java
  52. 9
      Aria/src/main/java/com/arialyy/aria/core/inf/IDownloadListener.java
  53. 5
      Aria/src/main/java/com/arialyy/aria/core/inf/IEventListener.java
  54. 2
      Aria/src/main/java/com/arialyy/aria/core/inf/ITarget.java
  55. 4
      Aria/src/main/java/com/arialyy/aria/core/inf/IUploadListener.java
  56. 2
      Aria/src/main/java/com/arialyy/aria/core/queue/DownloadTaskQueue.java
  57. 18
      Aria/src/main/java/com/arialyy/aria/core/scheduler/ISchedulerListener.java
  58. 138
      Aria/src/main/java/com/arialyy/aria/core/upload/BaseUListener.java
  59. 80
      Aria/src/main/java/com/arialyy/aria/core/upload/FtpUploadTarget.java
  60. 2
      Aria/src/main/java/com/arialyy/aria/core/upload/UploadEntity.java
  61. 6
      Aria/src/main/java/com/arialyy/aria/core/upload/UploadListener.java
  62. 19
      Aria/src/main/java/com/arialyy/aria/core/upload/UploadReceiver.java
  63. 47
      Aria/src/main/java/com/arialyy/aria/core/upload/UploadTarget.java
  64. 185
      Aria/src/main/java/com/arialyy/aria/core/upload/uploader/FtpThreadTask.java
  65. 76
      Aria/src/main/java/com/arialyy/aria/core/upload/uploader/HttpThreadTask.java
  66. 83
      Aria/src/main/java/com/arialyy/aria/core/upload/uploader/SimpleUploadUtil.java
  67. 87
      Aria/src/main/java/com/arialyy/aria/core/upload/uploader/Uploader.java
  68. 2
      Aria/src/main/java/com/arialyy/aria/orm/DBConfig.java
  69. 15
      Aria/src/main/java/com/arialyy/aria/util/CheckUtil.java
  70. 49
      Aria/src/main/java/com/arialyy/aria/util/CommonUtil.java
  71. 314
      Aria/src/main/java/org/apache/commons/net/DatagramSocketClient.java
  72. 69
      Aria/src/main/java/org/apache/commons/net/DatagramSocketFactory.java
  73. 79
      Aria/src/main/java/org/apache/commons/net/DefaultDatagramSocketFactory.java
  74. 230
      Aria/src/main/java/org/apache/commons/net/DefaultSocketFactory.java
  75. 56
      Aria/src/main/java/org/apache/commons/net/MalformedServerReplyException.java
  76. 202
      Aria/src/main/java/org/apache/commons/net/PrintCommandListener.java
  77. 148
      Aria/src/main/java/org/apache/commons/net/ProtocolCommandEvent.java
  78. 58
      Aria/src/main/java/org/apache/commons/net/ProtocolCommandListener.java
  79. 135
      Aria/src/main/java/org/apache/commons/net/ProtocolCommandSupport.java
  80. 887
      Aria/src/main/java/org/apache/commons/net/SocketClient.java
  81. 35
      Aria/src/main/java/org/apache/commons/net/ftp/Configurable.java
  82. 1870
      Aria/src/main/java/org/apache/commons/net/ftp/FTP.java
  83. 3980
      Aria/src/main/java/org/apache/commons/net/ftp/FTPClient.java
  84. 709
      Aria/src/main/java/org/apache/commons/net/ftp/FTPClientConfig.java
  85. 116
      Aria/src/main/java/org/apache/commons/net/ftp/FTPCmd.java
  86. 171
      Aria/src/main/java/org/apache/commons/net/ftp/FTPCommand.java
  87. 55
      Aria/src/main/java/org/apache/commons/net/ftp/FTPConnectionClosedException.java
  88. 536
      Aria/src/main/java/org/apache/commons/net/ftp/FTPFile.java
  89. 134
      Aria/src/main/java/org/apache/commons/net/ftp/FTPFileEntryParser.java
  90. 79
      Aria/src/main/java/org/apache/commons/net/ftp/FTPFileEntryParserImpl.java
  91. 33
      Aria/src/main/java/org/apache/commons/net/ftp/FTPFileFilter.java
  92. 57
      Aria/src/main/java/org/apache/commons/net/ftp/FTPFileFilters.java
  93. 202
      Aria/src/main/java/org/apache/commons/net/ftp/FTPHTTPClient.java
  94. 329
      Aria/src/main/java/org/apache/commons/net/ftp/FTPListParseEngine.java
  95. 201
      Aria/src/main/java/org/apache/commons/net/ftp/FTPReply.java
  96. 925
      Aria/src/main/java/org/apache/commons/net/ftp/FTPSClient.java
  97. 52
      Aria/src/main/java/org/apache/commons/net/ftp/FTPSCommand.java
  98. 74
      Aria/src/main/java/org/apache/commons/net/ftp/FTPSServerSocketFactory.java
  99. 112
      Aria/src/main/java/org/apache/commons/net/ftp/FTPSSocketFactory.java
  100. 60
      Aria/src/main/java/org/apache/commons/net/ftp/FTPSTrustManager.java
  101. Some files were not shown because too many files have changed in this diff Show More

1
.gitignore vendored

@ -13,3 +13,4 @@
/.idea /.idea
.idea .idea
/cache /cache
*.log

@ -23,5 +23,7 @@ dependencies {
testCompile 'junit:junit:4.12' testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.1.1' compile 'com.android.support:appcompat-v7:23.1.1'
compile project(':AriaAnnotations') compile project(':AriaAnnotations')
// compile project(':AriaFtpPlug')
} }
apply from: 'bintray-release.gradle' apply from: 'bintray-release.gradle'

@ -43,7 +43,7 @@ import com.arialyy.aria.core.upload.UploadTask;
* <code> * <code>
* //下载 * //下载
* Aria.download(this) * Aria.download(this)
* .load(DOWNLOAD_URL) //下载地址,必填 * .load(URL) //下载地址,必填
* //文件保存路径,必填 * //文件保存路径,必填
* .setDownloadPath(Environment.getExternalStorageDirectory().getPath() + "/test.apk") * .setDownloadPath(Environment.getExternalStorageDirectory().getPath() + "/test.apk")
* .start(); * .start();

@ -28,6 +28,7 @@ import android.support.v4.app.Fragment;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.widget.PopupWindow; import android.widget.PopupWindow;
import com.arialyy.aria.core.common.QueueMod;
import com.arialyy.aria.core.download.DownloadReceiver; import com.arialyy.aria.core.download.DownloadReceiver;
import com.arialyy.aria.core.command.ICmd; import com.arialyy.aria.core.command.ICmd;
import com.arialyy.aria.core.inf.IReceiver; import com.arialyy.aria.core.inf.IReceiver;
@ -37,10 +38,10 @@ import com.arialyy.aria.util.CommonUtil;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory; import javax.xml.parsers.SAXParserFactory;
@ -59,7 +60,7 @@ import org.xml.sax.SAXException;
public static final String DOWNLOAD_TEMP_DIR = "/Aria/temp/download/"; public static final String DOWNLOAD_TEMP_DIR = "/Aria/temp/download/";
public static final String UPLOAD_TEMP_DIR = "/Aria/temp/upload/"; public static final String UPLOAD_TEMP_DIR = "/Aria/temp/upload/";
@SuppressLint("StaticFieldLeak") private static volatile AriaManager INSTANCE = null; @SuppressLint("StaticFieldLeak") private static volatile AriaManager INSTANCE = null;
private Map<String, IReceiver> mReceivers = new HashMap<>(); private Map<String, IReceiver> mReceivers = new ConcurrentHashMap<>();
public static Context APP; public static Context APP;
private List<ICmd> mCommands = new ArrayList<>(); private List<ICmd> mCommands = new ArrayList<>();
private Configuration.DownloadConfig mDConfig; private Configuration.DownloadConfig mDConfig;
@ -88,7 +89,7 @@ import org.xml.sax.SAXException;
/** /**
* 设置上传任务的执行队列类型 * 设置上传任务的执行队列类型
* *
* @param mod {@link com.arialyy.aria.core.QueueMod} * @param mod {@link QueueMod}
*/ */
public AriaManager setUploadQueueMod(QueueMod mod) { public AriaManager setUploadQueueMod(QueueMod mod) {
mUConfig.setQueueMod(mod.tag); mUConfig.setQueueMod(mod.tag);
@ -98,7 +99,7 @@ import org.xml.sax.SAXException;
/** /**
* 设置下载任务的执行队列类型 * 设置下载任务的执行队列类型
* *
* @param mod {@link com.arialyy.aria.core.QueueMod} * @param mod {@link QueueMod}
*/ */
public AriaManager setDownloadQueueMod(QueueMod mod) { public AriaManager setDownloadQueueMod(QueueMod mod) {
mDConfig.setQueueMod(mod.tag); mDConfig.setQueueMod(mod.tag);

@ -228,7 +228,7 @@ class ConfigHelper extends DefaultHandler {
} }
if (num < 1) { if (num < 1) {
Log.e(TAG, "下载线程数不能小于 1"); Log.e(TAG, "下载线程数不能小于 1");
num = 3; num = 1;
} }
if (isDownloadConfig) { if (isDownloadConfig) {
mDownloadConfig.threadNum = num; mDownloadConfig.threadNum = num;

@ -17,7 +17,7 @@ package com.arialyy.aria.core.command.group;
import android.text.TextUtils; import android.text.TextUtils;
import com.arialyy.aria.core.AriaManager; import com.arialyy.aria.core.AriaManager;
import com.arialyy.aria.core.QueueMod; import com.arialyy.aria.core.common.QueueMod;
import com.arialyy.aria.core.inf.AbsGroupTask; import com.arialyy.aria.core.inf.AbsGroupTask;
import com.arialyy.aria.core.inf.AbsTaskEntity; import com.arialyy.aria.core.inf.AbsTaskEntity;
import com.arialyy.aria.core.inf.IEntity; import com.arialyy.aria.core.inf.IEntity;

@ -18,7 +18,7 @@ package com.arialyy.aria.core.command.normal;
import android.text.TextUtils; import android.text.TextUtils;
import com.arialyy.aria.core.AriaManager; import com.arialyy.aria.core.AriaManager;
import com.arialyy.aria.core.QueueMod; import com.arialyy.aria.core.common.QueueMod;
import com.arialyy.aria.core.inf.AbsTask; import com.arialyy.aria.core.inf.AbsTask;
import com.arialyy.aria.core.inf.IEntity; import com.arialyy.aria.core.inf.IEntity;
import com.arialyy.aria.core.inf.AbsTaskEntity; import com.arialyy.aria.core.inf.AbsTaskEntity;

@ -0,0 +1,398 @@
/*
* 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.content.Context;
import android.util.Log;
import android.util.SparseArray;
import com.arialyy.aria.core.AriaManager;
import com.arialyy.aria.core.download.DownloadTaskEntity;
import com.arialyy.aria.core.inf.AbsNormalEntity;
import com.arialyy.aria.core.inf.AbsTaskEntity;
import com.arialyy.aria.core.inf.IDownloadListener;
import com.arialyy.aria.core.inf.IEventListener;
import com.arialyy.aria.core.upload.UploadTaskEntity;
import com.arialyy.aria.util.CommonUtil;
import java.io.File;
import java.util.Properties;
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/7/1.
* 文件下载器
*/
public abstract class AbsFileer<ENTITY extends AbsNormalEntity, TASK_ENTITY extends AbsTaskEntity<ENTITY>>
implements Runnable, IUtil {
private final String TAG = "Downloader";
protected IEventListener mListener;
protected TASK_ENTITY mTaskEntity;
protected ENTITY mEntity;
protected File mConfigFile;//信息配置文件
protected Context mContext;
protected File mTempFile; //下载的文件
protected boolean isNewTask = true;
protected StateConstance mConstance;
private ExecutorService mFixedThreadPool;
private int mThreadNum, mRealThreadNum;
private SparseArray<AbsThreadTask> mTask = new SparseArray<>();
/**
* 小于1m的文件不启用多线程
*/
private static final long SUB_LEN = 1024 * 1024;
private Timer mTimer;
protected AbsFileer(IEventListener listener, TASK_ENTITY taskEntity) {
mListener = listener;
mTaskEntity = taskEntity;
mEntity = mTaskEntity.getEntity();
mContext = AriaManager.APP;
mConstance = new StateConstance();
}
@Override public void setMaxSpeed(double maxSpeed) {
for (int i = 0; i < mThreadNum; i++) {
AbsThreadTask task = mTask.get(i);
if (task != null) {
task.setMaxSpeed(maxSpeed);
}
}
}
public StateConstance getConstance() {
return mConstance;
}
@Override public void run() {
startFlow();
}
/**
* 开始下载流程
*/
private void startFlow() {
checkTask();
if (mListener instanceof IDownloadListener) {
((IDownloadListener) mListener).onPostPre(mEntity.getFileSize());
}
mConstance.cleanState();
mConstance.isRunning = true;
if (!mTaskEntity.isSupportBP) {
mThreadNum = 1;
mConstance.THREAD_NUM = mThreadNum;
handleNoSupportBP();
} else {
mThreadNum = isNewTask ? (getNewTaskThreadNum()) : mRealThreadNum;
mConstance.THREAD_NUM = mThreadNum;
handleBreakpoint();
}
startTimer();
}
/**
* 设置新任务的最大线程数
*/
protected int getNewTaskThreadNum() {
return mEntity.getFileSize() <= SUB_LEN || mTaskEntity.requestType == AbsTaskEntity.FTP_DIR ? 1
: AriaManager.getInstance(mContext).getDownloadConfig().getThreadNum();
}
/**
* 启动进度获取定时器
*/
private void startTimer() {
mTimer = new Timer(true);
mTimer.schedule(new TimerTask() {
@Override public void run() {
if (mConstance.isComplete() || !mConstance.isRunning) {
closeTimer();
} else if (mConstance.CURRENT_LOCATION >= 0) {
mListener.onProgress(mConstance.CURRENT_LOCATION);
}
}
}, 0, 1000);
}
private void closeTimer() {
if (mTimer != null) {
mTimer.purge();
mTimer.cancel();
}
}
@Override public long getFileSize() {
return mEntity.getFileSize();
}
/**
* 获取当前下载位置
*/
@Override public long getCurrentLocation() {
return mConstance.CURRENT_LOCATION;
}
@Override public boolean isRunning() {
return mConstance.isRunning;
}
@Override public void cancel() {
closeTimer();
mConstance.isCancel = true;
mConstance.isRunning = false;
if (mFixedThreadPool != null) {
mFixedThreadPool.shutdown();
}
for (int i = 0; i < mThreadNum; i++) {
AbsThreadTask task = mTask.get(i);
if (task != null) {
task.cancel();
}
}
if (mTaskEntity instanceof DownloadTaskEntity) {
CommonUtil.delDownloadTaskConfig(mTaskEntity.removeFile, (DownloadTaskEntity) mTaskEntity);
} else if (mTaskEntity instanceof UploadTaskEntity) {
CommonUtil.delUploadTaskConfig(mTaskEntity.removeFile, (UploadTaskEntity) mTaskEntity);
}
}
@Override public void stop() {
closeTimer();
if (mConstance.isComplete()) return;
mConstance.isStop = true;
mConstance.isRunning = false;
if (mFixedThreadPool != null) {
mFixedThreadPool.shutdown();
}
for (int i = 0; i < mThreadNum; i++) {
AbsThreadTask task = mTask.get(i);
if (task != null) {
task.stop();
}
}
}
/**
* 直接调用的时候会自动启动线程执行
*/
@Override public void start() {
new Thread(this).start();
}
@Override public void resume() {
start();
}
/**
* 返回该下载器的
*/
public IEventListener getListener() {
return mListener;
}
/**
* 检查任务是否是新任务新任务条件
* 1文件不存在
* 2下载记录文件不存在
* 3下载记录文件缺失或不匹配
* 4数据库记录不存在
* 5不支持断点则是新任务
*/
protected abstract void checkTask();
/**
* 检查记录文件如果是新任务返回{@code true}否则返回{@code false}
*/
protected boolean checkConfigFile() {
Properties pro = CommonUtil.loadConfig(mConfigFile);
if (pro.isEmpty()) {
return true;
}
Set<Object> keys = pro.keySet();
int num = 0;
for (Object key : keys) {
if (String.valueOf(key).contains("_record_")) {
num++;
}
}
if (num == 0) {
return true;
}
mRealThreadNum = num;
for (int i = 0; i < mRealThreadNum; i++) {
if (pro.getProperty(mTempFile.getName() + "_record_" + i) == null) {
Object state = pro.getProperty(mTempFile.getName() + "_state_" + i);
if (state != null && Integer.parseInt(state + "") == 1) {
continue;
}
return true;
}
}
return false;
}
/**
* 恢复记录地址
*
* @return true 表示下载完成
*/
private boolean resumeRecordLocation(int i, long startL, long endL) {
mConstance.CURRENT_LOCATION += endL - startL;
Log.d(TAG, "++++++++++ 线程_" + i + "_已经下载完成 ++++++++++");
mConstance.COMPLETE_THREAD_NUM++;
mConstance.STOP_NUM++;
mConstance.CANCEL_NUM++;
if (mConstance.isComplete()) {
if (mConfigFile.exists()) {
mConfigFile.delete();
}
mListener.onComplete();
mConstance.isRunning = false;
return true;
}
return false;
}
/**
* 启动断点任务时创建单线程任务
*
* @param i 线程id
* @param startL 该任务起始位置
* @param endL 该任务结束位置
* @param fileLength 该任务需要处理的文件长度
*/
private AbsThreadTask createSingThreadTask(int i, long startL, long endL, long fileLength) {
SubThreadConfig<TASK_ENTITY> config = new SubThreadConfig<>();
config.FILE_SIZE = fileLength;
config.URL = mEntity.isRedirect() ? mEntity.getRedirectUrl() : mEntity.getUrl();
config.TEMP_FILE = mTempFile;
config.THREAD_ID = i;
config.START_LOCATION = startL;
config.END_LOCATION = endL;
config.CONFIG_FILE_PATH = mConfigFile.getPath();
config.SUPPORT_BP = mTaskEntity.isSupportBP;
config.TASK_ENTITY = mTaskEntity;
return selectThreadTask(config);
}
/**
* 处理断点
*/
private void handleBreakpoint() {
long fileLength = mEntity.getFileSize();
Properties pro = CommonUtil.loadConfig(mConfigFile);
int blockSize = (int) (fileLength / mThreadNum);
int[] recordL = new int[mThreadNum];
for (int i = 0; i < mThreadNum; i++) {
recordL[i] = -1;
}
int rl = 0;
if (isNewTask) {
handleNewTask();
}
for (int i = 0; i < mThreadNum; i++) {
long startL = i * blockSize, endL = (i + 1) * blockSize;
Object state = pro.getProperty(mTempFile.getName() + "_state_" + i);
if (state != null && Integer.parseInt(state + "") == 1) { //该线程已经完成
if (resumeRecordLocation(i, startL, endL)) return;
continue;
}
//分配下载位置
Object record = pro.getProperty(mTempFile.getName() + "_record_" + i);
//如果有记录,则恢复下载
if (!isNewTask && record != null && Long.parseLong(record + "") >= 0) {
Long r = Long.parseLong(record + "");
mConstance.CURRENT_LOCATION += r - startL;
Log.d(TAG, "任务【" + mEntity.getFileName() + "】线程__" + i + "__恢复下载");
startL = r;
recordL[rl] = i;
rl++;
} else {
recordL[rl] = i;
rl++;
}
if (i == (mThreadNum - 1)) {
//最后一个线程的结束位置即为文件的总长度
endL = fileLength;
}
AbsThreadTask task = createSingThreadTask(i, startL, endL, fileLength);
if (task == null) return;
mTask.put(i, task);
}
startSingleTask(recordL);
}
/**
* 启动单线程下载任务
*/
private void startSingleTask(int[] recordL) {
if (mConstance.CURRENT_LOCATION > 0) {
mListener.onResume(mConstance.CURRENT_LOCATION);
} else {
mListener.onStart(mConstance.CURRENT_LOCATION);
}
mFixedThreadPool = Executors.newFixedThreadPool(recordL.length);
for (int l : recordL) {
if (l == -1) continue;
Runnable task = mTask.get(l);
if (task != null) {
mFixedThreadPool.execute(task);
}
}
}
/**
* 处理新任务
*/
protected abstract void handleNewTask();
/**
* 处理不支持断点的下载
*/
private void handleNoSupportBP() {
SubThreadConfig<TASK_ENTITY> config = new SubThreadConfig<>();
config.FILE_SIZE = mEntity.getFileSize();
config.URL = mEntity.isRedirect() ? mEntity.getRedirectUrl() : mEntity.getUrl();
config.TEMP_FILE = mTempFile;
config.THREAD_ID = 0;
config.START_LOCATION = 0;
config.END_LOCATION = config.FILE_SIZE;
config.CONFIG_FILE_PATH = mConfigFile.getPath();
config.SUPPORT_BP = mTaskEntity.isSupportBP;
config.TASK_ENTITY = mTaskEntity;
AbsThreadTask task = selectThreadTask(config);
if (task == null) return;
mTask.put(0, task);
mFixedThreadPool = Executors.newFixedThreadPool(1);
mFixedThreadPool.execute(task);
mListener.onStart(0);
}
/**
* 选择单任务线程的类型
*/
protected abstract AbsThreadTask selectThreadTask(SubThreadConfig<TASK_ENTITY> config);
protected void failDownload(String errorMsg) {
closeTimer();
Log.e(TAG, errorMsg);
mConstance.isRunning = false;
mListener.onFail();
}
}

@ -0,0 +1,204 @@
/*
* 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.Build;
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.AbsTaskEntity;
import com.arialyy.aria.core.inf.IEventListener;
import com.arialyy.aria.core.upload.UploadEntity;
import com.arialyy.aria.util.CommonUtil;
import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.Properties;
/**
* Created by lyy on 2017/1/18.
* 下载线程
*/
public abstract class AbsThreadTask<ENTITY extends AbsEntity, TASK_ENTITY extends AbsTaskEntity<ENTITY>>
implements Runnable {
private final String TAG = "AbsThreadTask";
protected long mChildCurrentLocation = 0, mSleepTime = 0;
protected int mBufSize;
protected String mConfigFPath;
protected IEventListener mListener;
protected StateConstance STATE;
protected SubThreadConfig<TASK_ENTITY> mConfig;
protected ENTITY mEntity;
protected TASK_ENTITY mTaskEntity;
/**
* FTP 服务器编码
*/
public static String SERVER_CHARSET = "ISO-8859-1";
protected AbsThreadTask(StateConstance constance, IEventListener listener,
SubThreadConfig<TASK_ENTITY> info) {
AriaManager manager = AriaManager.getInstance(AriaManager.APP);
STATE = constance;
STATE.CONNECT_TIME_OUT = manager.getDownloadConfig().getConnectTimeOut();
STATE.READ_TIME_OUT = manager.getDownloadConfig().getIOTimeOut();
mListener = listener;
mConfig = info;
mTaskEntity = mConfig.TASK_ENTITY;
mEntity = mTaskEntity.getEntity();
if (mConfig.SUPPORT_BP) {
mConfigFPath = info.CONFIG_FILE_PATH;
}
mBufSize = manager.getDownloadConfig().getBuffSize();
setMaxSpeed(AriaManager.getInstance(AriaManager.APP).getDownloadConfig().getMsxSpeed());
}
public void setMaxSpeed(double maxSpeed) {
if (-0.9999 < maxSpeed && maxSpeed < 0.00001) {
mSleepTime = 0;
} else {
BigDecimal db = new BigDecimal(
((mBufSize / 1024) * (filterVersion() ? 1 : STATE.THREAD_NUM) / maxSpeed) * 1000);
mSleepTime = db.setScale(0, BigDecimal.ROUND_HALF_UP).longValue();
}
}
private boolean filterVersion() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
}
/**
* 停止下载
*/
public void stop() {
synchronized (AriaManager.LOCK) {
try {
if (mConfig.SUPPORT_BP) {
STATE.STOP_NUM++;
Log.d(TAG, "任务【"
+ mConfig.TEMP_FILE.getName()
+ "】thread__"
+ mConfig.THREAD_ID
+ "__停止, stop location ==> "
+ mChildCurrentLocation);
writeConfig(false, mChildCurrentLocation);
if (STATE.isStop()) {
Log.d(TAG, "任务【" + mConfig.TEMP_FILE.getName() + "】已停止");
STATE.isRunning = false;
mListener.onStop(STATE.CURRENT_LOCATION);
}
} else {
Log.d(TAG, "任务【" + mConfig.TEMP_FILE.getName() + "】已停止");
STATE.isRunning = false;
mListener.onStop(STATE.CURRENT_LOCATION);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 下载中
*/
protected void progress(long len) {
synchronized (AriaManager.LOCK) {
mChildCurrentLocation += len;
STATE.CURRENT_LOCATION += len;
}
}
/**
* 取消下载
*/
public void cancel() {
synchronized (AriaManager.LOCK) {
if (mConfig.SUPPORT_BP) {
STATE.CANCEL_NUM++;
Log.d(TAG,
"任务【" + mConfig.TEMP_FILE.getName() + "】thread__" + mConfig.THREAD_ID + "__取消下载");
if (STATE.isCancel()) {
File configFile = new File(mConfigFPath);
if (configFile.exists()) {
configFile.delete();
}
if (mConfig.TEMP_FILE.exists() && !(mEntity instanceof UploadEntity)) {
mConfig.TEMP_FILE.delete();
}
Log.d(TAG, "任务【" + mConfig.TEMP_FILE.getName() + "】已取消");
STATE.isRunning = false;
mListener.onCancel();
}
} else {
Log.d(TAG, "任务【" + mConfig.TEMP_FILE.getName() + "】已取消");
STATE.isRunning = false;
mListener.onCancel();
}
}
}
/**
* 任务失败
*/
protected void fail(long currentLocation, String msg, Exception ex) {
synchronized (AriaManager.LOCK) {
try {
STATE.FAIL_NUM++;
STATE.isRunning = false;
STATE.isStop = true;
if (ex != null) {
Log.e(TAG, msg + "\n" + CommonUtil.getPrintException(ex));
} else {
Log.e(TAG, msg);
}
if (mConfig.SUPPORT_BP) {
writeConfig(false, currentLocation);
if (STATE.isFail()) {
Log.e(TAG, "任务【" + mConfig.TEMP_FILE.getName() + "】执行失败");
mListener.onFail();
}
} else {
Log.e(TAG, "任务【" + mConfig.TEMP_FILE.getName() + "】执行失败");
mListener.onFail();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 将记录写入到配置文件
*/
protected void writeConfig(boolean isComplete, long record) throws IOException {
synchronized (AriaManager.LOCK) {
String key = null, value = null;
if (0 < record && record < mConfig.END_LOCATION) {
key = mConfig.TEMP_FILE.getName() + "_record_" + mConfig.THREAD_ID;
value = String.valueOf(record);
} else if (record >= mConfig.END_LOCATION || isComplete) {
key = mConfig.TEMP_FILE.getName() + "_state_" + mConfig.THREAD_ID;
value = "1";
}
if (!TextUtils.isEmpty(key) && !TextUtils.isEmpty(value)) {
File configFile = new File(mConfigFPath);
Properties pro = CommonUtil.loadConfig(configFile);
pro.setProperty(key, value);
CommonUtil.saveConfig(configFile, pro);
}
}
}
}

@ -14,13 +14,13 @@
* limitations under the License. * limitations under the License.
*/ */
package com.arialyy.aria.core.download.downloader; package com.arialyy.aria.core.common;
/** /**
* Created by lyy on 2016/10/31. * Created by lyy on 2016/10/31.
* 抽象的下载接口 * 抽象的下载接口
*/ */
public interface IDownloadUtil { public interface IUtil {
/** /**
* 获取文件大小 * 获取文件大小
@ -37,25 +37,30 @@ public interface IDownloadUtil {
* *
* @return true, 正在下载 * @return true, 正在下载
*/ */
boolean isDownloading(); boolean isRunning();
/** /**
* 取消下载 * 取消下载
*/ */
void cancelDownload(); void cancel();
/** /**
* 停止下载 * 停止下载
*/ */
void stopDownload(); void stop();
/** /**
* 开始下载 * 开始下载
*/ */
void startDownload(); void start();
/** /**
* 从上次断点恢复下载 * 从上次断点恢复下载
*/ */
void resumeDownload(); void resume();
/**
* 设置最大下载速度
*/
void setMaxSpeed(double maxSpeed);
} }

@ -13,8 +13,9 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.arialyy.aria.core; package com.arialyy.aria.core.common;
import com.arialyy.aria.core.AriaManager;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Set; import java.util.Set;

@ -1,4 +1,4 @@
package com.arialyy.aria.core; package com.arialyy.aria.core.common;
/** /**
* Created by Aria.Lao on 2017/6/21. * Created by Aria.Lao on 2017/6/21.
@ -19,7 +19,7 @@ public enum QueueMod {
*/ */
NOW("now"); NOW("now");
String tag; public String tag;
public String getTag() { public String getTag() {
return tag; return tag;

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.arialyy.aria.core; package com.arialyy.aria.core.common;
/** /**
* Created by lyy on 2017/1/23. * Created by lyy on 2017/1/23.

@ -13,32 +13,32 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.arialyy.aria.core.download.downloader; package com.arialyy.aria.core.common;
/** /**
* Created by lyy on 2017/1/18. * Created by lyy on 2017/1/18.
* 下载状态常量 * 下载状态常量
*/ */
final class StateConstance { public class StateConstance {
int CANCEL_NUM = 0; public int CANCEL_NUM = 0;
int STOP_NUM = 0; public int STOP_NUM = 0;
int FAIL_NUM = 0; public int FAIL_NUM = 0;
int CONNECT_TIME_OUT; //连接超时时间 public int CONNECT_TIME_OUT; //连接超时时间
int READ_TIME_OUT; //流读取的超时时间 public int READ_TIME_OUT; //流读取的超时时间
int COMPLETE_THREAD_NUM = 0; public int COMPLETE_THREAD_NUM = 0;
int THREAD_NUM; public int THREAD_NUM;
long CURRENT_LOCATION = 0; public long CURRENT_LOCATION = 0;
boolean isDownloading = false; public boolean isRunning = false;
boolean isCancel = false; public boolean isCancel = false;
boolean isStop = false; public boolean isStop = false;
StateConstance() { public StateConstance() {
} }
void cleanState() { public void cleanState() {
isCancel = false; isCancel = false;
isStop = false; isStop = false;
isDownloading = true; isRunning = true;
CURRENT_LOCATION = 0; CURRENT_LOCATION = 0;
CANCEL_NUM = 0; CANCEL_NUM = 0;
STOP_NUM = 0; STOP_NUM = 0;
@ -48,28 +48,28 @@ final class StateConstance {
/** /**
* 所有子线程是否都已经停止下载 * 所有子线程是否都已经停止下载
*/ */
boolean isStop() { public boolean isStop() {
return STOP_NUM == THREAD_NUM; return STOP_NUM == THREAD_NUM;
} }
/** /**
* 所有子线程是否都已经下载失败 * 所有子线程是否都已经下载失败
*/ */
boolean isFail() { public boolean isFail() {
return FAIL_NUM == THREAD_NUM; return FAIL_NUM == THREAD_NUM;
} }
/** /**
* 所有子线程是否都已经完成下载 * 所有子线程是否都已经完成下载
*/ */
boolean isComplete() { public boolean isComplete() {
return COMPLETE_THREAD_NUM == THREAD_NUM; return COMPLETE_THREAD_NUM == THREAD_NUM;
} }
/** /**
* 所有子线程是否都已经取消下载 * 所有子线程是否都已经取消下载
*/ */
boolean isCancel() { public boolean isCancel() {
return CANCEL_NUM == THREAD_NUM; return CANCEL_NUM == THREAD_NUM;
} }
} }

@ -0,0 +1,25 @@
package com.arialyy.aria.core.common;
import com.arialyy.aria.core.inf.AbsTaskEntity;
import java.io.File;
/**
* 子线程下载信息类
*/
public class SubThreadConfig<TASK_ENTITY extends AbsTaskEntity> {
//线程Id
public int THREAD_ID;
//下载文件大小
public long FILE_SIZE;
//子线程启动下载位置
public long START_LOCATION;
//子线程结束下载位置
public long END_LOCATION;
//下载文件或上传的文件路径
public File TEMP_FILE;
//服务器地址
public String URL;
public String CONFIG_FILE_PATH;
public TASK_ENTITY TASK_ENTITY;
public boolean SUPPORT_BP = true;
}

@ -16,9 +16,11 @@
package com.arialyy.aria.core.download; package com.arialyy.aria.core.download;
import android.os.Handler; import android.os.Handler;
import android.util.Log;
import com.arialyy.aria.core.AriaManager; import com.arialyy.aria.core.AriaManager;
import com.arialyy.aria.core.inf.AbsEntity; import com.arialyy.aria.core.inf.AbsEntity;
import com.arialyy.aria.core.inf.AbsTask; import com.arialyy.aria.core.inf.AbsTask;
import com.arialyy.aria.core.inf.IDownloadListener;
import com.arialyy.aria.core.inf.IEntity; import com.arialyy.aria.core.inf.IEntity;
import com.arialyy.aria.core.inf.IEventListener; import com.arialyy.aria.core.inf.IEventListener;
import com.arialyy.aria.core.scheduler.ISchedulers; import com.arialyy.aria.core.scheduler.ISchedulers;
@ -28,8 +30,8 @@ import java.lang.ref.WeakReference;
/** /**
* 下载监听类 * 下载监听类
*/ */
class BaseListener<ENTITY extends AbsEntity, TASK extends AbsTask<ENTITY>> class BaseDListener<ENTITY extends AbsEntity, TASK extends AbsTask<ENTITY>>
implements IEventListener { implements IDownloadListener {
private WeakReference<Handler> outHandler; private WeakReference<Handler> outHandler;
private long mLastLen = 0; //上一次发送长度 private long mLastLen = 0; //上一次发送长度
private boolean isFirst = true; private boolean isFirst = true;
@ -38,7 +40,7 @@ class BaseListener<ENTITY extends AbsEntity, TASK extends AbsTask<ENTITY>>
private boolean isConvertSpeed = false; private boolean isConvertSpeed = false;
boolean isWait = false; boolean isWait = false;
BaseListener(TASK task, Handler outHandler) { BaseDListener(TASK task, Handler outHandler) {
this.outHandler = new WeakReference<>(outHandler); this.outHandler = new WeakReference<>(outHandler);
this.mTask = new WeakReference<>(task).get(); this.mTask = new WeakReference<>(task).get();
this.mEntity = this.mTask.getEntity(); this.mEntity = this.mTask.getEntity();
@ -59,6 +61,10 @@ class BaseListener<ENTITY extends AbsEntity, TASK extends AbsTask<ENTITY>>
sendInState2Target(ISchedulers.POST_PRE); sendInState2Target(ISchedulers.POST_PRE);
} }
@Override public void supportBreakpoint(boolean support) {
}
@Override public void onStart(long startLocation) { @Override public void onStart(long startLocation) {
saveData(IEntity.STATE_RUNNING, startLocation); saveData(IEntity.STATE_RUNNING, startLocation);
sendInState2Target(ISchedulers.START); sendInState2Target(ISchedulers.START);

@ -0,0 +1,202 @@
/*
* Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.arialyy.aria.core.download;
import android.text.TextUtils;
import com.arialyy.aria.core.inf.AbsDownloadTarget;
import com.arialyy.aria.core.inf.AbsTarget;
import com.arialyy.aria.core.inf.AbsTaskEntity;
import com.arialyy.aria.orm.DbEntity;
import com.arialyy.aria.util.CommonUtil;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Aria.Lao on 2017/7/26.
*/
abstract class BaseGroupTarget<TARGET extends AbsTarget, TASK_ENTITY extends AbsTaskEntity>
extends AbsDownloadTarget<TARGET, DownloadGroupEntity, TASK_ENTITY> {
List<String> mUrls = new ArrayList<>();
String mGroupName;
/**
* 子任务文件名
*/
private List<String> mSubTaskFileName = new ArrayList<>();
/**
* 是否已经设置了文件路径
*/
private boolean isSetDirPathed = false;
/**
* 查询任务组实体如果数据库不存在该实体则新创建一个新的任务组实体
*/
DownloadGroupEntity getDownloadGroupEntity() {
DownloadGroupEntity entity =
DbEntity.findFirst(DownloadGroupEntity.class, "groupName=?", mGroupName);
if (entity == null) {
entity = new DownloadGroupEntity();
entity.setGroupName(mGroupName);
entity.setUrls(mUrls);
entity.insert();
}
return entity;
}
/**
* 设置任务组别名
*/
public TARGET setGroupAlias(String alias) {
if (TextUtils.isEmpty(alias)) return (TARGET) this;
mEntity.setAlias(alias);
mEntity.update();
return (TARGET) this;
}
/**
* 设置任务组的文件夹路径在Aria中任务组的所有子任务都会下载到以任务组组名的文件夹中
* groupDirPath = "/mnt/sdcard/download/group_test"
* <pre>
* {@code
* + mnt
* + sdcard
* + download
* + group_test
* - task1.apk
* - task2.apk
* - task3.apk
* ....
*
* }
* </pre>
*
* @param groupDirPath 任务组保存文件夹路径
*/
public TARGET setDownloadDirPath(String groupDirPath) {
if (TextUtils.isEmpty(groupDirPath)) {
throw new NullPointerException("任务组文件夹保存路径不能为null");
}
isSetDirPathed = true;
if (mEntity.getDirPath().equals(groupDirPath)) return (TARGET) this;
File file = new File(groupDirPath);
if (file.exists() && file.isFile()) {
throw new IllegalArgumentException("路径不能为文件");
}
if (!file.exists()) {
file.mkdirs();
}
mEntity.setDirPath(groupDirPath);
if (!TextUtils.isEmpty(mEntity.getDirPath())) {
reChangeDirPath(groupDirPath);
} else {
mEntity.setSubTasks(createSubTask());
}
mEntity.update();
return (TARGET) this;
}
/**
* 改变任务组文件夹路径修改文件夹路径会将子任务所有路径更换
*
* @param newDirPath 新的文件夹路径
*/
private void reChangeDirPath(String newDirPath) {
List<DownloadEntity> subTask = mEntity.getSubTask();
if (subTask != null && !subTask.isEmpty()) {
for (DownloadEntity entity : subTask) {
String oldPath = entity.getDownloadPath();
String newPath = newDirPath + "/" + entity.getFileName();
File file = new File(oldPath);
file.renameTo(new File(newPath));
DbEntity.exeSql("UPDATE DownloadEntity SET downloadPath='"
+ newPath
+ "' WHERE downloadPath='"
+ oldPath
+ "'");
DbEntity.exeSql(
"UPDATE DownloadTaskEntity SET key='" + newPath + "' WHERE key='" + oldPath + "'");
}
} else {
mEntity.setSubTasks(createSubTask());
}
}
/**
* 设置子任务文件名该方法必须在{@link #setDownloadDirPath(String)}之后调用否则不生效
*/
public TARGET setSubTaskFileName(List<String> subTaskFileName) {
if (subTaskFileName == null || subTaskFileName.isEmpty()) return (TARGET) this;
mSubTaskFileName.addAll(subTaskFileName);
if (mUrls.size() != subTaskFileName.size()) {
throw new IllegalArgumentException("下载链接数必须要和保存路径的数量一致");
}
if (isSetDirPathed) {
List<DownloadEntity> entities = mEntity.getSubTask();
int i = 0;
for (DownloadEntity entity : entities) {
String newName = mSubTaskFileName.get(i);
updateSubFileName(entity, newName);
i++;
}
}
return (TARGET) this;
}
/**
* 更新子任务文件名
*/
private void updateSubFileName(DownloadEntity entity, String newName) {
if (!newName.equals(entity.getFileName())) {
String oldPath = mEntity.getDirPath() + "/" + entity.getFileName();
String newPath = mEntity.getDirPath() + "/" + newName;
File oldFile = new File(oldPath);
if (oldFile.exists()) {
oldFile.renameTo(new File(newPath));
}
CommonUtil.renameDownloadConfig(oldFile.getName(), newName);
DbEntity.exeSql(
"UPDATE DownloadTaskEntity SET key='" + newPath + "' WHERE key='" + oldPath + "'");
entity.setDownloadPath(newPath);
entity.setFileName(newName);
entity.update();
}
}
/**
* 创建子任务
*/
private List<DownloadEntity> createSubTask() {
List<DownloadEntity> list = new ArrayList<>();
for (int i = 0, len = mUrls.size(); i < len; i++) {
DownloadEntity entity = new DownloadEntity();
entity.setUrl(mUrls.get(i));
String fileName = mSubTaskFileName.isEmpty() ? createFileName(entity.getUrl())
: mSubTaskFileName.get(i);
entity.setDownloadPath(mEntity.getDirPath() + "/" + fileName);
entity.setGroupName(mGroupName);
entity.setGroupChild(true);
entity.setFileName(fileName);
entity.insert();
list.add(entity);
}
return list;
}
}

@ -27,10 +27,7 @@ import com.arialyy.aria.orm.Primary;
* 下载实体 * 下载实体
*/ */
public class DownloadEntity extends AbsNormalEntity implements Parcelable { public class DownloadEntity extends AbsNormalEntity implements Parcelable {
private String downloadUrl = ""; //下载路径
@Primary private String downloadPath = ""; //保存路径 @Primary private String downloadPath = ""; //保存路径
private boolean isRedirect = false; //是否重定向
private String redirectUrl = ""; //重定向链接
/** /**
* 所属任务组 * 所属任务组
@ -54,40 +51,12 @@ public class DownloadEntity extends AbsNormalEntity implements Parcelable {
private String serverFileName = ""; private String serverFileName = "";
@Override public String getKey() { @Override public String getKey() {
return downloadUrl; return getUrl();
} }
public DownloadEntity() { public DownloadEntity() {
} }
@Override public String toString() {
return "DownloadEntity{"
+ "downloadUrl='"
+ downloadUrl
+ '\''
+ ", downloadPath='"
+ downloadPath
+ '\''
+ ", isRedirect="
+ isRedirect
+ ", redirectUrl='"
+ redirectUrl
+ '\''
+ ", groupName='"
+ groupName
+ '\''
+ ", md5Code='"
+ md5Code
+ '\''
+ ", disposition='"
+ disposition
+ '\''
+ ", serverFileName='"
+ serverFileName
+ '\''
+ '}';
}
public String getMd5Code() { public String getMd5Code() {
return md5Code; return md5Code;
} }
@ -120,13 +89,11 @@ public class DownloadEntity extends AbsNormalEntity implements Parcelable {
this.groupName = groupName; this.groupName = groupName;
} }
public String getDownloadUrl() { /**
return downloadUrl; * {@link #getUrl()}
} */
@Deprecated public String getDownloadUrl() {
public DownloadEntity setDownloadUrl(String downloadUrl) { return getUrl();
this.downloadUrl = downloadUrl;
return this;
} }
public String getDownloadPath() { public String getDownloadPath() {
@ -142,44 +109,42 @@ public class DownloadEntity extends AbsNormalEntity implements Parcelable {
return (DownloadEntity) super.clone(); return (DownloadEntity) super.clone();
} }
public boolean isRedirect() {
return isRedirect;
}
public void setRedirect(boolean redirect) {
isRedirect = redirect;
}
public String getRedirectUrl() {
return redirectUrl;
}
public void setRedirectUrl(String redirectUrl) {
this.redirectUrl = redirectUrl;
}
@Override public int describeContents() { @Override public int describeContents() {
return 0; return 0;
} }
@Override public void writeToParcel(Parcel dest, int flags) { @Override public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags); super.writeToParcel(dest, flags);
dest.writeString(this.downloadUrl);
dest.writeString(this.downloadPath); dest.writeString(this.downloadPath);
dest.writeByte(this.isRedirect ? (byte) 1 : (byte) 0);
dest.writeString(this.redirectUrl);
dest.writeString(this.groupName); dest.writeString(this.groupName);
dest.writeString(this.md5Code); dest.writeString(this.md5Code);
dest.writeString(this.disposition); dest.writeString(this.disposition);
dest.writeString(this.serverFileName); dest.writeString(this.serverFileName);
} }
@Override public String toString() {
return "DownloadEntity{"
+ "downloadPath='"
+ downloadPath
+ '\''
+ ", groupName='"
+ groupName
+ '\''
+ ", md5Code='"
+ md5Code
+ '\''
+ ", disposition='"
+ disposition
+ '\''
+ ", serverFileName='"
+ serverFileName
+ '\''
+ '}';
}
protected DownloadEntity(Parcel in) { protected DownloadEntity(Parcel in) {
super(in); super(in);
this.downloadUrl = in.readString();
this.downloadPath = in.readString(); this.downloadPath = in.readString();
this.isRedirect = in.readByte() != 0;
this.redirectUrl = in.readString();
this.groupName = in.readString(); this.groupName = in.readString();
this.md5Code = in.readString(); this.md5Code = in.readString();
this.disposition = in.readString(); this.disposition = in.readString();

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

@ -16,14 +16,13 @@
package com.arialyy.aria.core.download; package com.arialyy.aria.core.download;
import android.os.Handler; import android.os.Handler;
import android.util.Log;
import com.arialyy.aria.core.download.downloader.IDownloadGroupListener; import com.arialyy.aria.core.download.downloader.IDownloadGroupListener;
/** /**
* Created by Aria.Lao on 2017/7/20. * Created by Aria.Lao on 2017/7/20.
* 任务组下载事件 * 任务组下载事件
*/ */
class DownloadGroupListener extends BaseListener<DownloadGroupEntity, DownloadGroupTask> class DownloadGroupListener extends BaseDListener<DownloadGroupEntity, DownloadGroupTask>
implements IDownloadGroupListener { implements IDownloadGroupListener {
private final String TAG = "DownloadGroupListener"; private final String TAG = "DownloadGroupListener";

@ -15,32 +15,18 @@
*/ */
package com.arialyy.aria.core.download; package com.arialyy.aria.core.download;
import android.text.TextUtils;
import com.arialyy.aria.core.inf.AbsDownloadTarget;
import com.arialyy.aria.core.inf.AbsUploadTarget;
import com.arialyy.aria.orm.DbEntity; import com.arialyy.aria.orm.DbEntity;
import com.arialyy.aria.util.CheckUtil; import com.arialyy.aria.util.CheckUtil;
import com.arialyy.aria.util.CommonUtil; import com.arialyy.aria.util.CommonUtil;
import java.io.File;
import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
* Created by AriaL on 2017/6/29. * Created by AriaL on 2017/6/29.
* 下载任务组
*/ */
public class DownloadGroupTarget public class DownloadGroupTarget
extends AbsDownloadTarget<DownloadGroupTarget, DownloadGroupEntity, DownloadGroupTaskEntity> { extends BaseGroupTarget<DownloadGroupTarget, DownloadGroupTaskEntity> {
private List<String> mUrls = new ArrayList<>();
private final String TAG = "DownloadGroupTarget"; private final String TAG = "DownloadGroupTarget";
/**
* 子任务文件名
*/
private List<String> mSubTaskFileName = new ArrayList<>();
private String mGroupName;
/**
* 是否已经设置了文件路径
*/
private boolean isSetDirPathed = false;
DownloadGroupTarget(DownloadGroupEntity groupEntity, String targetName) { DownloadGroupTarget(DownloadGroupEntity groupEntity, String targetName) {
this.mTargetName = targetName; this.mTargetName = targetName;
@ -71,31 +57,6 @@ public class DownloadGroupTarget
mEntity = mTaskEntity.entity; mEntity = mTaskEntity.entity;
} }
/**
* 查询任务组实体如果数据库不存在该实体则新创建一个新的任务组实体
*/
private DownloadGroupEntity getDownloadGroupEntity() {
DownloadGroupEntity entity =
DbEntity.findFirst(DownloadGroupEntity.class, "groupName=?", mGroupName);
if (entity == null) {
entity = new DownloadGroupEntity();
entity.setGroupName(mGroupName);
entity.setUrls(mUrls);
entity.insert();
}
return entity;
}
/**
* 设置任务组别名
*/
public DownloadGroupTarget setGroupAlias(String alias) {
if (TextUtils.isEmpty(alias)) return this;
mEntity.setAlias(alias);
mEntity.update();
return this;
}
/** /**
* 如果你是使用{@link DownloadReceiver#load(DownloadGroupEntity)}进行下载操作那么你需要设置任务组的下载地址 * 如果你是使用{@link DownloadReceiver#load(DownloadGroupEntity)}进行下载操作那么你需要设置任务组的下载地址
*/ */
@ -107,136 +68,4 @@ public class DownloadGroupTarget
mEntity.update(); mEntity.update();
return this; return this;
} }
/**
* 设置任务组的文件夹路径在Aria中任务组的所有子任务都会下载到以任务组组名的文件夹中
* groupDirPath = "/mnt/sdcard/download/group_test"
* <pre>
* {@code
* + mnt
* + sdcard
* + download
* + group_test
* - task1.apk
* - task2.apk
* - task3.apk
* ....
*
* }
* </pre>
*
* @param groupDirPath 任务组保存文件夹路径
*/
public DownloadGroupTarget setDownloadDirPath(String groupDirPath) {
if (TextUtils.isEmpty(groupDirPath)) {
throw new NullPointerException("任务组文件夹保存路径不能为null");
}
isSetDirPathed = true;
if (mEntity.getDirPath().equals(groupDirPath)) return this;
File file = new File(groupDirPath);
if (file.exists() && file.isFile()) {
throw new IllegalArgumentException("路径不能为文件");
}
if (!file.exists()) {
file.mkdirs();
}
mEntity.setDirPath(groupDirPath);
if (!TextUtils.isEmpty(mEntity.getDirPath())) {
reChangeDirPath(groupDirPath);
} else {
mEntity.setSubTasks(createSubTask());
}
mEntity.update();
return this;
}
/**
* 改变任务组文件夹路径修改文件夹路径会将子任务所有路径更换
*
* @param newDirPath 新的文件夹路径
*/
private void reChangeDirPath(String newDirPath) {
List<DownloadEntity> subTask = mEntity.getSubTask();
if (subTask != null && !subTask.isEmpty()) {
for (DownloadEntity entity : subTask) {
String oldPath = entity.getDownloadPath();
String newPath = newDirPath + "/" + entity.getFileName();
File file = new File(oldPath);
file.renameTo(new File(newPath));
DbEntity.exeSql("UPDATE DownloadEntity SET downloadPath='"
+ newPath
+ "' WHERE downloadPath='"
+ oldPath
+ "'");
DbEntity.exeSql(
"UPDATE DownloadTaskEntity SET key='" + newPath + "' WHERE key='" + oldPath + "'");
}
} else {
mEntity.setSubTasks(createSubTask());
}
}
/**
* 设置子任务文件名该方法必须在{@link #setDownloadDirPath(String)}之后调用否则不生效
*/
public DownloadGroupTarget setSubTaskFileName(List<String> subTaskFileName) {
if (subTaskFileName == null || subTaskFileName.isEmpty()) return this;
mSubTaskFileName.addAll(subTaskFileName);
if (mUrls.size() != subTaskFileName.size()) {
throw new IllegalArgumentException("下载链接数必须要和保存路径的数量一致");
}
if (isSetDirPathed) {
List<DownloadEntity> entities = mEntity.getSubTask();
int i = 0;
for (DownloadEntity entity : entities) {
String newName = mSubTaskFileName.get(i);
updateSubFileName(entity, newName);
i++;
}
}
return this;
}
/**
* 更新子任务文件名
*/
private void updateSubFileName(DownloadEntity entity, String newName) {
if (!newName.equals(entity.getFileName())) {
String oldPath = mEntity.getDirPath() + "/" + entity.getFileName();
String newPath = mEntity.getDirPath() + "/" + newName;
File oldFile = new File(oldPath);
if (oldFile.exists()) {
oldFile.renameTo(new File(newPath));
}
CommonUtil.renameDownloadConfig(oldFile.getName(), newName);
DbEntity.exeSql(
"UPDATE DownloadTaskEntity SET key='" + newPath + "' WHERE key='" + oldPath + "'");
entity.setDownloadPath(newPath);
entity.setFileName(newName);
entity.update();
}
}
/**
* 创建子任务
*/
private List<DownloadEntity> createSubTask() {
List<DownloadEntity> list = new ArrayList<>();
for (int i = 0, len = mUrls.size(); i < len; i++) {
DownloadEntity entity = new DownloadEntity();
entity.setDownloadUrl(mUrls.get(i));
String fileName = mSubTaskFileName.isEmpty() ? createFileName(entity.getDownloadUrl())
: mSubTaskFileName.get(i);
entity.setDownloadPath(mEntity.getDirPath() + "/" + fileName);
entity.setGroupName(mGroupName);
entity.setGroupChild(true);
entity.setFileName(fileName);
entity.insert();
list.add(entity);
}
return list;
}
} }

@ -18,8 +18,10 @@ package com.arialyy.aria.core.download;
import android.os.Handler; import android.os.Handler;
import com.arialyy.aria.core.AriaManager; import com.arialyy.aria.core.AriaManager;
import com.arialyy.aria.core.download.downloader.DownloadGroupUtil; import com.arialyy.aria.core.download.downloader.DownloadGroupUtil;
import com.arialyy.aria.core.download.downloader.IDownloadUtil; import com.arialyy.aria.core.download.downloader.FtpDirDownloadUtil;
import com.arialyy.aria.core.common.IUtil;
import com.arialyy.aria.core.inf.AbsGroupTask; 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.core.scheduler.ISchedulers;
import com.arialyy.aria.util.CheckUtil; import com.arialyy.aria.util.CheckUtil;
@ -30,7 +32,7 @@ import com.arialyy.aria.util.CheckUtil;
public class DownloadGroupTask extends AbsGroupTask<DownloadGroupTaskEntity, DownloadGroupEntity> { public class DownloadGroupTask extends AbsGroupTask<DownloadGroupTaskEntity, DownloadGroupEntity> {
private final String TAG = "DownloadGroupTask"; private final String TAG = "DownloadGroupTask";
private DownloadGroupListener mListener; private DownloadGroupListener mListener;
private IDownloadUtil mUtil; private IUtil mUtil;
private DownloadGroupTask(DownloadGroupTaskEntity taskEntity, Handler outHandler) { private DownloadGroupTask(DownloadGroupTaskEntity taskEntity, Handler outHandler) {
mTaskEntity = taskEntity; mTaskEntity = taskEntity;
@ -38,33 +40,40 @@ public class DownloadGroupTask extends AbsGroupTask<DownloadGroupTaskEntity, Dow
mOutHandler = outHandler; mOutHandler = outHandler;
mContext = AriaManager.APP; mContext = AriaManager.APP;
mListener = new DownloadGroupListener(this, mOutHandler); mListener = new DownloadGroupListener(this, mOutHandler);
switch (taskEntity.requestType) {
case AbsTaskEntity.HTTP:
mUtil = new DownloadGroupUtil(mListener, mTaskEntity); mUtil = new DownloadGroupUtil(mListener, mTaskEntity);
break;
case AbsTaskEntity.FTP_DIR:
mUtil = new FtpDirDownloadUtil(mListener, mTaskEntity);
break;
}
} }
@Override public boolean isRunning() { @Override public boolean isRunning() {
return mUtil.isDownloading(); return mUtil.isRunning();
} }
@Override public void start() { @Override public void start() {
mUtil.startDownload(); mUtil.start();
} }
@Override public void stop() { @Override public void stop() {
if (!mUtil.isDownloading()) { if (!mUtil.isRunning()) {
if (mOutHandler != null) { if (mOutHandler != null) {
mOutHandler.obtainMessage(ISchedulers.STOP, this).sendToTarget(); mOutHandler.obtainMessage(ISchedulers.STOP, this).sendToTarget();
} }
} }
mUtil.stopDownload(); mUtil.stop();
} }
@Override public void cancel() { @Override public void cancel() {
if (!mUtil.isDownloading()) { if (!mUtil.isRunning()) {
if (mOutHandler != null) { if (mOutHandler != null) {
mOutHandler.obtainMessage(ISchedulers.CANCEL, this).sendToTarget(); mOutHandler.obtainMessage(ISchedulers.CANCEL, this).sendToTarget();
} }
} }
mUtil.cancelDownload(); mUtil.cancel();
} }
public static class Builder { public static class Builder {

@ -16,13 +16,13 @@
package com.arialyy.aria.core.download; package com.arialyy.aria.core.download;
import android.os.Handler; import android.os.Handler;
import com.arialyy.aria.core.download.downloader.IDownloadListener; import com.arialyy.aria.core.inf.IDownloadListener;
/** /**
* Created by Aria.Lao on 2017/7/20. * Created by Aria.Lao on 2017/7/20.
* 普通任务下载的事件监听器 * 普通任务下载的事件监听器
*/ */
class DownloadListener extends BaseListener<DownloadEntity, DownloadTask> class DownloadListener extends BaseDListener<DownloadEntity, DownloadTask>
implements IDownloadListener { implements IDownloadListener {
DownloadListener(DownloadTask task, Handler outHandler) { DownloadListener(DownloadTask task, Handler outHandler) {
super(task, outHandler); super(task, outHandler);

@ -24,7 +24,7 @@ import com.arialyy.aria.core.command.normal.NormalCmdFactory;
import com.arialyy.aria.core.scheduler.DownloadGroupSchedulers; import com.arialyy.aria.core.scheduler.DownloadGroupSchedulers;
import com.arialyy.aria.core.scheduler.DownloadSchedulers; import com.arialyy.aria.core.scheduler.DownloadSchedulers;
import com.arialyy.aria.core.scheduler.ISchedulerListener; import com.arialyy.aria.core.scheduler.ISchedulerListener;
import com.arialyy.aria.core.ProxyHelper; import com.arialyy.aria.core.common.ProxyHelper;
import com.arialyy.aria.orm.DbEntity; import com.arialyy.aria.orm.DbEntity;
import com.arialyy.aria.util.CheckUtil; import com.arialyy.aria.util.CheckUtil;
import com.arialyy.aria.util.CommonUtil; import com.arialyy.aria.util.CommonUtil;
@ -58,11 +58,11 @@ public class DownloadReceiver extends AbsReceiver {
} }
/** /**
* 加载下载地址 * 加载Httphttps单任务下载地址
*/ */
public DownloadTarget load(@NonNull String downloadUrl) { public DownloadTarget load(@NonNull String url) {
CheckUtil.checkDownloadUrl(downloadUrl); CheckUtil.checkDownloadUrl(url);
return new DownloadTarget(downloadUrl, targetName); return new DownloadTarget(url, targetName);
} }
/** /**
@ -73,6 +73,14 @@ public class DownloadReceiver extends AbsReceiver {
return new DownloadGroupTarget(urls, targetName); return new DownloadGroupTarget(urls, targetName);
} }
/**
* 加载ftp单任务下载地址
*/
public FtpDownloadTarget loadFtp(@NonNull String url) {
CheckUtil.checkDownloadUrl(url);
return new FtpDownloadTarget(url, targetName);
}
/** /**
* 使用任务组实体执行任务组的实体执行任务组的下载操作 * 使用任务组实体执行任务组的实体执行任务组的下载操作
* *
@ -83,6 +91,14 @@ public class DownloadReceiver extends AbsReceiver {
return new DownloadGroupTarget(groupEntity, targetName); return new DownloadGroupTarget(groupEntity, targetName);
} }
/**
* 加载ftp文件夹下载地址
*/
public FtpDirDownloadTarget loadFtpDir(@NonNull String dirUrl) {
CheckUtil.checkDownloadUrl(dirUrl);
return new FtpDirDownloadTarget(dirUrl, targetName);
}
/** /**
* 将当前类注册到Aria * 将当前类注册到Aria
*/ */
@ -147,7 +163,7 @@ public class DownloadReceiver extends AbsReceiver {
*/ */
public DownloadEntity getDownloadEntity(String downloadUrl) { public DownloadEntity getDownloadEntity(String downloadUrl) {
CheckUtil.checkDownloadUrl(downloadUrl); CheckUtil.checkDownloadUrl(downloadUrl);
return DbEntity.findFirst(DownloadEntity.class, "downloadUrl=? and isGroupChild='false'", return DbEntity.findFirst(DownloadEntity.class, "url=? and isGroupChild='false'",
downloadUrl); downloadUrl);
} }
@ -169,11 +185,20 @@ public class DownloadReceiver extends AbsReceiver {
return DbEntity.findFirst(DownloadGroupTaskEntity.class, "key=?", hashCode); return DbEntity.findFirst(DownloadGroupTaskEntity.class, "key=?", hashCode);
} }
/**
* 通过任务组key获取任务组实体
* 如果是httpkey为所有子任务下载地址拼接后取md5
* 如果是ftpkey为ftp服务器的文件夹路径
*/
public DownloadGroupTaskEntity getDownloadGroupTask(String key) {
return DbEntity.findFirst(DownloadGroupTaskEntity.class, "key=?", key);
}
/** /**
* 下载任务是否存在 * 下载任务是否存在
*/ */
@Override public boolean taskExists(String downloadUrl) { @Override public boolean taskExists(String downloadUrl) {
return DownloadEntity.findFirst(DownloadEntity.class, "downloadUrl=?", downloadUrl) != null; return DownloadEntity.findFirst(DownloadEntity.class, "url=?", downloadUrl) != null;
} }
/** /**

@ -30,12 +30,13 @@ import java.io.File;
*/ */
public class DownloadTarget public class DownloadTarget
extends AbsDownloadTarget<DownloadTarget, DownloadEntity, DownloadTaskEntity> { extends AbsDownloadTarget<DownloadTarget, DownloadEntity, DownloadTaskEntity> {
protected String url;
DownloadTarget(DownloadEntity entity, String targetName) { DownloadTarget(DownloadEntity entity, String targetName) {
this(entity.getDownloadUrl(), targetName); this(entity.getUrl(), targetName);
} }
DownloadTarget(String url, String targetName) { DownloadTarget(String url, String targetName) {
this.url = url;
mTargetName = targetName; mTargetName = targetName;
DownloadEntity entity = getEntity(url); DownloadEntity entity = getEntity(url);
mTaskEntity = DbEntity.findFirst(DownloadTaskEntity.class, "key=? and isGroupTask='false'", mTaskEntity = DbEntity.findFirst(DownloadTaskEntity.class, "key=? and isGroupTask='false'",
@ -59,11 +60,11 @@ public class DownloadTarget
*/ */
private DownloadEntity getEntity(String downloadUrl) { private DownloadEntity getEntity(String downloadUrl) {
DownloadEntity entity = DownloadEntity entity =
DownloadEntity.findFirst(DownloadEntity.class, "downloadUrl=? and isGroupChild='false'", DownloadEntity.findFirst(DownloadEntity.class, "url=? and isGroupChild='false'",
downloadUrl); downloadUrl);
if (entity == null) { if (entity == null) {
entity = new DownloadEntity(); entity = new DownloadEntity();
entity.setDownloadUrl(downloadUrl); entity.setUrl(downloadUrl);
entity.setGroupChild(false); entity.setGroupChild(false);
entity.save(); entity.save();
} }
@ -92,7 +93,7 @@ public class DownloadTarget
* 下载任务是否存在 * 下载任务是否存在
*/ */
@Override public boolean taskExists() { @Override public boolean taskExists() {
return DownloadTaskQueue.getInstance().getTask(mEntity.getDownloadUrl()) != null; return DownloadTaskQueue.getInstance().getTask(mEntity.getUrl()) != null;
} }
/** /**

@ -19,6 +19,7 @@ package com.arialyy.aria.core.download;
import android.os.Handler; import android.os.Handler;
import android.util.Log; import android.util.Log;
import com.arialyy.aria.core.AriaManager; import com.arialyy.aria.core.AriaManager;
import com.arialyy.aria.core.common.IUtil;
import com.arialyy.aria.core.download.downloader.SimpleDownloadUtil; import com.arialyy.aria.core.download.downloader.SimpleDownloadUtil;
import com.arialyy.aria.core.inf.AbsNormalTask; import com.arialyy.aria.core.inf.AbsNormalTask;
import com.arialyy.aria.core.inf.IEntity; import com.arialyy.aria.core.inf.IEntity;
@ -33,7 +34,7 @@ public class DownloadTask extends AbsNormalTask<DownloadEntity> {
public static final String TAG = "DownloadTask"; public static final String TAG = "DownloadTask";
private DownloadListener mListener; private DownloadListener mListener;
private SimpleDownloadUtil mUtil; private IUtil mUtil;
private DownloadTask(DownloadTaskEntity taskEntity, Handler outHandler) { private DownloadTask(DownloadTaskEntity taskEntity, Handler outHandler) {
mEntity = taskEntity.getEntity(); mEntity = taskEntity.getEntity();
@ -62,11 +63,11 @@ public class DownloadTask extends AbsNormalTask<DownloadEntity> {
* @see DownloadTask#getKey() * @see DownloadTask#getKey()
*/ */
@Deprecated public String getDownloadUrl() { @Deprecated public String getDownloadUrl() {
return mEntity.getDownloadUrl(); return mEntity.getUrl();
} }
@Override public String getKey() { @Override public String getKey() {
return getDownloadUrl(); return mEntity.getUrl();
} }
/** /**
@ -75,7 +76,7 @@ public class DownloadTask extends AbsNormalTask<DownloadEntity> {
* @see DownloadTask#isRunning() * @see DownloadTask#isRunning()
*/ */
@Deprecated public boolean isDownloading() { @Deprecated public boolean isDownloading() {
return mUtil.isDownloading(); return mUtil.isRunning();
} }
@Override public boolean isRunning() { @Override public boolean isRunning() {
@ -107,10 +108,10 @@ public class DownloadTask extends AbsNormalTask<DownloadEntity> {
*/ */
@Override public void start() { @Override public void start() {
mListener.isWait = false; mListener.isWait = false;
if (mUtil.isDownloading()) { if (mUtil.isRunning()) {
Log.d(TAG, "任务正在下载"); Log.d(TAG, "任务正在下载");
} else { } else {
mUtil.startDownload(); mUtil.start();
} }
} }
@ -123,8 +124,8 @@ public class DownloadTask extends AbsNormalTask<DownloadEntity> {
private void stop(boolean isWait) { private void stop(boolean isWait) {
mListener.isWait = isWait; mListener.isWait = isWait;
if (mUtil.isDownloading()) { if (mUtil.isRunning()) {
mUtil.stopDownload(); mUtil.stop();
} else { } else {
mEntity.setState(isWait ? IEntity.STATE_WAIT : IEntity.STATE_STOP); mEntity.setState(isWait ? IEntity.STATE_WAIT : IEntity.STATE_STOP);
mEntity.update(); mEntity.update();
@ -138,14 +139,12 @@ public class DownloadTask extends AbsNormalTask<DownloadEntity> {
* 取消下载 * 取消下载
*/ */
@Override public void cancel() { @Override public void cancel() {
if (!mEntity.isComplete()) { if (!mUtil.isRunning()) {
if (!mUtil.isDownloading()) {
if (mOutHandler != null) { if (mOutHandler != null) {
mOutHandler.obtainMessage(ISchedulers.CANCEL, this).sendToTarget(); mOutHandler.obtainMessage(ISchedulers.CANCEL, this).sendToTarget();
} }
} }
mUtil.cancelDownload(); mUtil.cancel();
}
} }
public static class Builder { public static class Builder {

@ -16,6 +16,7 @@
package com.arialyy.aria.core.download; package com.arialyy.aria.core.download;
import com.arialyy.aria.core.inf.AbsTaskEntity; import com.arialyy.aria.core.inf.AbsTaskEntity;
import com.arialyy.aria.orm.Ignore;
import com.arialyy.aria.orm.OneToOne; import com.arialyy.aria.orm.OneToOne;
/** /**

@ -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;
import android.text.TextUtils;
import android.util.Log;
import com.arialyy.aria.core.inf.AbsTaskEntity;
import com.arialyy.aria.orm.DbEntity;
/**
* Created by Aria.Lao on 2017/7/26.
* ftp文件夹下载
*/
public class FtpDirDownloadTarget
extends BaseGroupTarget<FtpDirDownloadTarget, DownloadGroupTaskEntity> {
private final String TAG = "FtpDirDownloadTarget";
private String serverIp, remotePath;
private int port;
FtpDirDownloadTarget(String url, String targetName) {
init(url);
String[] pp = url.split("/")[2].split(":");
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");
}
}
private void init(String key) {
mGroupName = key;
mTaskEntity = DbEntity.findFirst(DownloadGroupTaskEntity.class, "key=?", key);
if (mTaskEntity == null) {
mTaskEntity = new DownloadGroupTaskEntity();
mTaskEntity.key = key;
mTaskEntity.entity = getDownloadGroupEntity();
mTaskEntity.insert();
}
if (mTaskEntity.entity == null) {
mTaskEntity.entity = getDownloadGroupEntity();
}
mEntity = mTaskEntity.entity;
}
/**
* 设置字符编码
*/
public FtpDirDownloadTarget charSet(String charSet) {
if (TextUtils.isEmpty(charSet)) return this;
mTaskEntity.charSet = charSet;
return this;
}
/**
* ftp 用户登录信息
*
* @param userName ftp用户名
* @param password ftp用户密码
*/
public FtpDirDownloadTarget login(String userName, String password) {
return login(userName, password, null);
}
/**
* ftp 用户登录信息
*
* @param userName ftp用户名
* @param password ftp用户密码
* @param account ftp账号
*/
public FtpDirDownloadTarget login(String userName, String password, String account) {
if (TextUtils.isEmpty(userName)) {
Log.e(TAG, "用户名不能为null");
return this;
} else if (TextUtils.isEmpty(password)) {
Log.e(TAG, "密码不能为null");
return this;
}
mTaskEntity.userName = userName;
mTaskEntity.userPw = password;
mTaskEntity.account = account;
return this;
}
}

@ -0,0 +1,120 @@
/*
* 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 android.support.annotation.NonNull;
import android.text.TextUtils;
import android.util.Log;
import com.arialyy.aria.core.inf.AbsTaskEntity;
import com.arialyy.aria.util.CommonUtil;
import java.io.File;
/**
* Created by lyy on 2016/12/5.
* https://github.com/AriaLyy/Aria
*/
public class FtpDownloadTarget extends DownloadTarget {
private final String TAG = "FtpDownloadTarget";
private String serverIp, remotePath;
private int port;
FtpDownloadTarget(String url, String targetName) {
super(url, targetName);
String[] pp = url.split("/")[2].split(":");
this.serverIp = pp[0];
this.port = Integer.parseInt(pp[1]);
mTaskEntity.requestType = AbsTaskEntity.FTP;
remotePath = url.substring(url.indexOf(pp[1]) + pp[1].length(), url.length());
if (TextUtils.isEmpty(remotePath)) {
throw new NullPointerException("ftp服务器地址不能为null");
}
int lastIndex = url.lastIndexOf("/");
mTaskEntity.serverIp = serverIp;
mTaskEntity.port = port;
mEntity.setFileName(url.substring(lastIndex + 1, url.length()));
}
/**
* 设置文件保存文件夹路径
* 关于文件名
* 1如果保存路径是该文件的保存路径/mnt/sdcard/file.zip则使用路径中的文件名file.zip
* 2如果保存路径是文件夹路径/mnt/sdcard/则使用FTP服务器该文件的文件名
*
* @param downloadPath 路径必须为文件路径不能为文件夹路径
*/
@Override public FtpDownloadTarget setDownloadPath(@NonNull String downloadPath) {
if (TextUtils.isEmpty(downloadPath)) {
throw new IllegalArgumentException("文件保持路径不能为null");
}
File file = new File(downloadPath);
if (file.isDirectory()) {
downloadPath += mEntity.getFileName();
}
if (!downloadPath.equals(mEntity.getDownloadPath())) {
File oldFile = new File(mEntity.getDownloadPath());
File newFile = new File(downloadPath);
if (TextUtils.isEmpty(mEntity.getDownloadPath()) || oldFile.renameTo(newFile)) {
mEntity.setDownloadPath(downloadPath);
mEntity.setFileName(newFile.getName());
mTaskEntity.key = downloadPath;
mEntity.update();
mTaskEntity.update();
CommonUtil.renameDownloadConfig(oldFile.getName(), newFile.getName());
}
}
return this;
}
/**
* 设置字符编码
*/
public FtpDownloadTarget charSet(String charSet) {
if (TextUtils.isEmpty(charSet)) return this;
mTaskEntity.charSet = charSet;
return this;
}
/**
* ftp 用户登录信息
*
* @param userName ftp用户名
* @param password ftp用户密码
*/
public FtpDownloadTarget login(String userName, String password) {
return login(userName, password, null);
}
/**
* ftp 用户登录信息
*
* @param userName ftp用户名
* @param password ftp用户密码
* @param account ftp账号
*/
public FtpDownloadTarget login(String userName, String password, String account) {
if (TextUtils.isEmpty(userName)) {
Log.e(TAG, "用户名不能为null");
return this;
} else if (TextUtils.isEmpty(password)) {
Log.e(TAG, "密码不能为null");
return this;
}
mTaskEntity.userName = userName;
mTaskEntity.userPw = password;
mTaskEntity.account = account;
return this;
}
}

@ -0,0 +1,155 @@
/*
* 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 android.text.TextUtils;
import android.util.Log;
import com.arialyy.aria.core.AriaManager;
import com.arialyy.aria.core.common.AbsThreadTask;
import com.arialyy.aria.core.inf.AbsEntity;
import com.arialyy.aria.core.inf.AbsTaskEntity;
import com.arialyy.aria.util.CommonUtil;
import java.io.IOException;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;
/**
* Created by Aria.Lao on 2017/7/25.
* 获取ftp文件夹信息
*/
abstract class AbsFtpInfoThread<ENTITY extends AbsEntity, TASK_ENTITY extends AbsTaskEntity<ENTITY>>
implements Runnable {
private final String TAG = "HttpFileInfoThread";
protected ENTITY mEntity;
protected TASK_ENTITY mTaskEntity;
private int mConnectTimeOut;
private OnFileInfoCallback mCallback;
AbsFtpInfoThread(TASK_ENTITY taskEntity, OnFileInfoCallback callback) {
mTaskEntity = taskEntity;
mEntity = taskEntity.getEntity();
mConnectTimeOut =
AriaManager.getInstance(AriaManager.APP).getDownloadConfig().getConnectTimeOut();
mCallback = callback;
}
@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();
client.connect(serverIp, port);
if (!TextUtils.isEmpty(mTaskEntity.account)) {
client.login(mTaskEntity.userName, mTaskEntity.userPw);
} else {
client.login(mTaskEntity.userName, mTaskEntity.userPw, mTaskEntity.account);
}
int reply = client.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply)) {
client.disconnect();
failDownload("无法连接到ftp服务器,错误码为:" + reply);
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(new String(remotePath.getBytes(charSet), AbsThreadTask.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);
} catch (IOException e) {
failDownload(e.getMessage());
} finally {
if (client != null) {
try {
client.disconnect();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
void start() {
new Thread(this).start();
}
protected void onPreComplete() {
}
/**
* 遍历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();
handleFile(path + file.getName(), file);
} else {
size += getFileSize(client.listFiles(
CommonUtil.strCharSetConvert(path + file.getName(), mTaskEntity.charSet)), client,
path + file.getName());
}
}
return size;
}
/**
* 处理FTP文件信息
*
* @param remotePath ftp服务器文件夹路径
* @param ftpFile ftp服务器上对应的文件
*/
void handleFile(String remotePath, FTPFile ftpFile) {
}
private void failDownload(String errorMsg) {
Log.e(TAG, errorMsg);
if (mCallback != null) {
mCallback.onFail(mEntity.getKey(), errorMsg);
}
}
}

@ -0,0 +1,399 @@
/*
* 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.common.IUtil;
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.IDownloadListener;
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 IUtil {
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().getUrl(), 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.getUrl(), createChildDownloadTask(entity));
mCurrentLocation += entity.getCurrentProgress();
mActualTaskNum++;
}
mTotalSize += entity.getFileSize();
}
}
@Override public long getFileSize() {
return mTotalSize;
}
@Override public long getCurrentLocation() {
return mCurrentLocation;
}
@Override public boolean isRunning() {
return isRunning;
}
@Override public void cancel() {
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.cancel();
}
}
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 stop() {
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.stop();
}
}
}
protected void onStop() {
}
@Override public void start() {
isRunning = true;
mFailNum = 0;
mListener.onPre();
onStart();
}
protected void onStart() {
}
@Override public void resume() {
start();
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().getUrl(), dt);
if (mExePool.isShutdown()) return;
mExePool.execute(dt);
}
/**
* 创建子任务下载信息
*/
DownloadTaskEntity createChildDownloadTask(DownloadEntity entity) {
DownloadTaskEntity taskEntity = mTasksMap.get(entity.getUrl());
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.getUrl());
dt.start();
}
}, 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) {
}
}
}

@ -1,24 +0,0 @@
package com.arialyy.aria.core.download.downloader;
import com.arialyy.aria.core.download.DownloadTaskEntity;
import java.io.File;
/**
* 子线程下载信息类
*/
final class ChildThreadConfigEntity {
//线程Id
int THREAD_ID;
//下载文件大小
long FILE_SIZE;
//子线程启动下载位置
long START_LOCATION;
//子线程结束下载位置
long END_LOCATION;
//下载路径
File TEMP_FILE;
String DOWNLOAD_URL;
String CONFIG_FILE_PATH;
DownloadTaskEntity DOWNLOAD_TASK_ENTITY;
boolean IS_SUPPORT_BREAK_POINT = true;
}

@ -26,12 +26,14 @@ import java.util.Set;
import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext; import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.SSLSocketFactory;
import org.apache.commons.net.ftp.FTPClient;
/** /**
* Created by lyy on 2017/1/18. * Created by lyy on 2017/1/18.
* 链接帮助类 * 链接帮助类
*/ */
class ConnectionHelp { class ConnectionHelp {
/** /**
* 处理链接 * 处理链接
* *

@ -16,19 +16,11 @@
package com.arialyy.aria.core.download.downloader; package com.arialyy.aria.core.download.downloader;
import android.util.SparseArray; import android.util.SparseArray;
import com.arialyy.aria.core.download.DownloadEntity; import com.arialyy.aria.core.common.IUtil;
import com.arialyy.aria.core.download.DownloadGroupTaskEntity; import com.arialyy.aria.core.download.DownloadGroupTaskEntity;
import com.arialyy.aria.core.download.DownloadTaskEntity; import com.arialyy.aria.core.download.DownloadTaskEntity;
import com.arialyy.aria.core.inf.IEntity; 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.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
@ -36,153 +28,37 @@ import java.util.concurrent.Executors;
* Created by AriaL on 2017/6/30. * Created by AriaL on 2017/6/30.
* 任务组下载工具 * 任务组下载工具
*/ */
public class DownloadGroupUtil implements IDownloadUtil { public class DownloadGroupUtil extends AbsGroupUtil implements IUtil {
private final String TAG = "DownloadGroupUtil"; private final String TAG = "DownloadGroupUtil";
/**
* 任务组所有任务总大小
*/
private long mTotalSize = 0;
private long mCurrentLocation = 0;
private ExecutorService mInfoPool; 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<FileInfoThread.OnFileInfoCallback> mFileInfoCallbacks = new SparseArray<>(); 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) { public DownloadGroupUtil(IDownloadGroupListener listener, DownloadGroupTaskEntity taskEntity) {
mListener = listener; super(listener, taskEntity);
mTaskEntity = taskEntity;
mInfoPool = Executors.newCachedThreadPool(); 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() { @Override public void onCancel() {
closeTimer(false); super.onCancel();
mListener.onCancel();
if (!mInfoPool.isShutdown()) { if (!mInfoPool.isShutdown()) {
mInfoPool.shutdown(); 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() { @Override protected void onStop() {
closeTimer(false); super.onStop();
mListener.onStop(mCurrentLocation);
if (!mInfoPool.isShutdown()) { if (!mInfoPool.isShutdown()) {
mInfoPool.shutdown(); 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() { @Override protected void onStart() {
isRunning = true; super.onStart();
mFailNum = 0;
Set<String> keys = mExeMap.keySet(); Set<String> keys = mExeMap.keySet();
mListener.onPre();
int i = 0; int i = 0;
for (String key : keys) { for (String key : keys) {
DownloadTaskEntity taskEntity = mExeMap.get(key); DownloadTaskEntity taskEntity = mExeMap.get(key);
@ -199,19 +75,14 @@ public class DownloadGroupUtil implements IDownloadUtil {
if (i == mExeMap.size()) startRunningFlow(); if (i == mExeMap.size()) startRunningFlow();
} }
@Override public void resumeDownload() {
startDownload();
mListener.onResume(mCurrentLocation);
}
/** /**
* 创建文件信息获取线程 * 创建文件信息获取线程
*/ */
private FileInfoThread createFileInfoThread(DownloadTaskEntity taskEntity) { private HttpFileInfoThread createFileInfoThread(DownloadTaskEntity taskEntity) {
FileInfoThread.OnFileInfoCallback callback = mFileInfoCallbacks.get(taskEntity.hashCode()); OnFileInfoCallback callback = mFileInfoCallbacks.get(taskEntity.hashCode());
if (callback == null) { if (callback == null) {
callback = new FileInfoThread.OnFileInfoCallback() { callback = new OnFileInfoCallback() {
int failNum = 0; int failNum = 0;
@Override public void onComplete(String url, int code) { @Override public void onComplete(String url, int code) {
@ -248,195 +119,6 @@ public class DownloadGroupUtil implements IDownloadUtil {
} }
}; };
} }
return new FileInfoThread(taskEntity, callback); 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) {
}
} }
} }

@ -15,192 +15,31 @@
*/ */
package com.arialyy.aria.core.download.downloader; package com.arialyy.aria.core.download.downloader;
import android.content.Context;
import android.util.Log;
import android.util.SparseArray;
import com.arialyy.aria.core.AriaManager; import com.arialyy.aria.core.AriaManager;
import com.arialyy.aria.core.common.AbsFileer;
import com.arialyy.aria.core.common.AbsThreadTask;
import com.arialyy.aria.core.common.SubThreadConfig;
import com.arialyy.aria.core.download.DownloadEntity; import com.arialyy.aria.core.download.DownloadEntity;
import com.arialyy.aria.core.download.DownloadTaskEntity; import com.arialyy.aria.core.download.DownloadTaskEntity;
import com.arialyy.aria.core.inf.AbsTaskEntity;
import com.arialyy.aria.core.inf.IDownloadListener;
import com.arialyy.aria.orm.DbEntity; import com.arialyy.aria.orm.DbEntity;
import com.arialyy.aria.util.BufferedRandomAccessFile; import com.arialyy.aria.util.BufferedRandomAccessFile;
import com.arialyy.aria.util.CommonUtil; import com.arialyy.aria.util.CommonUtil;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.Properties;
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/7/1. * Created by AriaL on 2017/7/1.
* 文件下载器 * 文件下载器
*/ */
class Downloader implements Runnable, IDownloadUtil { class Downloader extends AbsFileer<DownloadEntity, DownloadTaskEntity> {
private final String TAG = "Downloader";
private IDownloadListener mListener;
private DownloadTaskEntity mTaskEntity;
private DownloadEntity mEntity;
private ExecutorService mFixedThreadPool;
private File mConfigFile;//下载信息配置文件
private Context mContext;
private File mTempFile; //下载的文件
private boolean isNewTask = true;
private int mThreadNum, mRealThreadNum;
private StateConstance mConstance;
private SparseArray<Runnable> mTask = new SparseArray<>();
/**
* 小于1m的文件不启用多线程
*/
private static final long SUB_LEN = 1024 * 1024;
private Timer mTimer;
Downloader(IDownloadListener listener, DownloadTaskEntity taskEntity) { Downloader(IDownloadListener listener, DownloadTaskEntity taskEntity) {
mListener = listener; super(listener, taskEntity);
mTaskEntity = taskEntity;
mEntity = mTaskEntity.getEntity();
mContext = AriaManager.APP;
mConstance = new StateConstance();
}
void setMaxSpeed(double maxSpeed) {
for (int i = 0; i < mThreadNum; i++) {
SingleThreadTask task = (SingleThreadTask) mTask.get(i);
if (task != null) {
task.setMaxSpeed(maxSpeed);
}
}
} }
public StateConstance getConstance() { @Override protected void checkTask() {
return mConstance;
}
@Override public void run() {
startFlow();
}
/**
* 开始下载流程
*/
private void startFlow() {
checkTask();
mListener.onPostPre(mEntity.getFileSize());
mConstance.cleanState();
mConstance.isDownloading = true;
if (!mTaskEntity.isSupportBP) {
mThreadNum = 1;
mConstance.THREAD_NUM = mThreadNum;
handleNoSupportBreakpointDownload();
} else {
mThreadNum = isNewTask ? (mEntity.getFileSize() <= SUB_LEN ? 1
: AriaManager.getInstance(mContext).getDownloadConfig().getThreadNum()) : mRealThreadNum;
mConstance.THREAD_NUM = mThreadNum;
mFixedThreadPool = Executors.newFixedThreadPool(mThreadNum);
handleBreakpoint();
}
startTimer();
}
/**
* 启动进度获取定时器
*/
private void startTimer() {
mTimer = new Timer(true);
mTimer.schedule(new TimerTask() {
@Override public void run() {
if (mConstance.isComplete() || !mConstance.isDownloading) {
closeTimer();
} else if (mConstance.CURRENT_LOCATION >= 0) {
mListener.onProgress(mConstance.CURRENT_LOCATION);
}
}
}, 0, 1000);
}
private void closeTimer() {
if (mTimer != null) {
mTimer.purge();
mTimer.cancel();
}
}
@Override public long getFileSize() {
return mEntity.getFileSize();
}
/**
* 获取当前下载位置
*/
@Override public long getCurrentLocation() {
return mConstance.CURRENT_LOCATION;
}
@Override public boolean isDownloading() {
return mConstance.isDownloading;
}
@Override public void cancelDownload() {
closeTimer();
mConstance.isCancel = true;
mConstance.isDownloading = false;
if (mFixedThreadPool != null) {
mFixedThreadPool.shutdown();
}
for (int i = 0; i < mThreadNum; i++) {
SingleThreadTask task = (SingleThreadTask) mTask.get(i);
if (task != null) {
task.cancel();
}
}
CommonUtil.delDownloadTaskConfig(mTaskEntity.removeFile, mTaskEntity);
}
@Override public void stopDownload() {
closeTimer();
if (mConstance.isComplete()) return;
mConstance.isStop = true;
mConstance.isDownloading = false;
if (mFixedThreadPool != null) {
mFixedThreadPool.shutdown();
}
for (int i = 0; i < mThreadNum; i++) {
SingleThreadTask task = (SingleThreadTask) mTask.get(i);
if (task != null) {
task.stop();
}
}
}
/**
* 直接调用的时候会自动启动线程执行
*/
@Override public void startDownload() {
new Thread(this).start();
}
@Override public void resumeDownload() {
startDownload();
}
/**
* 返回该下载器的
*/
public IDownloadListener getListener() {
return mListener;
}
/**
* 检查任务是否是新任务新任务条件
* 1文件不存在
* 2下载记录文件不存在
* 3下载记录文件缺失或不匹配
* 4数据库记录不存在
* 5不支持断点则是新任务
*/
private void checkTask() {
if (!mTaskEntity.isSupportBP) { if (!mTaskEntity.isSupportBP) {
isNewTask = true; isNewTask = true;
return; return;
@ -215,7 +54,7 @@ class Downloader implements Runnable, IDownloadUtil {
CommonUtil.createFile(mConfigFile.getPath()); CommonUtil.createFile(mConfigFile.getPath());
} else if (!mTempFile.exists()) { } else if (!mTempFile.exists()) {
isNewTask = true; isNewTask = true;
} else if (DbEntity.findFirst(DownloadEntity.class, "downloadUrl=?", mEntity.getDownloadUrl()) } else if (DbEntity.findFirst(DownloadEntity.class, "url=?", mEntity.getDownloadUrl())
== null) { == null) {
isNewTask = true; isNewTask = true;
} else { } else {
@ -223,155 +62,16 @@ class Downloader implements Runnable, IDownloadUtil {
} }
} }
/** @Override protected void handleNewTask() {
* 检查记录文件如果是新任务返回{@code true}否则返回{@code false}
*/
private boolean checkConfigFile() {
Properties pro = CommonUtil.loadConfig(mConfigFile);
if (pro.isEmpty()) {
return true;
}
Set<Object> keys = pro.keySet();
int num = 0;
for (Object key : keys) {
if (String.valueOf(key).contains("_record_")) {
num++;
}
}
if (num == 0) {
return true;
}
mRealThreadNum = num;
for (int i = 0; i < mRealThreadNum; i++) {
if (pro.getProperty(mTempFile.getName() + "_record_" + i) == null) {
Object state = pro.getProperty(mTempFile.getName() + "_state_" + i);
if (state != null && Integer.parseInt(state + "") == 1) {
continue;
}
return true;
}
}
return false;
}
/**
* 恢复记录地址
*
* @return true 表示下载完成
*/
private boolean resumeRecordLocation(int i, long startL, long endL) {
mConstance.CURRENT_LOCATION += endL - startL;
Log.d(TAG, "++++++++++ 线程_" + i + "_已经下载完成 ++++++++++");
mConstance.COMPLETE_THREAD_NUM++;
mConstance.STOP_NUM++;
mConstance.CANCEL_NUM++;
if (mConstance.isComplete()) {
if (mConfigFile.exists()) {
mConfigFile.delete();
}
mListener.onComplete();
mConstance.isDownloading = false;
return true;
}
return false;
}
/**
* 创建单线程任务
*/
private void addSingleTask(int i, long startL, long endL, long fileLength) {
ChildThreadConfigEntity entity = new ChildThreadConfigEntity();
entity.FILE_SIZE = fileLength;
entity.DOWNLOAD_URL =
mEntity.isRedirect() ? mEntity.getRedirectUrl() : mEntity.getDownloadUrl();
entity.TEMP_FILE = mTempFile;
entity.THREAD_ID = i;
entity.START_LOCATION = startL;
entity.END_LOCATION = endL;
entity.CONFIG_FILE_PATH = mConfigFile.getPath();
entity.IS_SUPPORT_BREAK_POINT = mTaskEntity.isSupportBP;
entity.DOWNLOAD_TASK_ENTITY = mTaskEntity;
SingleThreadTask task = new SingleThreadTask(mConstance, mListener, entity);
mTask.put(i, task);
}
/**
* 启动单线程下载任务
*/
private void startSingleTask(int[] recordL) {
if (mConstance.CURRENT_LOCATION > 0) {
mListener.onResume(mConstance.CURRENT_LOCATION);
} else {
mListener.onStart(mConstance.CURRENT_LOCATION);
}
mFixedThreadPool = Executors.newFixedThreadPool(recordL.length);
for (int l : recordL) {
if (l == -1) continue;
Runnable task = mTask.get(l);
if (task != null) {
mFixedThreadPool.execute(task);
}
}
}
/**
* 处理断点
*/
private void handleBreakpoint() {
long fileLength = mEntity.getFileSize();
Properties pro = CommonUtil.loadConfig(mConfigFile);
int blockSize = (int) (fileLength / mThreadNum);
int[] recordL = new int[mThreadNum];
for (int i = 0; i < mThreadNum; i++) {
recordL[i] = -1;
}
int rl = 0;
if (isNewTask) {
createNewFile(fileLength);
}
for (int i = 0; i < mThreadNum; i++) {
long startL = i * blockSize, endL = (i + 1) * blockSize;
Object state = pro.getProperty(mTempFile.getName() + "_state_" + i);
if (state != null && Integer.parseInt(state + "") == 1) { //该线程已经完成
if (resumeRecordLocation(i, startL, endL)) return;
continue;
}
//分配下载位置
Object record = pro.getProperty(mTempFile.getName() + "_record_" + i);
//如果有记录,则恢复下载
if (!isNewTask && record != null && Long.parseLong(record + "") >= 0) {
Long r = Long.parseLong(record + "");
mConstance.CURRENT_LOCATION += r - startL;
Log.d(TAG, "任务【" + mEntity.getFileName() + "】线程__" + i + "__恢复下载");
startL = r;
recordL[rl] = i;
rl++;
} else {
recordL[rl] = i;
rl++;
}
if (i == (mThreadNum - 1)) {
//最后一个线程的结束位置即为文件的总长度
endL = fileLength;
}
addSingleTask(i, startL, endL, fileLength);
}
startSingleTask(recordL);
}
/**
* 创建新的下载文件
*/
private void createNewFile(long fileLength) {
CommonUtil.createFile(mTempFile.getPath()); CommonUtil.createFile(mTempFile.getPath());
BufferedRandomAccessFile file = null; BufferedRandomAccessFile file = null;
try { try {
file = new BufferedRandomAccessFile(new File(mTempFile.getPath()), "rwd", 8192); file = new BufferedRandomAccessFile(new File(mTempFile.getPath()), "rwd", 8192);
//设置文件长度 //设置文件长度
file.setLength(fileLength); file.setLength(mEntity.getFileSize());
} catch (IOException e) { } catch (IOException e) {
failDownload("下载失败【downloadUrl:" failDownload("下载失败【downloadUrl:"
+ mEntity.getDownloadUrl() + mEntity.getUrl()
+ "】\n【filePath:" + "】\n【filePath:"
+ mEntity.getDownloadPath() + mEntity.getDownloadPath()
+ "】\n" + "】\n"
@ -387,33 +87,14 @@ class Downloader implements Runnable, IDownloadUtil {
} }
} }
/** @Override protected AbsThreadTask selectThreadTask(SubThreadConfig<DownloadTaskEntity> config) {
* 处理不支持断点的下载 switch (mTaskEntity.requestType) {
*/ case AbsTaskEntity.FTP:
private void handleNoSupportBreakpointDownload() { case AbsTaskEntity.FTP_DIR:
ChildThreadConfigEntity entity = new ChildThreadConfigEntity(); return new FtpThreadTask(mConstance, (IDownloadListener) mListener, config);
long len = mEntity.getFileSize(); case AbsTaskEntity.HTTP:
entity.FILE_SIZE = len; return new HttpThreadTask(mConstance, (IDownloadListener) mListener, config);
entity.DOWNLOAD_URL =
mEntity.isRedirect() ? mEntity.getRedirectUrl() : mEntity.getDownloadUrl();
entity.TEMP_FILE = mTempFile;
entity.THREAD_ID = 0;
entity.START_LOCATION = 0;
entity.END_LOCATION = entity.FILE_SIZE;
entity.CONFIG_FILE_PATH = mConfigFile.getPath();
entity.IS_SUPPORT_BREAK_POINT = mTaskEntity.isSupportBP;
entity.DOWNLOAD_TASK_ENTITY = mTaskEntity;
SingleThreadTask task = new SingleThreadTask(mConstance, mListener, entity);
mTask.put(0, task);
mFixedThreadPool.execute(task);
mListener.onPostPre(len);
mListener.onStart(0);
} }
return null;
private void failDownload(String errorMsg) {
closeTimer();
Log.e(TAG, errorMsg);
mConstance.isDownloading = false;
mListener.onFail();
} }
} }

@ -0,0 +1,118 @@
/*
* 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 android.text.TextUtils;
import android.util.Log;
import com.arialyy.aria.core.AriaManager;
import java.io.IOException;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPReply;
/**
* Created by Aria.Lao on 2017/7/26.
*/
public class FtpClientHelp {
private final String TAG = "FtpClientHelp";
private static volatile FtpClientHelp INSTANCE = null;
private FTPClient client;
private String serverIp, user, pw, account;
private int port;
private FtpClientHelp() {
}
public static FtpClientHelp getInstnce() {
if (INSTANCE == null) {
synchronized (AriaManager.LOCK) {
INSTANCE = new FtpClientHelp();
}
}
return INSTANCE;
}
public FTPClient getClient() {
if (client == null || !client.isConnected()) {
createClient();
}
return client;
}
/**
* 登录到FTP服务器当客户端为null或客户端没有连接到FTP服务器时才会执行登录操作
*/
public FTPClient login(String serverIp, int port, String user, String pw, String account) {
this.serverIp = serverIp;
this.port = port;
this.user = user;
this.pw = pw;
this.account = account;
if (client == null || !client.isConnected()) {
createClient();
}
return client;
}
/**
* 登出
*/
public void logout() {
try {
if (client != null && client.isConnected()) {
client.logout();
}
} catch (IOException e) {
e.printStackTrace();
}
}
FTPClient createClient() {
new Thread(new Runnable() {
@Override public void run() {
client = new FTPClient();
try {
client.connect(serverIp, port);
if (!TextUtils.isEmpty(account)) {
client.login(user, pw);
} else {
client.login(user, pw, account);
}
int reply = client.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply)) {
client.disconnect();
Log.e(TAG, "无法连接到ftp服务器,错误码为:" + reply);
}
} catch (IOException e) {
Log.d(TAG, e.getMessage());
} finally {
synchronized (FtpClientHelp.this) {
FtpClientHelp.this.notify();
}
}
}
}).start();
synchronized (FtpClientHelp.this) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return client;
}
}

@ -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) {
startDownload();
} 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.getUrl(), createChildDownloadTask(entity));
}
mActualTaskNum = mTaskEntity.entity.getSubTask().size();
startDownload();
}
}
@Override public void onFail(String url, String errorMsg) {
mListener.onFail();
}
}).start();
}
}
private void startDownload() {
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();
}
}

@ -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.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;
/**
* Created by Aria.Lao on 2017/7/25.
* 获取ftp文件夹信息
*/
class FtpDirInfoThread extends AbsFtpInfoThread<DownloadGroupEntity, DownloadGroupTaskEntity> {
private long mSize = 0;
FtpDirInfoThread(DownloadGroupTaskEntity taskEntity, OnFileInfoCallback callback) {
super(taskEntity, callback);
}
@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.setUrl("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);
}
}

@ -1,40 +0,0 @@
package com.arialyy.aria.core.download.downloader;
/**
* Created by Aria.Lao on 2017/7/21.
*/
public class FtpDownloadUtil implements IDownloadUtil, Runnable{
@Override public long getFileSize() {
return 0;
}
@Override public long getCurrentLocation() {
return 0;
}
@Override public boolean isDownloading() {
return false;
}
@Override public void cancelDownload() {
}
@Override public void stopDownload() {
}
@Override public void startDownload() {
}
@Override public void resumeDownload() {
}
@Override public void run() {
}
}

@ -0,0 +1,30 @@
/*
* 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.DownloadTaskEntity;
/**
* Created by Aria.Lao on 2017/7/25.
* 获取ftp文件信息
*/
class FtpFileInfoThread extends AbsFtpInfoThread<DownloadEntity, DownloadTaskEntity> {
FtpFileInfoThread(DownloadTaskEntity taskEntity, OnFileInfoCallback callback) {
super(taskEntity, callback);
}
}

@ -0,0 +1,151 @@
/*
* 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 android.text.TextUtils;
import android.util.Log;
import com.arialyy.aria.core.common.AbsThreadTask;
import com.arialyy.aria.core.common.StateConstance;
import com.arialyy.aria.core.common.SubThreadConfig;
import com.arialyy.aria.core.download.DownloadEntity;
import com.arialyy.aria.core.download.DownloadTaskEntity;
import com.arialyy.aria.core.inf.IDownloadListener;
import com.arialyy.aria.util.BufferedRandomAccessFile;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPReply;
/**
* Created by Aria.Lao on 2017/7/24.
* Ftp下载任务
*/
class FtpThreadTask extends AbsThreadTask<DownloadEntity, DownloadTaskEntity> {
private final String TAG = "FtpThreadTask";
FtpThreadTask(StateConstance constance, IDownloadListener listener,
SubThreadConfig<DownloadTaskEntity> downloadInfo) {
super(constance, listener, downloadInfo);
}
@Override public void run() {
FTPClient client = null;
InputStream is = null;
BufferedRandomAccessFile file = null;
try {
Log.d(TAG, "任务【"
+ mConfig.TEMP_FILE.getName()
+ "】线程__"
+ mConfig.THREAD_ID
+ "__开始下载【开始位置 : "
+ mConfig.START_LOCATION
+ ",结束位置:"
+ mConfig.END_LOCATION
+ "】");
String url = mEntity.getUrl();
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(serverIp, port);
if (!TextUtils.isEmpty(mTaskEntity.account)) {
client.login(mTaskEntity.userName, mTaskEntity.userPw);
} else {
client.login(mTaskEntity.userName, mTaskEntity.userPw, mTaskEntity.account);
}
int reply = client.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply)) {
client.disconnect();
fail(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(
new String(remotePath.getBytes(charSet), SERVER_CHARSET));
//发送第二次指令时,还需要再做一次判断
reply = client.getReplyCode();
if (!FTPReply.isPositivePreliminary(reply)) {
client.disconnect();
fail(mChildCurrentLocation, "获取文件信息错误,错误码为:" + reply, null);
return;
}
file = new BufferedRandomAccessFile(mConfig.TEMP_FILE, "rwd", mBufSize);
file.seek(mConfig.START_LOCATION);
byte[] buffer = new byte[mBufSize];
int len;
//当前子线程的下载位置
mChildCurrentLocation = mConfig.START_LOCATION;
while ((len = is.read(buffer)) != -1) {
if (STATE.isCancel) break;
if (STATE.isStop) break;
if (mSleepTime > 0) Thread.sleep(mSleepTime);
if (mChildCurrentLocation + len >= mConfig.END_LOCATION) {
len = (int) (mConfig.END_LOCATION - mChildCurrentLocation);
file.write(buffer, 0, len);
progress(len);
break;
} else {
file.write(buffer, 0, len);
progress(len);
}
}
if (STATE.isCancel || STATE.isStop) return;
Log.i(TAG, "任务【" + mConfig.TEMP_FILE.getName() + "】线程__" + mConfig.THREAD_ID + "__下载完毕");
writeConfig(true, 1);
STATE.COMPLETE_THREAD_NUM++;
if (STATE.isComplete()) {
File configFile = new File(mConfigFPath);
if (configFile.exists()) {
configFile.delete();
}
STATE.isRunning = false;
mListener.onComplete();
}
} catch (IOException e) {
fail(mChildCurrentLocation, "下载失败【" + mConfig.URL + "】", e);
} catch (Exception e) {
fail(mChildCurrentLocation, "获取流失败", e);
} finally {
try {
if (file != null) {
file.close();
}
if (is != null) {
is.close();
}
if (client != null && client.isConnected()) {
client.disconnect();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

@ -20,6 +20,7 @@ import android.util.Log;
import com.arialyy.aria.core.AriaManager; import com.arialyy.aria.core.AriaManager;
import com.arialyy.aria.core.download.DownloadEntity; import com.arialyy.aria.core.download.DownloadEntity;
import com.arialyy.aria.core.download.DownloadTaskEntity; import com.arialyy.aria.core.download.DownloadTaskEntity;
import com.arialyy.aria.core.download.downloader.ConnectionHelp;
import com.arialyy.aria.util.CommonUtil; import com.arialyy.aria.util.CommonUtil;
import java.io.IOException; import java.io.IOException;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
@ -29,30 +30,14 @@ import java.net.URLDecoder;
/** /**
* 下载文件信息获取 * 下载文件信息获取
*/ */
class FileInfoThread implements Runnable { class HttpFileInfoThread implements Runnable {
private final String TAG = "FileInfoThread"; private final String TAG = "HttpFileInfoThread";
private DownloadEntity mEntity; private DownloadEntity mEntity;
private DownloadTaskEntity mTaskEntity; private DownloadTaskEntity mTaskEntity;
private int mConnectTimeOut; private int mConnectTimeOut;
private OnFileInfoCallback onFileInfoListener; private OnFileInfoCallback onFileInfoListener;
interface OnFileInfoCallback { HttpFileInfoThread(DownloadTaskEntity taskEntity, OnFileInfoCallback callback) {
/**
* 处理完成
*
* @param code 状态码
*/
void onComplete(String url, int code);
/**
* 请求失败
*
* @param errorMsg 错误信息
*/
void onFail(String url, String errorMsg);
}
FileInfoThread(DownloadTaskEntity taskEntity, OnFileInfoCallback callback) {
this.mTaskEntity = taskEntity; this.mTaskEntity = taskEntity;
mEntity = taskEntity.getEntity(); mEntity = taskEntity.getEntity();
mConnectTimeOut = mConnectTimeOut =
@ -63,7 +48,7 @@ class FileInfoThread implements Runnable {
@Override public void run() { @Override public void run() {
HttpURLConnection conn = null; HttpURLConnection conn = null;
try { try {
URL url = new URL(mEntity.getDownloadUrl()); URL url = new URL(mEntity.getUrl());
conn = ConnectionHelp.handleConnection(url); conn = ConnectionHelp.handleConnection(url);
conn = ConnectionHelp.setConnectParam(mTaskEntity, conn); conn = ConnectionHelp.setConnectParam(mTaskEntity, conn);
conn.setRequestProperty("Range", "bytes=" + 0 + "-"); conn.setRequestProperty("Range", "bytes=" + 0 + "-");
@ -72,7 +57,7 @@ class FileInfoThread implements Runnable {
handleConnect(conn); handleConnect(conn);
} catch (IOException e) { } catch (IOException e) {
failDownload("下载失败【downloadUrl:" failDownload("下载失败【downloadUrl:"
+ mEntity.getDownloadUrl() + mEntity.getUrl()
+ "】\n【filePath:" + "】\n【filePath:"
+ mEntity.getDownloadPath() + mEntity.getDownloadPath()
+ "】\n" + "】\n"
@ -87,7 +72,8 @@ class FileInfoThread implements Runnable {
private void handleConnect(HttpURLConnection conn) throws IOException { private void handleConnect(HttpURLConnection conn) throws IOException {
long len = conn.getContentLength(); long len = conn.getContentLength();
if (len < 0) { if (len < 0) {
len = Long.parseLong(conn.getHeaderField(mTaskEntity.contentLength)); String temp = conn.getHeaderField(mTaskEntity.contentLength);
len = TextUtils.isEmpty(temp) ? -1 : Long.parseLong(temp);
} }
int code = conn.getResponseCode(); int code = conn.getResponseCode();
boolean isComplete = false; boolean isComplete = false;
@ -117,7 +103,7 @@ class FileInfoThread implements Runnable {
mTaskEntity.isSupportBP = false; mTaskEntity.isSupportBP = false;
isComplete = true; isComplete = true;
} else if (code == HttpURLConnection.HTTP_NOT_FOUND) { } else if (code == HttpURLConnection.HTTP_NOT_FOUND) {
failDownload("任务【" + mEntity.getDownloadUrl() + "】下载失败,错误码:404"); failDownload("任务【" + mEntity.getUrl() + "】下载失败,错误码:404");
} else if (code == HttpURLConnection.HTTP_MOVED_TEMP } else if (code == HttpURLConnection.HTTP_MOVED_TEMP
|| code == HttpURLConnection.HTTP_MOVED_PERM || code == HttpURLConnection.HTTP_MOVED_PERM
|| code == HttpURLConnection.HTTP_SEE_OTHER) { || code == HttpURLConnection.HTTP_SEE_OTHER) {
@ -126,11 +112,11 @@ class FileInfoThread implements Runnable {
mEntity.setRedirectUrl(mTaskEntity.redirectUrl); mEntity.setRedirectUrl(mTaskEntity.redirectUrl);
handle302Turn(conn); handle302Turn(conn);
} else { } else {
failDownload("任务【" + mEntity.getDownloadUrl() + "】下载失败,错误码:" + code); failDownload("任务【" + mEntity.getUrl() + "】下载失败,错误码:" + code);
} }
if (isComplete) { if (isComplete) {
if (onFileInfoListener != null) { if (onFileInfoListener != null) {
onFileInfoListener.onComplete(mEntity.getDownloadUrl(), code); onFileInfoListener.onComplete(mEntity.getUrl(), code);
} }
mEntity.update(); mEntity.update();
mTaskEntity.update(); mTaskEntity.update();
@ -145,7 +131,7 @@ class FileInfoThread implements Runnable {
Log.d(TAG, "30x跳转,location【 " + mTaskEntity.redirectUrlKey + "】" + "新url为【" + newUrl + "】"); Log.d(TAG, "30x跳转,location【 " + mTaskEntity.redirectUrlKey + "】" + "新url为【" + newUrl + "】");
if (TextUtils.isEmpty(newUrl) || newUrl.equalsIgnoreCase("null")) { if (TextUtils.isEmpty(newUrl) || newUrl.equalsIgnoreCase("null")) {
if (onFileInfoListener != null) { if (onFileInfoListener != null) {
onFileInfoListener.onFail(mEntity.getDownloadUrl(), "获取重定向链接失败"); onFileInfoListener.onFail(mEntity.getUrl(), "获取重定向链接失败");
} }
return; return;
} }
@ -168,7 +154,7 @@ class FileInfoThread implements Runnable {
*/ */
private boolean checkLen(long len) { private boolean checkLen(long len) {
if (len < 0) { if (len < 0) {
failDownload("任务【" + mEntity.getDownloadUrl() + "】下载失败,文件长度小于0"); failDownload("任务【" + mEntity.getUrl() + "】下载失败,文件长度小于0");
return false; return false;
} }
return true; return true;
@ -177,7 +163,7 @@ class FileInfoThread implements Runnable {
private void failDownload(String errorMsg) { private void failDownload(String errorMsg) {
Log.e(TAG, errorMsg); Log.e(TAG, errorMsg);
if (onFileInfoListener != null) { if (onFileInfoListener != null) {
onFileInfoListener.onFail(mEntity.getDownloadUrl(), errorMsg); onFileInfoListener.onFail(mEntity.getUrl(), errorMsg);
} }
} }
} }

@ -0,0 +1,128 @@
/*
* 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 android.util.Log;
import com.arialyy.aria.core.common.AbsThreadTask;
import com.arialyy.aria.core.common.StateConstance;
import com.arialyy.aria.core.common.SubThreadConfig;
import com.arialyy.aria.core.download.DownloadEntity;
import com.arialyy.aria.core.download.DownloadTaskEntity;
import com.arialyy.aria.core.inf.IDownloadListener;
import com.arialyy.aria.util.BufferedRandomAccessFile;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
/**
* Created by lyy on 2017/1/18.
* 下载线程
*/
final class HttpThreadTask extends AbsThreadTask<DownloadEntity, DownloadTaskEntity> {
private final String TAG = "HttpThreadTask";
HttpThreadTask(StateConstance constance, IDownloadListener listener,
SubThreadConfig<DownloadTaskEntity> downloadInfo) {
super(constance, listener, downloadInfo);
}
@Override public void run() {
HttpURLConnection conn = null;
InputStream is = null;
BufferedRandomAccessFile file = null;
try {
URL url = new URL(mConfig.URL);
conn = ConnectionHelp.handleConnection(url);
if (mConfig.SUPPORT_BP) {
Log.d(TAG, "任务【"
+ mConfig.TEMP_FILE.getName()
+ "】线程__"
+ mConfig.THREAD_ID
+ "__开始下载【开始位置 : "
+ mConfig.START_LOCATION
+ ",结束位置:"
+ mConfig.END_LOCATION
+ "】");
//在头里面请求下载开始位置和结束位置
conn.setRequestProperty("Range",
"bytes=" + mConfig.START_LOCATION + "-" + (mConfig.END_LOCATION - 1));
} else {
Log.w(TAG, "该下载不支持断点");
}
conn = ConnectionHelp.setConnectParam(mConfig.TASK_ENTITY, conn);
conn.setConnectTimeout(STATE.CONNECT_TIME_OUT);
conn.setReadTimeout(STATE.READ_TIME_OUT); //设置读取流的等待时间,必须设置该参数
is = conn.getInputStream();
//创建可设置位置的文件
file = new BufferedRandomAccessFile(mConfig.TEMP_FILE, "rwd", mBufSize);
//设置每条线程写入文件的位置
file.seek(mConfig.START_LOCATION);
byte[] buffer = new byte[mBufSize];
int len;
//当前子线程的下载位置
mChildCurrentLocation = mConfig.START_LOCATION;
while ((len = is.read(buffer)) != -1) {
if (STATE.isCancel) break;
if (STATE.isStop) break;
if (mSleepTime > 0) Thread.sleep(mSleepTime);
file.write(buffer, 0, len);
progress(len);
}
if (STATE.isCancel || STATE.isStop) return;
//支持断点的处理
if (mConfig.SUPPORT_BP) {
Log.i(TAG, "任务【" + mConfig.TEMP_FILE.getName() + "】线程__" + mConfig.THREAD_ID + "__下载完毕");
writeConfig(true, 1);
STATE.COMPLETE_THREAD_NUM++;
if (STATE.isComplete()) {
File configFile = new File(mConfigFPath);
if (configFile.exists()) {
configFile.delete();
}
STATE.isRunning = false;
mListener.onComplete();
}
} else {
Log.i(TAG, "下载任务完成");
STATE.isRunning = false;
mListener.onComplete();
}
} catch (MalformedURLException e) {
fail(mChildCurrentLocation, "下载链接异常", e);
} catch (IOException e) {
fail(mChildCurrentLocation, "下载失败【" + mConfig.URL + "】", e);
} catch (Exception e) {
fail(mChildCurrentLocation, "获取流失败", e);
} finally {
try {
if (file != null) {
file.close();
}
if (is != null) {
is.close();
}
if (conn != null) {
conn.disconnect();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

@ -16,13 +16,14 @@
package com.arialyy.aria.core.download.downloader; package com.arialyy.aria.core.download.downloader;
import com.arialyy.aria.core.download.DownloadEntity; import com.arialyy.aria.core.download.DownloadEntity;
import com.arialyy.aria.core.inf.IDownloadListener;
import com.arialyy.aria.core.inf.IEventListener; import com.arialyy.aria.core.inf.IEventListener;
/** /**
* Created by Aria.Lao on 2017/7/20. * Created by Aria.Lao on 2017/7/20.
* 下载任务组事件 * 下载任务组事件
*/ */
public interface IDownloadGroupListener extends IEventListener { public interface IDownloadGroupListener extends IDownloadListener {
/** /**
* 子任务支持断点回调 * 子任务支持断点回调

@ -0,0 +1,17 @@
package com.arialyy.aria.core.download.downloader;
interface OnFileInfoCallback {
/**
* 处理完成
*
* @param code 状态码
*/
void onComplete(String url, int code);
/**
* 请求失败
*
* @param errorMsg 错误信息
*/
void onFail(String url, String errorMsg);
}

@ -16,89 +16,110 @@
package com.arialyy.aria.core.download.downloader; package com.arialyy.aria.core.download.downloader;
import android.text.TextUtils; import com.arialyy.aria.core.common.IUtil;
import android.util.Log;
import com.arialyy.aria.core.download.DownloadTaskEntity; import com.arialyy.aria.core.download.DownloadTaskEntity;
import com.arialyy.aria.core.inf.AbsTaskEntity;
import com.arialyy.aria.core.inf.IDownloadListener;
/** /**
* Created by lyy on 2015/8/25. * Created by lyy on 2015/8/25.
* 简单的下载工具 * HTTP单任务下载工具
*/ */
public class SimpleDownloadUtil implements IDownloadUtil, Runnable { public class SimpleDownloadUtil implements IUtil, Runnable {
private static final String TAG = "SimpleDownloadUtil"; private static final String TAG = "SimpleDownloadUtil";
private IDownloadListener mListener; private IDownloadListener mListener;
private Downloader mDT; private Downloader mDownloader;
private DownloadTaskEntity mTaskEntity; private DownloadTaskEntity mTaskEntity;
public SimpleDownloadUtil(DownloadTaskEntity entity, IDownloadListener downloadListener) { public SimpleDownloadUtil(DownloadTaskEntity entity, IDownloadListener downloadListener) {
mTaskEntity = entity; mTaskEntity = entity;
mListener = downloadListener; mListener = downloadListener;
mDT = new Downloader(downloadListener, entity); mDownloader = new Downloader(downloadListener, entity);
} }
@Override public long getFileSize() { @Override public long getFileSize() {
return mDT.getFileSize(); return mDownloader.getFileSize();
} }
/** /**
* 获取当前下载位置 * 获取当前下载位置
*/ */
@Override public long getCurrentLocation() { @Override public long getCurrentLocation() {
return mDT.getCurrentLocation(); return mDownloader.getCurrentLocation();
} }
@Override public boolean isDownloading() { @Override public boolean isRunning() {
return mDT.isDownloading(); return mDownloader.isRunning();
} }
/** /**
* 取消下载 * 取消下载
*/ */
@Override public void cancelDownload() { @Override public void cancel() {
mDT.cancelDownload(); mDownloader.cancel();
} }
/** /**
* 停止下载 * 停止下载
*/ */
@Override public void stopDownload() { @Override public void stop() {
mDT.stopDownload(); mDownloader.stop();
} }
/** /**
* 多线程断点续传下载文件开始下载 * 多线程断点续传下载文件开始下载
*/ */
@Override public void startDownload() { @Override public void start() {
mListener.onPre();
new Thread(this).start(); new Thread(this).start();
} }
@Override public void resumeDownload() { @Override public void resume() {
startDownload(); start();
} }
public void setMaxSpeed(double maxSpeed) { public void setMaxSpeed(double maxSpeed) {
mDT.setMaxSpeed(maxSpeed); mDownloader.setMaxSpeed(maxSpeed);
} }
private void failDownload(String msg) { private void failDownload(String msg) {
Log.e(TAG, msg);
mListener.onFail(); mListener.onFail();
} }
@Override public void run() { @Override public void run() {
if (TextUtils.isEmpty(mTaskEntity.redirectUrl)) { mListener.onPre();
new Thread(new FileInfoThread(mTaskEntity, new FileInfoThread.OnFileInfoCallback() { if (mTaskEntity.getEntity().getFileSize() <= 1) {
new Thread(createInfoThread()).start();
} else {
mDownloader.start();
}
}
/**
* 通过链接类型创建不同的获取文件信息的线程
*/
private Runnable createInfoThread() {
switch (mTaskEntity.requestType) {
case AbsTaskEntity.FTP:
return new FtpFileInfoThread(mTaskEntity, new OnFileInfoCallback() {
@Override public void onComplete(String url, int code) { @Override public void onComplete(String url, int code) {
mDT.startDownload(); mDownloader.start();
} }
@Override public void onFail(String url, String errorMsg) { @Override public void onFail(String url, String errorMsg) {
failDownload(errorMsg); failDownload(errorMsg);
} }
})).start(); });
} else { case AbsTaskEntity.HTTP:
mDT.startDownload(); return new HttpFileInfoThread(mTaskEntity, new OnFileInfoCallback() {
@Override public void onComplete(String url, int code) {
mDownloader.start();
}
@Override public void onFail(String url, String errorMsg) {
failDownload(errorMsg);
}
});
} }
return null;
} }
} }

@ -1,288 +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 android.os.Build;
import android.text.TextUtils;
import android.util.Log;
import com.arialyy.aria.core.AriaManager;
import com.arialyy.aria.util.BufferedRandomAccessFile;
import com.arialyy.aria.util.CommonUtil;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Properties;
/**
* Created by lyy on 2017/1/18.
* 下载线程
*/
final class SingleThreadTask implements Runnable {
private static final String TAG = "SingleThreadTask";
private ChildThreadConfigEntity mConfigEntity;
private String mConfigFPath;
private long mChildCurrentLocation = 0;
private int mBufSize;
private IDownloadListener mListener;
private StateConstance CONSTANCE;
private long mSleepTime = 0;
SingleThreadTask(StateConstance constance, IDownloadListener listener,
ChildThreadConfigEntity downloadInfo) {
AriaManager manager = AriaManager.getInstance(AriaManager.APP);
CONSTANCE = constance;
CONSTANCE.CONNECT_TIME_OUT = manager.getDownloadConfig().getConnectTimeOut();
CONSTANCE.READ_TIME_OUT = manager.getDownloadConfig().getIOTimeOut();
mListener = listener;
this.mConfigEntity = downloadInfo;
if (mConfigEntity.IS_SUPPORT_BREAK_POINT) {
mConfigFPath = downloadInfo.CONFIG_FILE_PATH;
}
mBufSize = manager.getDownloadConfig().getBuffSize();
setMaxSpeed(AriaManager.getInstance(AriaManager.APP).getDownloadConfig().getMsxSpeed());
}
void setMaxSpeed(double maxSpeed) {
if (-0.9999 < maxSpeed && maxSpeed < 0.00001) {
mSleepTime = 0;
} else {
BigDecimal db = new BigDecimal(
((mBufSize / 1024) * (filterVersion() ? 1 : CONSTANCE.THREAD_NUM) / maxSpeed) * 1000);
mSleepTime = db.setScale(0, BigDecimal.ROUND_HALF_UP).longValue();
}
}
private boolean filterVersion() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
}
@Override public void run() {
HttpURLConnection conn = null;
InputStream is = null;
BufferedRandomAccessFile file = null;
try {
URL url = new URL(mConfigEntity.DOWNLOAD_URL);
conn = ConnectionHelp.handleConnection(url);
if (mConfigEntity.IS_SUPPORT_BREAK_POINT) {
Log.d(TAG, "任务【"
+ mConfigEntity.TEMP_FILE.getName()
+ "】线程__"
+ mConfigEntity.THREAD_ID
+ "__开始下载【开始位置 : "
+ mConfigEntity.START_LOCATION
+ ",结束位置:"
+ mConfigEntity.END_LOCATION
+ "】");
//在头里面请求下载开始位置和结束位置
conn.setRequestProperty("Range",
"bytes=" + mConfigEntity.START_LOCATION + "-" + (mConfigEntity.END_LOCATION - 1));
} else {
Log.w(TAG, "该下载不支持断点");
}
conn = ConnectionHelp.setConnectParam(mConfigEntity.DOWNLOAD_TASK_ENTITY, conn);
conn.setConnectTimeout(CONSTANCE.CONNECT_TIME_OUT);
conn.setReadTimeout(CONSTANCE.READ_TIME_OUT); //设置读取流的等待时间,必须设置该参数
is = conn.getInputStream();
//创建可设置位置的文件
file = new BufferedRandomAccessFile(mConfigEntity.TEMP_FILE, "rwd", mBufSize);
//设置每条线程写入文件的位置
file.seek(mConfigEntity.START_LOCATION);
byte[] buffer = new byte[mBufSize];
int len;
//当前子线程的下载位置
mChildCurrentLocation = mConfigEntity.START_LOCATION;
while ((len = is.read(buffer)) != -1) {
if (CONSTANCE.isCancel) break;
if (CONSTANCE.isStop) break;
if (mSleepTime > 0) Thread.sleep(mSleepTime);
file.write(buffer, 0, len);
progress(len);
}
if (CONSTANCE.isCancel) return;
//停止状态不需要删除记录文件
if (CONSTANCE.isStop) return;
//支持断点的处理
if (mConfigEntity.IS_SUPPORT_BREAK_POINT) {
Log.i(TAG, "任务【"
+ mConfigEntity.TEMP_FILE.getName()
+ "】线程__"
+ mConfigEntity.THREAD_ID
+ "__下载完毕");
writeConfig(true, 1);
CONSTANCE.COMPLETE_THREAD_NUM++;
if (CONSTANCE.isComplete()) {
File configFile = new File(mConfigFPath);
if (configFile.exists()) {
configFile.delete();
}
CONSTANCE.isDownloading = false;
mListener.onComplete();
}
} else {
Log.i(TAG, "下载任务完成");
CONSTANCE.isDownloading = false;
mListener.onComplete();
}
} catch (MalformedURLException e) {
failDownload(mChildCurrentLocation, "下载链接异常", e);
} catch (IOException e) {
failDownload(mChildCurrentLocation, "下载失败【" + mConfigEntity.DOWNLOAD_URL + "】", e);
} catch (Exception e) {
failDownload(mChildCurrentLocation, "获取流失败", e);
} finally {
try {
if (file != null) {
file.close();
}
if (is != null) {
is.close();
}
if (conn != null) {
conn.disconnect();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 停止下载
*/
protected void stop() {
synchronized (AriaManager.LOCK) {
try {
if (mConfigEntity.IS_SUPPORT_BREAK_POINT) {
CONSTANCE.STOP_NUM++;
Log.d(TAG, "任务【"
+ mConfigEntity.TEMP_FILE.getName()
+ "】thread__"
+ mConfigEntity.THREAD_ID
+ "__停止, stop location ==> "
+ mChildCurrentLocation);
writeConfig(false, mChildCurrentLocation);
if (CONSTANCE.isStop()) {
Log.d(TAG, "任务【" + mConfigEntity.TEMP_FILE.getName() + "】已停止");
CONSTANCE.isDownloading = false;
mListener.onStop(CONSTANCE.CURRENT_LOCATION);
}
} else {
Log.d(TAG, "任务【" + mConfigEntity.TEMP_FILE.getName() + "】已停止");
CONSTANCE.isDownloading = false;
mListener.onStop(CONSTANCE.CURRENT_LOCATION);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 下载中
*/
private void progress(long len) {
synchronized (AriaManager.LOCK) {
mChildCurrentLocation += len;
CONSTANCE.CURRENT_LOCATION += len;
}
}
/**
* 取消下载
*/
protected void cancel() {
synchronized (AriaManager.LOCK) {
if (mConfigEntity.IS_SUPPORT_BREAK_POINT) {
CONSTANCE.CANCEL_NUM++;
Log.d(TAG, "任务【"
+ mConfigEntity.TEMP_FILE.getName()
+ "】thread__"
+ mConfigEntity.THREAD_ID
+ "__取消下载");
if (CONSTANCE.isCancel()) {
File configFile = new File(mConfigFPath);
if (configFile.exists()) {
configFile.delete();
}
if (mConfigEntity.TEMP_FILE.exists()) {
mConfigEntity.TEMP_FILE.delete();
}
Log.d(TAG, "任务【" + mConfigEntity.TEMP_FILE.getName() + "】已取消");
CONSTANCE.isDownloading = false;
mListener.onCancel();
}
} else {
Log.d(TAG, "任务【" + mConfigEntity.TEMP_FILE.getName() + "】已取消");
CONSTANCE.isDownloading = false;
mListener.onCancel();
}
}
}
/**
* 下载失败
*/
private void failDownload(long currentLocation, String msg, Exception ex) {
synchronized (AriaManager.LOCK) {
try {
CONSTANCE.FAIL_NUM++;
CONSTANCE.isDownloading = false;
CONSTANCE.isStop = true;
if (ex != null) {
Log.e(TAG, msg + "\n" + CommonUtil.getPrintException(ex));
}
if (mConfigEntity.IS_SUPPORT_BREAK_POINT) {
writeConfig(false, currentLocation);
if (CONSTANCE.isFail()) {
Log.e(TAG, "任务【" + mConfigEntity.TEMP_FILE.getName() + "】下载失败");
mListener.onFail();
}
} else {
Log.e(TAG, "任务【" + mConfigEntity.TEMP_FILE.getName() + "】下载失败");
mListener.onFail();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 将记录写入到配置文件
*/
private void writeConfig(boolean isComplete, long record) throws IOException {
synchronized (AriaManager.LOCK) {
String key = null, value = null;
if (0 < record && record < mConfigEntity.END_LOCATION) {
key = mConfigEntity.TEMP_FILE.getName() + "_record_" + mConfigEntity.THREAD_ID;
value = String.valueOf(record);
} else if (record >= mConfigEntity.END_LOCATION || isComplete) {
key = mConfigEntity.TEMP_FILE.getName() + "_state_" + mConfigEntity.THREAD_ID;
value = "1";
}
if (!TextUtils.isEmpty(key) && !TextUtils.isEmpty(value)) {
File configFile = new File(mConfigFPath);
Properties pro = CommonUtil.loadConfig(configFile);
pro.setProperty(key, value);
CommonUtil.saveConfig(configFile, pro);
}
}
}
}

@ -25,6 +25,11 @@ import com.arialyy.aria.orm.Ignore;
*/ */
public abstract class AbsNormalEntity extends AbsEntity implements Parcelable { public abstract class AbsNormalEntity extends AbsEntity implements Parcelable {
/**
* 服务器地址
*/
private String url = "";
/** /**
* 文件名 * 文件名
*/ */
@ -35,6 +40,16 @@ public abstract class AbsNormalEntity extends AbsEntity implements Parcelable {
*/ */
private boolean isGroupChild = false; private boolean isGroupChild = false;
private boolean isRedirect = false; //是否重定向
private String redirectUrl = ""; //重定向链接
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public boolean isGroupChild() { public boolean isGroupChild() {
return isGroupChild; return isGroupChild;
@ -52,6 +67,22 @@ public abstract class AbsNormalEntity extends AbsEntity implements Parcelable {
this.fileName = fileName; this.fileName = fileName;
} }
public boolean isRedirect() {
return isRedirect;
}
public void setRedirect(boolean redirect) {
isRedirect = redirect;
}
public String getRedirectUrl() {
return redirectUrl;
}
public void setRedirectUrl(String redirectUrl) {
this.redirectUrl = redirectUrl;
}
public AbsNormalEntity() { public AbsNormalEntity() {
} }
@ -61,13 +92,19 @@ public abstract class AbsNormalEntity extends AbsEntity implements Parcelable {
@Override public void writeToParcel(Parcel dest, int flags) { @Override public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags); super.writeToParcel(dest, flags);
dest.writeString(this.url);
dest.writeString(this.fileName); dest.writeString(this.fileName);
dest.writeByte(this.isGroupChild ? (byte) 1 : (byte) 0); dest.writeByte(this.isGroupChild ? (byte) 1 : (byte) 0);
dest.writeByte(this.isRedirect ? (byte) 1 : (byte) 0);
dest.writeString(this.redirectUrl);
} }
protected AbsNormalEntity(Parcel in) { protected AbsNormalEntity(Parcel in) {
super(in); super(in);
this.url = in.readString();
this.fileName = in.readString(); this.fileName = in.readString();
this.isGroupChild = in.readByte() != 0; this.isGroupChild = in.readByte() != 0;
this.isRedirect = in.readByte() != 0;
this.redirectUrl = in.readString();
} }
} }

@ -19,7 +19,7 @@ import android.support.annotation.NonNull;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import com.arialyy.aria.core.AriaManager; import com.arialyy.aria.core.AriaManager;
import com.arialyy.aria.core.RequestEnum; import com.arialyy.aria.core.common.RequestEnum;
import com.arialyy.aria.core.command.normal.NormalCmdFactory; import com.arialyy.aria.core.command.normal.NormalCmdFactory;
import com.arialyy.aria.util.CommonUtil; import com.arialyy.aria.util.CommonUtil;
import java.util.Map; import java.util.Map;
@ -157,7 +157,7 @@ public abstract class AbsTarget<TARGET extends AbsTarget, ENTITY extends AbsEnti
} }
/** /**
* 开始下载 * 开始任务
*/ */
@Override public void start() { @Override public void start() {
AriaManager.getInstance(AriaManager.APP) AriaManager.getInstance(AriaManager.APP)
@ -166,7 +166,7 @@ public abstract class AbsTarget<TARGET extends AbsTarget, ENTITY extends AbsEnti
} }
/** /**
* 停止下载 * 停止任务
* *
* @see #stop() * @see #stop()
*/ */
@ -181,7 +181,7 @@ public abstract class AbsTarget<TARGET extends AbsTarget, ENTITY extends AbsEnti
} }
/** /**
* 恢复下载 * 恢复任务
*/ */
@Override public void resume() { @Override public void resume() {
AriaManager.getInstance(AriaManager.APP) AriaManager.getInstance(AriaManager.APP)
@ -190,7 +190,7 @@ public abstract class AbsTarget<TARGET extends AbsTarget, ENTITY extends AbsEnti
} }
/** /**
* 取消下载 * 删除任务
*/ */
@Override public void cancel() { @Override public void cancel() {
AriaManager.getInstance(AriaManager.APP) AriaManager.getInstance(AriaManager.APP)
@ -198,6 +198,19 @@ public abstract class AbsTarget<TARGET extends AbsTarget, ENTITY extends AbsEnti
.exe(); .exe();
} }
/**
* 删除任务
*
* @param removeFile {@code true} 不仅删除任务数据库记录还会删除已经删除完成的文件
* {@code false}如果任务已经完成只删除任务数据库记录
*/
public void cancel(boolean removeFile) {
mTaskEntity.removeFile = removeFile;
AriaManager.getInstance(AriaManager.APP)
.setCmd(CommonUtil.createCmd(mTargetName, mTaskEntity, NormalCmdFactory.TASK_CANCEL))
.exe();
}
/** /**
* 创建文件名如果url链接有后缀名则使用url中的后缀名 * 创建文件名如果url链接有后缀名则使用url中的后缀名
* *

@ -15,7 +15,7 @@
*/ */
package com.arialyy.aria.core.inf; package com.arialyy.aria.core.inf;
import com.arialyy.aria.core.RequestEnum; import com.arialyy.aria.core.common.RequestEnum;
import com.arialyy.aria.orm.DbEntity; import com.arialyy.aria.orm.DbEntity;
import com.arialyy.aria.orm.Ignore; import com.arialyy.aria.orm.Ignore;
import com.arialyy.aria.orm.Primary; import com.arialyy.aria.orm.Primary;
@ -26,17 +26,46 @@ import java.util.Map;
* Created by lyy on 2017/2/23. * Created by lyy on 2017/2/23.
*/ */
public abstract class AbsTaskEntity<ENTITY extends AbsEntity> extends DbEntity { public abstract class AbsTaskEntity<ENTITY extends AbsEntity> extends DbEntity {
/**
* HTTP下载
*/
public static final int HTTP = 0x11;
/**
* FTP当文件下载
*/
public static final int FTP = 0x12;
/**
* FTP文件夹下载为避免登录过多子任务由单线程进行处理
*/
public static final int FTP_DIR = 0x13;
/** /**
* Task实体对应的key * Task实体对应的key
*/ */
@Primary public String key = ""; @Primary public String key = "";
/**
* 账号和密码
*/
@Ignore public String userName, userPw, account, serverIp;
@Ignore public int port;
/**
* 请求类型
* {@link AbsTaskEntity#HTTP}{@link AbsTaskEntity#FTP}
*/
public int requestType = HTTP;
/** /**
* http 请求头 * http 请求头
*/ */
public Map<String, String> headers = new HashMap<>(); public Map<String, String> headers = new HashMap<>();
/**
* 字符编码默认为"utf-8"
*/
public String charSet = "utf-8";
/** /**
* 网络请求类型 * 网络请求类型
*/ */

@ -15,12 +15,66 @@
*/ */
package com.arialyy.aria.core.inf; package com.arialyy.aria.core.inf;
import android.support.annotation.NonNull;
import com.arialyy.aria.core.queue.UploadTaskQueue;
import com.arialyy.aria.core.upload.UploadEntity;
import com.arialyy.aria.core.upload.UploadTask;
import com.arialyy.aria.core.upload.UploadTaskEntity;
import com.arialyy.aria.util.CheckUtil;
import java.util.regex.Pattern;
/** /**
* Created by AriaL on 2017/6/29. * Created by AriaL on 2017/6/29.
* 任务组超类 * 任务组超类
*/ */
public abstract class AbsUploadTarget<TARGET extends AbsUploadTarget, ENTITY extends AbsEntity, TASK_ENTITY extends AbsTaskEntity> public abstract class AbsUploadTarget<TARGET extends AbsUploadTarget, ENTITY extends UploadEntity, TASK_ENTITY extends UploadTaskEntity>
extends AbsTarget<TARGET, ENTITY, TASK_ENTITY> { extends AbsTarget<TARGET, ENTITY, TASK_ENTITY> {
/**
* 设置上传路径
*
* @param uploadUrl 上传路径
*/
public TARGET setUploadUrl(@NonNull String uploadUrl) {
CheckUtil.checkDownloadUrl(uploadUrl);
if (mEntity.getUrl().equals(uploadUrl)) return (TARGET) this;
mEntity.setUrl(uploadUrl);
mEntity.update();
return (TARGET) this;
}
/**
* 从数据中读取上传实体如果数据库查不到则新创建一个上传实体
*
* @param filePath 上传文件的文件路径
*/
protected UploadEntity getUploadEntity(String filePath) {
UploadEntity entity = UploadEntity.findFirst(UploadEntity.class, "filePath=?", filePath);
if (entity == null) {
entity = new UploadEntity();
String regex = "[/|\\\\|//]";
Pattern p = Pattern.compile(regex);
String[] strs = p.split(filePath);
String fileName = strs[strs.length - 1];
entity.setFileName(fileName);
entity.setFilePath(filePath);
entity.insert();
}
return entity;
}
/**
* 下载任务是否存在
*/
@Override public boolean taskExists() {
return UploadTaskQueue.getInstance().getTask(mEntity.getFilePath()) != null;
}
/**
* 是否在下载
*/
public boolean isUploading() {
UploadTask task = UploadTaskQueue.getInstance().getTask(mEntity);
return task != null && task.isRunning();
}
} }

@ -14,15 +14,18 @@
* limitations under the License. * limitations under the License.
*/ */
package com.arialyy.aria.core.download.downloader; package com.arialyy.aria.core.inf;
import com.arialyy.aria.core.inf.IEventListener;
/** /**
* 下载监听 * 下载监听
*/ */
public interface IDownloadListener extends IEventListener { public interface IDownloadListener extends IEventListener {
/**
* 预处理完成,准备下载---开始下载之间
*/
void onPostPre(long fileSize);
/** /**
* 支持断点回调 * 支持断点回调
* *

@ -26,11 +26,6 @@ public interface IEventListener {
*/ */
void onPre(); void onPre();
/**
* 预处理完成,准备下载---开始下载之间
*/
void onPostPre(long fileSize);
/** /**
* 开始 * 开始
*/ */

@ -16,7 +16,7 @@
package com.arialyy.aria.core.inf; package com.arialyy.aria.core.inf;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import com.arialyy.aria.core.RequestEnum; import com.arialyy.aria.core.common.RequestEnum;
import java.util.Map; import java.util.Map;
/** /**

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.arialyy.aria.core.upload; package com.arialyy.aria.core.inf;
import com.arialyy.aria.core.inf.IEventListener; import com.arialyy.aria.core.inf.IEventListener;
@ -21,6 +21,6 @@ import com.arialyy.aria.core.inf.IEventListener;
* Created by lyy on 2017/2/9. * Created by lyy on 2017/2/9.
* 上传监听 * 上传监听
*/ */
interface IUploadListener extends IEventListener { public interface IUploadListener extends IEventListener {
} }

@ -60,7 +60,7 @@ public class DownloadTaskQueue
} }
@Override public String getKey(DownloadEntity entity) { @Override public String getKey(DownloadEntity entity) {
return entity.getDownloadUrl(); return entity.getUrl();
} }
@Override public int getConfigMaxNum() { @Override public int getConfigMaxNum() {

@ -25,45 +25,45 @@ public interface ISchedulerListener<TASK extends ITask> {
* 预处理有时有些地址链接比较慢这时可以先在这个地方出来一些界面上的UI如按钮的状态 * 预处理有时有些地址链接比较慢这时可以先在这个地方出来一些界面上的UI如按钮的状态
* 在这个回调中任务是获取不到文件大小下载速度等参数 * 在这个回调中任务是获取不到文件大小下载速度等参数
*/ */
public void onPre(TASK task); void onPre(TASK task);
/** /**
* 任务预加载完成 * 任务预加载完成
*/ */
public void onTaskPre(TASK task); void onTaskPre(TASK task);
/** /**
* 任务恢复下载 * 任务恢复下载
*/ */
public void onTaskResume(TASK task); void onTaskResume(TASK task);
/** /**
* 任务开始 * 任务开始
*/ */
public void onTaskStart(TASK task); void onTaskStart(TASK task);
/** /**
* 任务停止 * 任务停止
*/ */
public void onTaskStop(TASK task); void onTaskStop(TASK task);
/** /**
* 任务取消 * 任务取消
*/ */
public void onTaskCancel(TASK task); void onTaskCancel(TASK task);
/** /**
* 任务下载失败 * 任务下载失败
*/ */
public void onTaskFail(TASK task); void onTaskFail(TASK task);
/** /**
* 任务完成 * 任务完成
*/ */
public void onTaskComplete(TASK task); void onTaskComplete(TASK task);
/** /**
* 任务执行中 * 任务执行中
*/ */
public void onTaskRunning(TASK task); void onTaskRunning(TASK task);
} }

@ -0,0 +1,138 @@
/*
* 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.upload;
import android.os.Handler;
import com.arialyy.aria.core.AriaManager;
import com.arialyy.aria.core.inf.AbsEntity;
import com.arialyy.aria.core.inf.AbsTask;
import com.arialyy.aria.core.inf.IEntity;
import com.arialyy.aria.core.inf.IUploadListener;
import com.arialyy.aria.core.scheduler.ISchedulers;
import com.arialyy.aria.util.CommonUtil;
import java.lang.ref.WeakReference;
/**
* 下载监听类
*/
class BaseUListener<ENTITY extends AbsEntity, TASK extends AbsTask<ENTITY>>
implements IUploadListener {
private WeakReference<Handler> outHandler;
private long mLastLen = 0; //上一次发送长度
private boolean isFirst = true;
protected ENTITY mEntity;
protected TASK mTask;
private boolean isConvertSpeed = false;
boolean isWait = false;
BaseUListener(TASK task, Handler outHandler) {
this.outHandler = new WeakReference<>(outHandler);
this.mTask = new WeakReference<>(task).get();
this.mEntity = this.mTask.getEntity();
final AriaManager manager = AriaManager.getInstance(AriaManager.APP);
isConvertSpeed = manager.getDownloadConfig().isConvertSpeed();
mLastLen = mEntity.getCurrentProgress();
}
@Override public void onPre() {
saveData(IEntity.STATE_PRE, -1);
sendInState2Target(ISchedulers.PRE);
}
@Override public void onStart(long startLocation) {
saveData(IEntity.STATE_RUNNING, startLocation);
sendInState2Target(ISchedulers.START);
}
@Override public void onResume(long resumeLocation) {
saveData(IEntity.STATE_RUNNING, resumeLocation);
sendInState2Target(ISchedulers.RESUME);
}
@Override public void onProgress(long currentLocation) {
mEntity.setCurrentProgress(currentLocation);
long speed = currentLocation - mLastLen;
if (isFirst) {
speed = 0;
isFirst = false;
}
handleSpeed(speed);
sendInState2Target(ISchedulers.RUNNING);
mLastLen = currentLocation;
}
@Override public void onStop(long stopLocation) {
saveData(isWait ? IEntity.STATE_WAIT : IEntity.STATE_STOP, stopLocation);
handleSpeed(0);
sendInState2Target(ISchedulers.STOP);
}
@Override public void onCancel() {
saveData(IEntity.STATE_CANCEL, -1);
handleSpeed(0);
sendInState2Target(ISchedulers.CANCEL);
}
@Override public void onComplete() {
saveData(IEntity.STATE_COMPLETE, mEntity.getFileSize());
handleSpeed(0);
sendInState2Target(ISchedulers.COMPLETE);
}
@Override public void onFail() {
mEntity.setFailNum(mEntity.getFailNum() + 1);
saveData(IEntity.STATE_FAIL, mEntity.getCurrentProgress());
handleSpeed(0);
sendInState2Target(ISchedulers.FAIL);
}
private void handleSpeed(long speed) {
if (isConvertSpeed) {
mEntity.setConvertSpeed(CommonUtil.formatFileSize(speed < 0 ? 0 : speed) + "/s");
} else {
mEntity.setSpeed(speed < 0 ? 0 : speed);
}
}
/**
* 将任务状态发送给下载器
*
* @param state {@link ISchedulers#START}
*/
private void sendInState2Target(int state) {
if (outHandler.get() != null) {
outHandler.get().obtainMessage(state, mTask).sendToTarget();
}
}
private void saveData(int state, long location) {
mEntity.setComplete(state == IEntity.STATE_COMPLETE);
if (state == IEntity.STATE_CANCEL) {
mEntity.deleteData();
} else if (state == IEntity.STATE_COMPLETE) {
mEntity.setState(state);
mEntity.setCompleteTime(System.currentTimeMillis());
mEntity.setCurrentProgress(mEntity.getFileSize());
mEntity.update();
} else {
mEntity.setState(state);
if (location != -1) {
mEntity.setCurrentProgress(location);
}
mEntity.update();
}
}
}

@ -0,0 +1,80 @@
/*
* 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.upload;
import android.text.TextUtils;
import android.util.Log;
import com.arialyy.aria.core.inf.AbsTaskEntity;
import com.arialyy.aria.core.inf.AbsUploadTarget;
import com.arialyy.aria.orm.DbEntity;
import java.io.File;
/**
* Created by Aria.Lao on 2017/7/27.
* ftp单任务上传
*/
public class FtpUploadTarget
extends AbsUploadTarget<FtpUploadTarget, UploadEntity, UploadTaskEntity> {
private final String TAG = "FtpUploadTarget";
FtpUploadTarget(String filePath, String targetName) {
this.mTargetName = targetName;
mTaskEntity = DbEntity.findFirst(UploadTaskEntity.class, "key=?", filePath);
if (mTaskEntity == null) {
mTaskEntity = new UploadTaskEntity();
mTaskEntity.entity = getUploadEntity(filePath);
}
if (mTaskEntity.entity == null) {
mTaskEntity.entity = getUploadEntity(filePath);
}
mTaskEntity.requestType = AbsTaskEntity.FTP;
mEntity = mTaskEntity.entity;
File file = new File(filePath);
mEntity.setFileName(file.getName());
mEntity.setFileSize(file.length());
}
/**
* ftp 用户登录信息
*
* @param userName ftp用户名
* @param password ftp用户密码
*/
public FtpUploadTarget login(String userName, String password) {
return login(userName, password, null);
}
/**
* ftp 用户登录信息
*
* @param userName ftp用户名
* @param password ftp用户密码
* @param account ftp账号
*/
public FtpUploadTarget login(String userName, String password, String account) {
if (TextUtils.isEmpty(userName)) {
Log.e(TAG, "用户名不能为null");
return this;
} else if (TextUtils.isEmpty(password)) {
Log.e(TAG, "密码不能为null");
return this;
}
mTaskEntity.userName = userName;
mTaskEntity.userPw = password;
mTaskEntity.account = account;
return this;
}
}

@ -59,7 +59,7 @@ public class UploadEntity extends AbsNormalEntity implements Parcelable {
this.filePath = in.readString(); this.filePath = in.readString();
} }
@Ignore public static final Creator<UploadEntity> CREATOR = new Creator<UploadEntity>() { public static final Creator<UploadEntity> CREATOR = new Creator<UploadEntity>() {
@Override public UploadEntity createFromParcel(Parcel source) { @Override public UploadEntity createFromParcel(Parcel source) {
return new UploadEntity(source); return new UploadEntity(source);
} }

@ -15,6 +15,8 @@
*/ */
package com.arialyy.aria.core.upload; package com.arialyy.aria.core.upload;
import com.arialyy.aria.core.inf.IUploadListener;
/** /**
* Created by lyy on 2017/2/23. * Created by lyy on 2017/2/23.
*/ */
@ -23,10 +25,6 @@ class UploadListener implements IUploadListener {
} }
@Override public void onPostPre(long fileSize) {
}
@Override public void onStart(long startLocation) { @Override public void onStart(long startLocation) {
} }

@ -17,7 +17,7 @@ package com.arialyy.aria.core.upload;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import com.arialyy.aria.core.AriaManager; import com.arialyy.aria.core.AriaManager;
import com.arialyy.aria.core.ProxyHelper; import com.arialyy.aria.core.common.ProxyHelper;
import com.arialyy.aria.core.command.normal.NormalCmdFactory; import com.arialyy.aria.core.command.normal.NormalCmdFactory;
import com.arialyy.aria.core.download.DownloadTaskEntity; import com.arialyy.aria.core.download.DownloadTaskEntity;
import com.arialyy.aria.core.inf.AbsReceiver; import com.arialyy.aria.core.inf.AbsReceiver;
@ -35,24 +35,33 @@ import java.util.Set;
* 上传功能接收器 * 上传功能接收器
*/ */
public class UploadReceiver extends AbsReceiver<UploadEntity> { public class UploadReceiver extends AbsReceiver<UploadEntity> {
private static final String TAG = "DownloadReceiver"; private static final String TAG = "UploadReceiver";
public ISchedulerListener<UploadTask> listener; public ISchedulerListener<UploadTask> listener;
/** /**
* 加载任务 * 加载HTTP单文件上传任务
* *
* @param filePath 文件地址 * @param filePath 文件路径
*/ */
public UploadTarget load(@NonNull String filePath) { public UploadTarget load(@NonNull String filePath) {
CheckUtil.checkUploadPath(filePath); CheckUtil.checkUploadPath(filePath);
return new UploadTarget(filePath, targetName); return new UploadTarget(filePath, targetName);
} }
/**
* 加载FTP单文件上传任务
*
* @param filePath 文件路径
*/
public FtpUploadTarget loadFtp(@NonNull String filePath) {
CheckUtil.checkUploadPath(filePath);
return new FtpUploadTarget(filePath, targetName);
}
/** /**
* 通过上传路径获取上传实体 * 通过上传路径获取上传实体
*/ */
public UploadEntity getUploadEntity(String filePath) { public UploadEntity getUploadEntity(String filePath) {
CheckUtil.checkUploadPath(filePath);
return DbEntity.findFirst(UploadEntity.class, "filePath=?", filePath); return DbEntity.findFirst(UploadEntity.class, "filePath=?", filePath);
} }

@ -20,10 +20,12 @@ import com.arialyy.aria.core.inf.AbsDownloadTarget;
import com.arialyy.aria.core.inf.AbsUploadTarget; import com.arialyy.aria.core.inf.AbsUploadTarget;
import com.arialyy.aria.core.queue.UploadTaskQueue; import com.arialyy.aria.core.queue.UploadTaskQueue;
import com.arialyy.aria.orm.DbEntity; import com.arialyy.aria.orm.DbEntity;
import java.io.File;
import java.util.regex.Pattern; import java.util.regex.Pattern;
/** /**
* Created by lyy on 2017/2/28. * Created by lyy on 2017/2/28.
* http 当文件上传
*/ */
public class UploadTarget extends AbsUploadTarget<UploadTarget, UploadEntity, UploadTaskEntity> { public class UploadTarget extends AbsUploadTarget<UploadTarget, UploadEntity, UploadTaskEntity> {
@ -38,21 +40,11 @@ public class UploadTarget extends AbsUploadTarget<UploadTarget, UploadEntity, Up
mTaskEntity.entity = getUploadEntity(filePath); mTaskEntity.entity = getUploadEntity(filePath);
} }
mEntity = mTaskEntity.entity; mEntity = mTaskEntity.entity;
} File file = new File(filePath);
mEntity.setFileSize(file.length());
private UploadEntity getUploadEntity(String filePath) { mEntity = mTaskEntity.entity;
UploadEntity entity = UploadEntity.findFirst(UploadEntity.class, "filePath=?", filePath); //http暂时不支持断点上传
if (entity == null) { mTaskEntity.isSupportBP = false;
entity = new UploadEntity();
String regex = "[/|\\\\|//]";
Pattern p = Pattern.compile(regex);
String[] strs = p.split(filePath);
String fileName = strs[strs.length - 1];
entity.setFileName(fileName);
entity.setFilePath(filePath);
entity.insert();
}
return entity;
} }
/** /**
@ -63,16 +55,6 @@ public class UploadTarget extends AbsUploadTarget<UploadTarget, UploadEntity, Up
return this; return this;
} }
/**
* 设置上传路径
*
* @param uploadUrl 上传路径
*/
public UploadTarget setUploadUrl(@NonNull String uploadUrl) {
mTaskEntity.uploadUrl = uploadUrl;
return this;
}
/** /**
* 设置服务器需要的附件key * 设置服务器需要的附件key
* *
@ -92,19 +74,4 @@ public class UploadTarget extends AbsUploadTarget<UploadTarget, UploadEntity, Up
mTaskEntity.contentType = contentType; mTaskEntity.contentType = contentType;
return this; return this;
} }
/**
* 下载任务是否存在
*/
@Override public boolean taskExists() {
return UploadTaskQueue.getInstance().getTask(mEntity.getFilePath()) != null;
}
/**
* 是否在下载
*/
public boolean isUploading() {
UploadTask task = UploadTaskQueue.getInstance().getTask(mEntity);
return task != null && task.isRunning();
}
} }

@ -0,0 +1,185 @@
/*
* 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.upload.uploader;
import android.text.TextUtils;
import android.util.Log;
import com.arialyy.aria.core.common.AbsThreadTask;
import com.arialyy.aria.core.common.StateConstance;
import com.arialyy.aria.core.common.SubThreadConfig;
import com.arialyy.aria.core.inf.IEventListener;
import com.arialyy.aria.core.upload.UploadEntity;
import com.arialyy.aria.core.upload.UploadTaskEntity;
import com.arialyy.aria.util.BufferedRandomAccessFile;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;
/**
* Created by Aria.Lao on 2017/7/28.
* FTP 单线程上传任务需要FTP 服务器给用户打开删除和读入IO的权限
*/
class FtpThreadTask extends AbsThreadTask<UploadEntity, UploadTaskEntity> {
private final String TAG = "FtpThreadTask";
private String dir, remotePath, charSet;
FtpThreadTask(StateConstance constance, IEventListener listener,
SubThreadConfig<UploadTaskEntity> info) {
super(constance, listener, info);
}
@Override public void run() {
FTPClient client = null;
OutputStream os = null;
BufferedRandomAccessFile file = null;
try {
Log.d(TAG, "任务【"
+ mConfig.TEMP_FILE.getName()
+ "】线程__"
+ mConfig.THREAD_ID
+ "__开始上传【开始位置 : "
+ mConfig.START_LOCATION
+ ",结束位置:"
+ mConfig.END_LOCATION
+ "】");
//当前子线程的下载位置
mChildCurrentLocation = mConfig.START_LOCATION;
client = createClient();
if (client == null) return;
client.makeDirectory(dir);
client.changeWorkingDirectory(dir);
client.setRestartOffset(mConfig.START_LOCATION);
file = new BufferedRandomAccessFile(mConfig.TEMP_FILE, "rwd", mBufSize);
file.seek(mConfig.START_LOCATION);
if (!isRemoteComplete(client)) {
os = client.storeFileStream(new String(remotePath.getBytes(charSet), SERVER_CHARSET));
//发送第二次指令时,还需要再做一次判断
int reply = client.getReplyCode();
if (!FTPReply.isPositivePreliminary(reply)) {
client.disconnect();
fail(mChildCurrentLocation, "上传文件错误,错误码为:" + reply, null);
return;
}
upload(file, os);
}
if (STATE.isCancel || STATE.isStop) return;
Log.i(TAG, "任务【" + mConfig.TEMP_FILE.getName() + "】线程__" + mConfig.THREAD_ID + "__上传完毕");
writeConfig(true, 1);
STATE.COMPLETE_THREAD_NUM++;
if (STATE.isComplete()) {
File configFile = new File(mConfigFPath);
if (configFile.exists()) {
configFile.delete();
}
STATE.isRunning = false;
mListener.onComplete();
}
} catch (IOException e) {
fail(mChildCurrentLocation, "上传失败【" + mConfig.URL + "】", e);
} catch (Exception e) {
fail(mChildCurrentLocation, "获取流失败", e);
} finally {
try {
if (file != null) {
file.close();
}
if (os != null) {
os.close();
}
if (client != null && client.isConnected()) {
client.disconnect();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 远程文件是否已经玩飞车
*
* @return true 任务已经完成
*/
private boolean isRemoteComplete(FTPClient client) throws IOException {
FTPFile[] files = client.listFiles(new String(remotePath.getBytes(charSet), SERVER_CHARSET));
return files.length != 0 && files[0].getSize() == mEntity.getFileSize();
}
/**
* 执行上传操作
*/
private void upload(BufferedRandomAccessFile file, OutputStream os)
throws IOException, InterruptedException {
int len;
byte[] buffer = new byte[mBufSize];
while ((len = file.read(buffer)) != -1) {
if (STATE.isCancel) break;
if (STATE.isStop) break;
if (mSleepTime > 0) Thread.sleep(mSleepTime);
if (mChildCurrentLocation + len >= mConfig.END_LOCATION) {
len = (int) (mConfig.END_LOCATION - mChildCurrentLocation);
os.write(buffer, 0, len);
progress(len);
break;
} else {
os.write(buffer, 0, len);
progress(len);
}
}
}
/**
* 构建FTP客户端
*/
private FTPClient createClient() throws IOException {
String url = mEntity.getUrl();
String[] pp = url.split("/")[2].split(":");
String serverIp = pp[0];
int port = Integer.parseInt(pp[1]);
dir = url.substring(url.indexOf(pp[1]) + pp[1].length(), url.length());
remotePath = dir + "/" + mEntity.getFileName();
FTPClient client = new FTPClient();
client.connect(serverIp, port);
if (!TextUtils.isEmpty(mTaskEntity.account)) {
client.login(mTaskEntity.userName, mTaskEntity.userPw);
} else {
client.login(mTaskEntity.userName, mTaskEntity.userPw, mTaskEntity.account);
}
int reply = client.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply)) {
client.disconnect();
fail(STATE.CURRENT_LOCATION, "无法连接到ftp服务器,错误码为:" + reply, null);
return null;
}
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.allocate(mBufSize);
return client;
}
}

@ -13,10 +13,15 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.arialyy.aria.core.upload; package com.arialyy.aria.core.upload.uploader;
import android.util.Log; import android.util.Log;
import com.arialyy.aria.util.CheckUtil; import com.arialyy.aria.core.common.AbsThreadTask;
import com.arialyy.aria.core.common.StateConstance;
import com.arialyy.aria.core.common.SubThreadConfig;
import com.arialyy.aria.core.inf.IUploadListener;
import com.arialyy.aria.core.upload.UploadEntity;
import com.arialyy.aria.core.upload.UploadTaskEntity;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
@ -32,55 +37,34 @@ import java.util.Set;
import java.util.UUID; import java.util.UUID;
/** /**
* Created by lyy on 2017/2/9. * Created by Aria.Lao on 2017/7/28.
* 上传工具 * 不支持断点的HTTP上传任务
*/ */
final class UploadUtil implements Runnable { class HttpThreadTask extends AbsThreadTask<UploadEntity, UploadTaskEntity> {
private static final String TAG = "UploadUtil"; private final String TAG = "HttpThreadTask";
private final String BOUNDARY = UUID.randomUUID().toString(); // 边界标识 随机生成 private final String BOUNDARY = UUID.randomUUID().toString(); // 边界标识 随机生成
private final String PREFIX = "--", LINE_END = "\r\n"; private final String PREFIX = "--", LINE_END = "\r\n";
private UploadEntity mUploadEntity;
private UploadTaskEntity mTaskEntity;
private IUploadListener mListener;
private HttpURLConnection mHttpConn; private HttpURLConnection mHttpConn;
private long mCurrentLocation = 0; private long mCurrentLocation = 0;
private boolean isCancel = false;
private boolean isRunning = false;
private OutputStream mOutputStream; private OutputStream mOutputStream;
UploadUtil(UploadTaskEntity taskEntity, IUploadListener listener) { HttpThreadTask(StateConstance constance, IUploadListener listener,
mTaskEntity = taskEntity; SubThreadConfig<UploadTaskEntity> uploadInfo) {
CheckUtil.checkTaskEntity(taskEntity); super(constance, listener, uploadInfo);
mUploadEntity = taskEntity.getEntity();
if (listener == null) {
throw new IllegalArgumentException("上传监听不能为空");
}
mListener = listener;
}
public void start() {
isCancel = false;
isRunning = false;
new Thread(this).start();
}
public void cancel() {
isCancel = true;
isRunning = false;
} }
@Override public void run() { @Override public void run() {
File uploadFile = new File(mUploadEntity.getFilePath()); File uploadFile = new File(mEntity.getFilePath());
if (!uploadFile.exists()) { if (!uploadFile.exists()) {
Log.e(TAG, "【" + mUploadEntity.getFilePath() + "】,文件不存在。"); Log.e(TAG, "【" + mEntity.getFilePath() + "】,文件不存在。");
fail(); fail();
return; return;
} }
mListener.onPre(); mListener.onPre();
URL url; URL url;
try { try {
url = new URL(mTaskEntity.uploadUrl); url = new URL(mEntity.getUrl());
mHttpConn = (HttpURLConnection) url.openConnection(); mHttpConn = (HttpURLConnection) url.openConnection();
mHttpConn.setUseCaches(false); mHttpConn.setUseCaches(false);
mHttpConn.setDoOutput(true); mHttpConn.setDoOutput(true);
@ -97,19 +81,14 @@ final class UploadUtil implements Runnable {
for (String key : keys) { for (String key : keys) {
mHttpConn.setRequestProperty(key, mTaskEntity.headers.get(key)); mHttpConn.setRequestProperty(key, mTaskEntity.headers.get(key));
} }
mOutputStream = mHttpConn.getOutputStream(); mOutputStream = mHttpConn.getOutputStream();
mListener.onPostPre(uploadFile.length());
PrintWriter writer = PrintWriter writer =
new PrintWriter(new OutputStreamWriter(mOutputStream, mTaskEntity.charset), true); new PrintWriter(new OutputStreamWriter(mOutputStream, mTaskEntity.charSet), true);
//添加文件上传表单字段 //添加文件上传表单字段
keys = mTaskEntity.formFields.keySet(); keys = mTaskEntity.formFields.keySet();
for (String key : keys) { for (String key : keys) {
addFormField(writer, key, mTaskEntity.formFields.get(key)); addFormField(writer, key, mTaskEntity.formFields.get(key));
} }
mListener.onStart(0);
uploadFile(writer, mTaskEntity.attachment, uploadFile); uploadFile(writer, mTaskEntity.attachment, uploadFile);
Log.d(TAG, finish(writer) + ""); Log.d(TAG, finish(writer) + "");
} catch (IOException e) { } catch (IOException e) {
@ -118,13 +97,10 @@ final class UploadUtil implements Runnable {
} }
} }
boolean isRunning() {
return isRunning;
}
private void fail() { private void fail() {
try { try {
mListener.onFail(); mListener.onFail();
STATE.isRunning = false;
if (mOutputStream != null) { if (mOutputStream != null) {
mOutputStream.close(); mOutputStream.close();
} }
@ -143,7 +119,7 @@ final class UploadUtil implements Runnable {
.append("\"") .append("\"")
.append(LINE_END); .append(LINE_END);
writer.append("Content-Type: text/plain; charset=") writer.append("Content-Type: text/plain; charset=")
.append(mTaskEntity.charset) .append(mTaskEntity.charSet)
.append(LINE_END); .append(LINE_END);
writer.append(LINE_END); writer.append(LINE_END);
writer.append(value).append(LINE_END); writer.append(value).append(LINE_END);
@ -178,10 +154,9 @@ final class UploadUtil implements Runnable {
while ((bytesRead = inputStream.read(buffer)) != -1) { while ((bytesRead = inputStream.read(buffer)) != -1) {
mCurrentLocation += bytesRead; mCurrentLocation += bytesRead;
mOutputStream.write(buffer, 0, bytesRead); mOutputStream.write(buffer, 0, bytesRead);
if (isCancel) { if (STATE.isCancel) {
break; break;
} }
isRunning = true;
mListener.onProgress(mCurrentLocation); mListener.onProgress(mCurrentLocation);
} }
@ -190,12 +165,13 @@ final class UploadUtil implements Runnable {
inputStream.close(); inputStream.close();
writer.append(LINE_END); writer.append(LINE_END);
writer.flush(); writer.flush();
isRunning = false; if (STATE.isCancel) {
if (isCancel) {
mListener.onCancel(); mListener.onCancel();
STATE.isRunning = false;
return; return;
} }
mListener.onComplete(); mListener.onComplete();
STATE.isRunning = false;
} }
/** /**
@ -221,7 +197,7 @@ final class UploadUtil implements Runnable {
mHttpConn.disconnect(); mHttpConn.disconnect();
} else { } else {
Log.w(TAG, "state_code = " + status); Log.w(TAG, "state_code = " + status);
mListener.onFail(); fail();
} }
writer.flush(); writer.flush();

@ -0,0 +1,83 @@
/*
* 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.upload.uploader;
import com.arialyy.aria.core.common.IUtil;
import com.arialyy.aria.core.inf.IUploadListener;
import com.arialyy.aria.core.upload.UploadEntity;
import com.arialyy.aria.core.upload.UploadTaskEntity;
import com.arialyy.aria.util.CheckUtil;
/**
* Created by lyy on 2017/2/9.
* 简单的文件上传工具
*/
public class SimpleUploadUtil implements IUtil, Runnable {
private static final String TAG = "SimpleUploadUtil";
private UploadEntity mUploadEntity;
private UploadTaskEntity mTaskEntity;
private IUploadListener mListener;
private Uploader mUploader;
public SimpleUploadUtil(UploadTaskEntity taskEntity, IUploadListener listener) {
mTaskEntity = taskEntity;
CheckUtil.checkTaskEntity(taskEntity);
mUploadEntity = taskEntity.getEntity();
if (listener == null) {
throw new IllegalArgumentException("上传监听不能为空");
}
mListener = listener;
mUploader = new Uploader(mListener, taskEntity);
}
@Override public void run() {
mListener.onPre();
mUploader.start();
}
@Override public long getFileSize() {
return mUploader.getFileSize();
}
@Override public long getCurrentLocation() {
return mUploader.getCurrentLocation();
}
@Override public boolean isRunning() {
return mUploader.isRunning();
}
@Override public void cancel() {
mUploader.cancel();
}
@Override public void stop() {
mUploader.stop();
}
@Override public void start() {
new Thread(this).start();
}
@Override public void resume() {
mUploader.cancel();
}
@Override public void setMaxSpeed(double maxSpeed) {
mUploader.setMaxSpeed(maxSpeed);
}
}

@ -0,0 +1,87 @@
/*
* 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.upload.uploader;
import com.arialyy.aria.core.AriaManager;
import com.arialyy.aria.core.common.AbsFileer;
import com.arialyy.aria.core.common.AbsThreadTask;
import com.arialyy.aria.core.common.SubThreadConfig;
import com.arialyy.aria.core.inf.AbsTaskEntity;
import com.arialyy.aria.core.inf.IUploadListener;
import com.arialyy.aria.core.upload.UploadEntity;
import com.arialyy.aria.core.upload.UploadTaskEntity;
import com.arialyy.aria.orm.DbEntity;
import com.arialyy.aria.util.CommonUtil;
import java.io.File;
/**
* Created by Aria.Lao on 2017/7/27.
* 文件上传器
*/
class Uploader extends AbsFileer<UploadEntity, UploadTaskEntity> {
Uploader(IUploadListener listener, UploadTaskEntity taskEntity) {
super(listener, taskEntity);
mTempFile = new File(mEntity.getFilePath());
}
/**
* 检查任务是否是新任务新任务条件
* 1文件不存在
* 2记录文件不存在
* 3记录文件缺失或不匹配
* 4数据库记录不存在
* 5不支持断点则是新任务
*/
protected void checkTask() {
mConfigFile = new File(mContext.getFilesDir().getPath()
+ AriaManager.UPLOAD_TEMP_DIR
+ mEntity.getFileName()
+ ".properties");
if (!mTaskEntity.isSupportBP) {
isNewTask = true;
return;
}
if (!mConfigFile.exists()) { //记录文件被删除,则重新下载
isNewTask = true;
CommonUtil.createFile(mConfigFile.getPath());
} else if (DbEntity.findFirst(UploadEntity.class, "filePath=?", mEntity.getFilePath())
== null) {
isNewTask = true;
} else {
isNewTask = checkConfigFile();
}
}
@Override protected void handleNewTask() {
}
@Override protected int getNewTaskThreadNum() {
return 1;
}
@Override protected AbsThreadTask selectThreadTask(SubThreadConfig<UploadTaskEntity> config) {
switch (mTaskEntity.requestType) {
case AbsTaskEntity.FTP:
case AbsTaskEntity.FTP_DIR:
return new FtpThreadTask(mConstance, mListener, config);
case AbsTaskEntity.HTTP:
return new HttpThreadTask(mConstance, (IUploadListener) mListener, config);
}
return null;
}
}

@ -32,7 +32,7 @@ import java.util.Map;
class DBConfig { class DBConfig {
static Map<String, Class> mapping = new HashMap<>(); static Map<String, Class> mapping = new HashMap<>();
static String DB_NAME; static String DB_NAME;
static int VERSION = 11; static int VERSION = 12;
static { static {
if (TextUtils.isEmpty(DB_NAME)) { if (TextUtils.isEmpty(DB_NAME)) {

@ -24,6 +24,7 @@ import com.arialyy.aria.core.inf.AbsTaskEntity;
import com.arialyy.aria.core.upload.UploadEntity; import com.arialyy.aria.core.upload.UploadEntity;
import com.arialyy.aria.core.upload.UploadTaskEntity; import com.arialyy.aria.core.upload.UploadTaskEntity;
import com.arialyy.aria.exception.FileException; import com.arialyy.aria.exception.FileException;
import java.io.File;
import java.util.List; import java.util.List;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -77,6 +78,14 @@ public class CheckUtil {
if (TextUtils.isEmpty(downloadUrl)) throw new IllegalArgumentException("下载链接不能为null"); if (TextUtils.isEmpty(downloadUrl)) throw new IllegalArgumentException("下载链接不能为null");
} }
/**
* 检测下载链接是否为null
*/
public static void checkUploadUrl(String downloadUrl) {
if (TextUtils.isEmpty(downloadUrl)) throw new IllegalArgumentException("上传地址不能为null");
}
/** /**
* 检测下载链接组是否为null * 检测下载链接组是否为null
*/ */
@ -100,6 +109,8 @@ public class CheckUtil {
*/ */
public static void checkUploadPath(String uploadPath) { public static void checkUploadPath(String uploadPath) {
if (TextUtils.isEmpty(uploadPath)) throw new IllegalArgumentException("上传地址不能为null"); if (TextUtils.isEmpty(uploadPath)) throw new IllegalArgumentException("上传地址不能为null");
File file = new File(uploadPath);
if (!file.exists()) throw new IllegalArgumentException("上传文件不存在");
} }
/** /**
@ -125,7 +136,7 @@ public class CheckUtil {
DownloadEntity entity1 = ((DownloadTaskEntity) entity).getEntity(); DownloadEntity entity1 = ((DownloadTaskEntity) entity).getEntity();
if (entity1 == null) { if (entity1 == null) {
Log.e(TAG, "下载实体不能为空"); Log.e(TAG, "下载实体不能为空");
} else if (checkType && TextUtils.isEmpty(entity1.getDownloadUrl())) { } else if (checkType && TextUtils.isEmpty(entity1.getUrl())) {
Log.e(TAG, "下载链接不能为空"); Log.e(TAG, "下载链接不能为空");
} else if (checkType && TextUtils.isEmpty(entity1.getDownloadPath())) { } else if (checkType && TextUtils.isEmpty(entity1.getDownloadPath())) {
Log.e(TAG, "保存路径不能为空"); Log.e(TAG, "保存路径不能为空");
@ -167,7 +178,7 @@ public class CheckUtil {
private static void checkDownloadTaskEntity(DownloadEntity entity) { private static void checkDownloadTaskEntity(DownloadEntity entity) {
if (entity == null) { if (entity == null) {
throw new NullPointerException("下载实体不能为空"); throw new NullPointerException("下载实体不能为空");
} else if (TextUtils.isEmpty(entity.getDownloadUrl())) { } else if (TextUtils.isEmpty(entity.getUrl())) {
throw new IllegalArgumentException("下载链接不能为空"); throw new IllegalArgumentException("下载链接不能为空");
} else if (TextUtils.isEmpty(entity.getFileName())) { } else if (TextUtils.isEmpty(entity.getFileName())) {
throw new FileException("文件名不能为null"); throw new FileException("文件名不能为null");

@ -37,6 +37,7 @@ import java.io.FileNotFoundException;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.BigInteger; import java.math.BigInteger;
@ -63,6 +64,18 @@ import java.lang.reflect.WildcardType;
public class CommonUtil { public class CommonUtil {
private static final String TAG = "CommonUtil"; private static final String TAG = "CommonUtil";
/**
* 字符串编码转换
*/
public static String strCharSetConvert(String oldStr, String charSet) {
try {
return new String(oldStr.getBytes(), charSet);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
}
/** /**
* 实例化泛型的实际类型参数 * 实例化泛型的实际类型参数
* *
@ -137,7 +150,7 @@ public class CommonUtil {
/** /**
* 删除上传任务的配置包括 * 删除上传任务的配置包括
* *
* @param removeFile {@code true} 不仅删除任务数据库记录还会删除已经下载完成的文件 * @param removeFile {@code true} 不仅删除任务数据库记录还会删除已经删除完成的文件
* {@code false}如果任务已经完成只删除任务数据库记录 * {@code false}如果任务已经完成只删除任务数据库记录
*/ */
public static void delUploadTaskConfig(boolean removeFile, UploadTaskEntity tEntity) { public static void delUploadTaskConfig(boolean removeFile, UploadTaskEntity tEntity) {
@ -147,15 +160,8 @@ public class CommonUtil {
if (file.exists()) { if (file.exists()) {
file.delete(); file.delete();
} }
} else {
if (!uEntity.isComplete()) {
if (file.exists()) {
file.delete();
}
} }
} File config = new File(getFileConfig(false, uEntity.getFileName()));
File config = new File(
AriaManager.APP.getFilesDir().getPath() + "/temp/" + uEntity.getFileName() + ".properties");
if (config.exists()) { if (config.exists()) {
config.delete(); config.delete();
} }
@ -184,8 +190,7 @@ public class CommonUtil {
} }
} }
File config = new File( File config = new File(getFileConfig(true, dEntity.getFileName()));
AriaManager.APP.getFilesDir().getPath() + "/temp/" + dEntity.getFileName() + ".properties");
if (config.exists()) { if (config.exists()) {
config.delete(); config.delete();
} }
@ -599,6 +604,16 @@ public class CommonUtil {
return err.toString(); return err.toString();
} }
/**
* 通过文件名获取下载配置文件
*
* @param fileName 文件名
*/
public static String getFileConfig(boolean isDownload, String fileName) {
return AriaManager.APP.getFilesDir().getPath() + (isDownload ? AriaManager.DOWNLOAD_TEMP_DIR
: AriaManager.UPLOAD_TEMP_DIR) + fileName + ".properties";
}
/** /**
* 重命名下载配置文件 * 重命名下载配置文件
* 如果旧的配置文件名不存在则使用新的配置文件名新创建一个文件否则将旧的配置文件重命名为新的位置文件名 * 如果旧的配置文件名不存在则使用新的配置文件名新创建一个文件否则将旧的配置文件重命名为新的位置文件名
@ -608,8 +623,7 @@ public class CommonUtil {
* @param newName 新的下载文件名 * @param newName 新的下载文件名
*/ */
public static void renameDownloadConfig(String oldName, String newName) { public static void renameDownloadConfig(String oldName, String newName) {
renameConfig(AriaManager.APP.getFilesDir().getPath() + AriaManager.DOWNLOAD_TEMP_DIR, oldName, renameConfig(true, oldName, newName);
newName);
} }
/** /**
@ -621,14 +635,13 @@ public class CommonUtil {
* @param newName 新的上传文件名 * @param newName 新的上传文件名
*/ */
public static void renameUploadConfig(String oldName, String newName) { public static void renameUploadConfig(String oldName, String newName) {
renameConfig(AriaManager.APP.getFilesDir().getPath() + AriaManager.UPLOAD_TEMP_DIR, oldName, renameConfig(false, oldName, newName);
newName);
} }
private static void renameConfig(String basePath, String oldName, String newName) { private static void renameConfig(boolean isDownload, String oldName, String newName) {
if (oldName.equals(newName)) return; if (oldName.equals(newName)) return;
File oldFile = new File(basePath + oldName + ".properties"); File oldFile = new File(getFileConfig(isDownload, oldName));
File newFile = new File(basePath + newName + ".properties"); File newFile = new File(getFileConfig(isDownload, oldName));
if (!oldFile.exists()) { if (!oldFile.exists()) {
createFile(newFile.getPath()); createFile(newFile.getPath());
} else { } else {

@ -0,0 +1,314 @@
/*
* 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 org.apache.commons.net;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.nio.charset.Charset;
/***
* The DatagramSocketClient provides the basic operations that are required
* of client objects accessing datagram sockets. It is meant to be
* subclassed to avoid having to rewrite the same code over and over again
* to open a socket, close a socket, set timeouts, etc. Of special note
* is the {@link #setDatagramSocketFactory setDatagramSocketFactory }
* method, which allows you to control the type of DatagramSocket the
* DatagramSocketClient creates for network communications. This is
* especially useful for adding things like proxy support as well as better
* support for applets. For
* example, you could create a
* {@link org.apache.commons.net.DatagramSocketFactory}
* that
* requests browser security capabilities before creating a socket.
* All classes derived from DatagramSocketClient should use the
* {@link #_socketFactory_ _socketFactory_ } member variable to
* create DatagramSocket instances rather than instantiating
* them by directly invoking a constructor. By honoring this contract
* you guarantee that a user will always be able to provide his own
* Socket implementations by substituting his own SocketFactory.
*
*
* @see DatagramSocketFactory
***/
public abstract class DatagramSocketClient
{
/***
* The default DatagramSocketFactory shared by all DatagramSocketClient
* instances.
***/
private static final DatagramSocketFactory __DEFAULT_SOCKET_FACTORY =
new DefaultDatagramSocketFactory();
/**
* Charset to use for byte IO.
*/
private Charset charset = Charset.defaultCharset();
/*** The timeout to use after opening a socket. ***/
protected int _timeout_;
/*** The datagram socket used for the connection. ***/
protected DatagramSocket _socket_;
/***
* A status variable indicating if the client's socket is currently open.
***/
protected boolean _isOpen_;
/*** The datagram socket's DatagramSocketFactory. ***/
protected DatagramSocketFactory _socketFactory_;
/***
* Default constructor for DatagramSocketClient. Initializes
* _socket_ to null, _timeout_ to 0, and _isOpen_ to false.
***/
public DatagramSocketClient()
{
_socket_ = null;
_timeout_ = 0;
_isOpen_ = false;
_socketFactory_ = __DEFAULT_SOCKET_FACTORY;
}
/***
* Opens a DatagramSocket on the local host at the first available port.
* Also sets the timeout on the socket to the default timeout set
* by {@link #setDefaultTimeout setDefaultTimeout() }.
* <p>
* _isOpen_ is set to true after calling this method and _socket_
* is set to the newly opened socket.
*
* @throws SocketException If the socket could not be opened or the
* timeout could not be set.
***/
public void open() throws SocketException
{
_socket_ = _socketFactory_.createDatagramSocket();
_socket_.setSoTimeout(_timeout_);
_isOpen_ = true;
}
/***
* Opens a DatagramSocket on the local host at a specified port.
* Also sets the timeout on the socket to the default timeout set
* by {@link #setDefaultTimeout setDefaultTimeout() }.
* <p>
* _isOpen_ is set to true after calling this method and _socket_
* is set to the newly opened socket.
*
* @param port The port to use for the socket.
* @throws SocketException If the socket could not be opened or the
* timeout could not be set.
***/
public void open(int port) throws SocketException
{
_socket_ = _socketFactory_.createDatagramSocket(port);
_socket_.setSoTimeout(_timeout_);
_isOpen_ = true;
}
/***
* Opens a DatagramSocket at the specified address on the local host
* at a specified port.
* Also sets the timeout on the socket to the default timeout set
* by {@link #setDefaultTimeout setDefaultTimeout() }.
* <p>
* _isOpen_ is set to true after calling this method and _socket_
* is set to the newly opened socket.
*
* @param port The port to use for the socket.
* @param laddr The local address to use.
* @throws SocketException If the socket could not be opened or the
* timeout could not be set.
***/
public void open(int port, InetAddress laddr) throws SocketException
{
_socket_ = _socketFactory_.createDatagramSocket(port, laddr);
_socket_.setSoTimeout(_timeout_);
_isOpen_ = true;
}
/***
* Closes the DatagramSocket used for the connection.
* You should call this method after you've finished using the class
* instance and also before you call {@link #open open() }
* again. _isOpen_ is set to false and _socket_ is set to null.
***/
public void close()
{
if (_socket_ != null) {
_socket_.close();
}
_socket_ = null;
_isOpen_ = false;
}
/***
* Returns true if the client has a currently open socket.
*
* @return True if the client has a currently open socket, false otherwise.
***/
public boolean isOpen()
{
return _isOpen_;
}
/***
* Set the default timeout in milliseconds to use when opening a socket.
* After a call to open, the timeout for the socket is set using this value.
* This method should be used prior to a call to {@link #open open()}
* and should not be confused with {@link #setSoTimeout setSoTimeout()}
* which operates on the currently open socket. _timeout_ contains
* the new timeout value.
*
* @param timeout The timeout in milliseconds to use for the datagram socket
* connection.
***/
public void setDefaultTimeout(int timeout)
{
_timeout_ = timeout;
}
/***
* Returns the default timeout in milliseconds that is used when
* opening a socket.
*
* @return The default timeout in milliseconds that is used when
* opening a socket.
***/
public int getDefaultTimeout()
{
return _timeout_;
}
/***
* Set the timeout in milliseconds of a currently open connection.
* Only call this method after a connection has been opened
* by {@link #open open()}.
*
* @param timeout The timeout in milliseconds to use for the currently
* open datagram socket connection.
* @throws SocketException if an error setting the timeout
***/
public void setSoTimeout(int timeout) throws SocketException
{
_socket_.setSoTimeout(timeout);
}
/***
* Returns the timeout in milliseconds of the currently opened socket.
* If you call this method when the client socket is not open,
* a NullPointerException is thrown.
*
* @return The timeout in milliseconds of the currently opened socket.
* @throws SocketException if an error getting the timeout
***/
public int getSoTimeout() throws SocketException
{
return _socket_.getSoTimeout();
}
/***
* Returns the port number of the open socket on the local host used
* for the connection. If you call this method when the client socket
* is not open, a NullPointerException is thrown.
*
* @return The port number of the open socket on the local host used
* for the connection.
***/
public int getLocalPort()
{
return _socket_.getLocalPort();
}
/***
* Returns the local address to which the client's socket is bound.
* If you call this method when the client socket is not open, a
* NullPointerException is thrown.
*
* @return The local address to which the client's socket is bound.
***/
public InetAddress getLocalAddress()
{
return _socket_.getLocalAddress();
}
/***
* Sets the DatagramSocketFactory used by the DatagramSocketClient
* to open DatagramSockets. If the factory value is null, then a default
* factory is used (only do this to reset the factory after having
* previously altered it).
*
* @param factory The new DatagramSocketFactory the DatagramSocketClient
* should use.
***/
public void setDatagramSocketFactory(DatagramSocketFactory factory)
{
if (factory == null) {
_socketFactory_ = __DEFAULT_SOCKET_FACTORY;
} else {
_socketFactory_ = factory;
}
}
/**
* Gets the charset name.
*
* @return the charset name.
* @since 3.3
* TODO Will be deprecated once the code requires Java 1.6 as a mininmum
*/
public String getCharsetName() {
return charset.name();
}
/**
* Gets the charset.
*
* @return the charset.
* @since 3.3
*/
public Charset getCharset() {
return charset;
}
/**
* Sets the charset.
*
* @param charset the charset.
* @since 3.3
*/
public void setCharset(Charset charset) {
this.charset = charset;
}
}

@ -0,0 +1,69 @@
/*
* 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 org.apache.commons.net;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
/***
* The DatagramSocketFactory interface provides a means for the
* programmer to control the creation of datagram sockets and
* provide his own DatagramSocket implementations for use by all
* classes derived from
* {@link DatagramSocketClient}
* .
* This allows you to provide your own DatagramSocket implementations and
* to perform security checks or browser capability requests before
* creating a DatagramSocket.
*
*
***/
public interface DatagramSocketFactory
{
/***
* Creates a DatagramSocket on the local host at the first available port.
* @return the socket
*
* @throws SocketException If the socket could not be created.
***/
public DatagramSocket createDatagramSocket() throws SocketException;
/***
* Creates a DatagramSocket on the local host at a specified port.
*
* @param port The port to use for the socket.
* @return the socket
* @throws SocketException If the socket could not be created.
***/
public DatagramSocket createDatagramSocket(int port) throws SocketException;
/***
* Creates a DatagramSocket at the specified address on the local host
* at a specified port.
*
* @param port The port to use for the socket.
* @param laddr The local address to use.
* @return the socket
* @throws SocketException If the socket could not be created.
***/
public DatagramSocket createDatagramSocket(int port, InetAddress laddr)
throws SocketException;
}

@ -0,0 +1,79 @@
/*
* 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 org.apache.commons.net;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
/***
* DefaultDatagramSocketFactory implements the DatagramSocketFactory
* interface by simply wrapping the java.net.DatagramSocket
* constructors. It is the default DatagramSocketFactory used by
* {@link DatagramSocketClient}
* implementations.
*
*
* @see DatagramSocketFactory
* @see DatagramSocketClient
* @see DatagramSocketClient#setDatagramSocketFactory
***/
public class DefaultDatagramSocketFactory implements DatagramSocketFactory
{
/***
* Creates a DatagramSocket on the local host at the first available port.
* @return a new DatagramSocket
* @throws SocketException If the socket could not be created.
***/
@Override
public DatagramSocket createDatagramSocket() throws SocketException
{
return new DatagramSocket();
}
/***
* Creates a DatagramSocket on the local host at a specified port.
*
* @param port The port to use for the socket.
* @return a new DatagramSocket
* @throws SocketException If the socket could not be created.
***/
@Override
public DatagramSocket createDatagramSocket(int port) throws SocketException
{
return new DatagramSocket(port);
}
/***
* Creates a DatagramSocket at the specified address on the local host
* at a specified port.
*
* @param port The port to use for the socket.
* @param laddr The local address to use.
* @return a new DatagramSocket
* @throws SocketException If the socket could not be created.
***/
@Override
public DatagramSocket createDatagramSocket(int port, InetAddress laddr)
throws SocketException
{
return new DatagramSocket(port, laddr);
}
}

@ -0,0 +1,230 @@
/*
* 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 org.apache.commons.net;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import javax.net.SocketFactory;
/***
* DefaultSocketFactory implements the SocketFactory interface by
* simply wrapping the java.net.Socket and java.net.ServerSocket
* constructors. It is the default SocketFactory used by
* {@link SocketClient}
* implementations.
*
*
* @see SocketFactory
* @see SocketClient
* @see SocketClient#setSocketFactory
***/
public class DefaultSocketFactory extends SocketFactory
{
/** The proxy to use when creating new sockets. */
private final Proxy connProxy;
/**
* The default constructor.
*/
public DefaultSocketFactory()
{
this(null);
}
/**
* A constructor for sockets with proxy support.
*
* @param proxy The Proxy to use when creating new Sockets.
* @since 3.2
*/
public DefaultSocketFactory(Proxy proxy)
{
connProxy = proxy;
}
/**
* Creates an unconnected Socket.
*
* @return A new unconnected Socket.
* @throws IOException If an I/O error occurs while creating the Socket.
* @since 3.2
*/
@Override
public Socket createSocket() throws IOException
{
if (connProxy != null)
{
return new Socket(connProxy);
}
return new Socket();
}
/***
* Creates a Socket connected to the given host and port.
*
* @param host The hostname to connect to.
* @param port The port to connect to.
* @return A Socket connected to the given host and port.
* @throws UnknownHostException If the hostname cannot be resolved.
* @throws IOException If an I/O error occurs while creating the Socket.
***/
@Override
public Socket createSocket(String host, int port)
throws UnknownHostException, IOException
{
if (connProxy != null)
{
Socket s = new Socket(connProxy);
s.connect(new InetSocketAddress(host, port));
return s;
}
return new Socket(host, port);
}
/***
* Creates a Socket connected to the given host and port.
*
* @param address The address of the host to connect to.
* @param port The port to connect to.
* @return A Socket connected to the given host and port.
* @throws IOException If an I/O error occurs while creating the Socket.
***/
@Override
public Socket createSocket(InetAddress address, int port)
throws IOException
{
if (connProxy != null)
{
Socket s = new Socket(connProxy);
s.connect(new InetSocketAddress(address, port));
return s;
}
return new Socket(address, port);
}
/***
* Creates a Socket connected to the given host and port and
* originating from the specified local address and port.
*
* @param host The hostname to connect to.
* @param port The port to connect to.
* @param localAddr The local address to use.
* @param localPort The local port to use.
* @return A Socket connected to the given host and port.
* @throws UnknownHostException If the hostname cannot be resolved.
* @throws IOException If an I/O error occurs while creating the Socket.
***/
@Override
public Socket createSocket(String host, int port,
InetAddress localAddr, int localPort)
throws UnknownHostException, IOException
{
if (connProxy != null)
{
Socket s = new Socket(connProxy);
s.bind(new InetSocketAddress(localAddr, localPort));
s.connect(new InetSocketAddress(host, port));
return s;
}
return new Socket(host, port, localAddr, localPort);
}
/***
* Creates a Socket connected to the given host and port and
* originating from the specified local address and port.
*
* @param address The address of the host to connect to.
* @param port The port to connect to.
* @param localAddr The local address to use.
* @param localPort The local port to use.
* @return A Socket connected to the given host and port.
* @throws IOException If an I/O error occurs while creating the Socket.
***/
@Override
public Socket createSocket(InetAddress address, int port,
InetAddress localAddr, int localPort)
throws IOException
{
if (connProxy != null)
{
Socket s = new Socket(connProxy);
s.bind(new InetSocketAddress(localAddr, localPort));
s.connect(new InetSocketAddress(address, port));
return s;
}
return new Socket(address, port, localAddr, localPort);
}
/***
* Creates a ServerSocket bound to a specified port. A port
* of 0 will create the ServerSocket on a system-determined free port.
*
* @param port The port on which to listen, or 0 to use any free port.
* @return A ServerSocket that will listen on a specified port.
* @throws IOException If an I/O error occurs while creating
* the ServerSocket.
***/
public ServerSocket createServerSocket(int port) throws IOException
{
return new ServerSocket(port);
}
/***
* Creates a ServerSocket bound to a specified port with a given
* maximum queue length for incoming connections. A port of 0 will
* create the ServerSocket on a system-determined free port.
*
* @param port The port on which to listen, or 0 to use any free port.
* @param backlog The maximum length of the queue for incoming connections.
* @return A ServerSocket that will listen on a specified port.
* @throws IOException If an I/O error occurs while creating
* the ServerSocket.
***/
public ServerSocket createServerSocket(int port, int backlog)
throws IOException
{
return new ServerSocket(port, backlog);
}
/***
* Creates a ServerSocket bound to a specified port on a given local
* address with a given maximum queue length for incoming connections.
* A port of 0 will
* create the ServerSocket on a system-determined free port.
*
* @param port The port on which to listen, or 0 to use any free port.
* @param backlog The maximum length of the queue for incoming connections.
* @param bindAddr The local address to which the ServerSocket should bind.
* @return A ServerSocket that will listen on a specified port.
* @throws IOException If an I/O error occurs while creating
* the ServerSocket.
***/
public ServerSocket createServerSocket(int port, int backlog,
InetAddress bindAddr)
throws IOException
{
return new ServerSocket(port, backlog, bindAddr);
}
}

@ -0,0 +1,56 @@
/*
* 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 org.apache.commons.net;
import java.io.IOException;
/***
* This exception is used to indicate that the reply from a server
* could not be interpreted. Most of the NetComponents classes attempt
* to be as lenient as possible when receiving server replies. Many
* server implementations deviate from IETF protocol specifications, making
* it necessary to be as flexible as possible. However, there will be
* certain situations where it is not possible to continue an operation
* because the server reply could not be interpreted in a meaningful manner.
* In these cases, a MalformedServerReplyException should be thrown.
*
*
***/
public class MalformedServerReplyException extends IOException
{
private static final long serialVersionUID = 6006765264250543945L;
/*** Constructs a MalformedServerReplyException with no message ***/
public MalformedServerReplyException()
{
super();
}
/***
* Constructs a MalformedServerReplyException with a specified message.
*
* @param message The message explaining the reason for the exception.
***/
public MalformedServerReplyException(String message)
{
super(message);
}
}

@ -0,0 +1,202 @@
/*
* 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 org.apache.commons.net;
import java.io.PrintStream;
import java.io.PrintWriter;
/***
* This is a support class for some of the example programs. It is
* a sample implementation of the ProtocolCommandListener interface
* which just prints out to a specified stream all command/reply traffic.
*
* @since 2.0
***/
public class PrintCommandListener implements ProtocolCommandListener
{
private final PrintWriter __writer;
private final boolean __nologin;
private final char __eolMarker;
private final boolean __directionMarker;
/**
* Create the default instance which prints everything.
*
* @param stream where to write the commands and responses
* e.g. System.out
* @since 3.0
*/
public PrintCommandListener(PrintStream stream)
{
this(new PrintWriter(stream));
}
/**
* Create an instance which optionally suppresses login command text
* and indicates where the EOL starts with the specified character.
*
* @param stream where to write the commands and responses
* @param suppressLogin if {@code true}, only print command name for login
*
* @since 3.0
*/
public PrintCommandListener(PrintStream stream, boolean suppressLogin) {
this(new PrintWriter(stream), suppressLogin);
}
/**
* Create an instance which optionally suppresses login command text
* and indicates where the EOL starts with the specified character.
*
* @param stream where to write the commands and responses
* @param suppressLogin if {@code true}, only print command name for login
* @param eolMarker if non-zero, add a marker just before the EOL.
*
* @since 3.0
*/
public PrintCommandListener(PrintStream stream, boolean suppressLogin, char eolMarker) {
this(new PrintWriter(stream), suppressLogin, eolMarker);
}
/**
* Create an instance which optionally suppresses login command text
* and indicates where the EOL starts with the specified character.
*
* @param stream where to write the commands and responses
* @param suppressLogin if {@code true}, only print command name for login
* @param eolMarker if non-zero, add a marker just before the EOL.
* @param showDirection if {@code true}, add {@code "> "} or {@code "< "} as appropriate to the output
*
* @since 3.0
*/
public PrintCommandListener(PrintStream stream, boolean suppressLogin, char eolMarker, boolean showDirection) {
this(new PrintWriter(stream), suppressLogin, eolMarker, showDirection);
}
/**
* Create the default instance which prints everything.
*
* @param writer where to write the commands and responses
*/
public PrintCommandListener(PrintWriter writer)
{
this(writer, false);
}
/**
* Create an instance which optionally suppresses login command text.
*
* @param writer where to write the commands and responses
* @param suppressLogin if {@code true}, only print command name for login
*
* @since 3.0
*/
public PrintCommandListener(PrintWriter writer, boolean suppressLogin)
{
this(writer, suppressLogin, (char) 0);
}
/**
* Create an instance which optionally suppresses login command text
* and indicates where the EOL starts with the specified character.
*
* @param writer where to write the commands and responses
* @param suppressLogin if {@code true}, only print command name for login
* @param eolMarker if non-zero, add a marker just before the EOL.
*
* @since 3.0
*/
public PrintCommandListener(PrintWriter writer, boolean suppressLogin, char eolMarker)
{
this(writer, suppressLogin, eolMarker, false);
}
/**
* Create an instance which optionally suppresses login command text
* and indicates where the EOL starts with the specified character.
*
* @param writer where to write the commands and responses
* @param suppressLogin if {@code true}, only print command name for login
* @param eolMarker if non-zero, add a marker just before the EOL.
* @param showDirection if {@code true}, add {@code ">} " or {@code "< "} as appropriate to the output
*
* @since 3.0
*/
public PrintCommandListener(PrintWriter writer, boolean suppressLogin, char eolMarker, boolean showDirection)
{
__writer = writer;
__nologin = suppressLogin;
__eolMarker = eolMarker;
__directionMarker = showDirection;
}
@Override
public void protocolCommandSent(ProtocolCommandEvent event)
{
if (__directionMarker) {
__writer.print("> ");
}
if (__nologin) {
String cmd = event.getCommand();
if ("PASS".equalsIgnoreCase(cmd) || "USER".equalsIgnoreCase(cmd)) {
__writer.print(cmd);
__writer.println(" *******"); // Don't bother with EOL marker for this!
} else {
final String IMAP_LOGIN = "LOGIN";
if (IMAP_LOGIN.equalsIgnoreCase(cmd)) { // IMAP
String msg = event.getMessage();
msg=msg.substring(0, msg.indexOf(IMAP_LOGIN)+IMAP_LOGIN.length());
__writer.print(msg);
__writer.println(" *******"); // Don't bother with EOL marker for this!
} else {
__writer.print(getPrintableString(event.getMessage()));
}
}
} else {
__writer.print(getPrintableString(event.getMessage()));
}
__writer.flush();
}
private String getPrintableString(String msg){
if (__eolMarker == 0) {
return msg;
}
int pos = msg.indexOf(SocketClient.NETASCII_EOL);
if (pos > 0) {
StringBuilder sb = new StringBuilder();
sb.append(msg.substring(0,pos));
sb.append(__eolMarker);
sb.append(msg.substring(pos));
return sb.toString();
}
return msg;
}
@Override
public void protocolReplyReceived(ProtocolCommandEvent event)
{
if (__directionMarker) {
__writer.print("< ");
}
__writer.print(event.getMessage());
__writer.flush();
}
}

@ -0,0 +1,148 @@
/*
* 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 org.apache.commons.net;
import java.util.EventObject;
/***
* There exists a large class of IETF protocols that work by sending an
* ASCII text command and arguments to a server, and then receiving an
* ASCII text reply. For debugging and other purposes, it is extremely
* useful to log or keep track of the contents of the protocol messages.
* The ProtocolCommandEvent class coupled with the
* {@link org.apache.commons.net.ProtocolCommandListener}
* interface facilitate this process.
*
*
* @see ProtocolCommandListener
* @see ProtocolCommandSupport
***/
public class ProtocolCommandEvent extends EventObject
{
private static final long serialVersionUID = 403743538418947240L;
private final int __replyCode;
private final boolean __isCommand;
private final String __message, __command;
/***
* Creates a ProtocolCommandEvent signalling a command was sent to
* the server. ProtocolCommandEvents created with this constructor
* should only be sent after a command has been sent, but before the
* reply has been received.
*
* @param source The source of the event.
* @param command The string representation of the command type sent, not
* including the arguments (e.g., "STAT" or "GET").
* @param message The entire command string verbatim as sent to the server,
* including all arguments.
***/
public ProtocolCommandEvent(Object source, String command, String message)
{
super(source);
__replyCode = 0;
__message = message;
__isCommand = true;
__command = command;
}
/***
* Creates a ProtocolCommandEvent signalling a reply to a command was
* received. ProtocolCommandEvents created with this constructor
* should only be sent after a complete command reply has been received
* fromt a server.
*
* @param source The source of the event.
* @param replyCode The integer code indicating the natureof the reply.
* This will be the protocol integer value for protocols
* that use integer reply codes, or the reply class constant
* corresponding to the reply for protocols like POP3 that use
* strings like OK rather than integer codes (i.e., POP3Repy.OK).
* @param message The entire reply as received from the server.
***/
public ProtocolCommandEvent(Object source, int replyCode, String message)
{
super(source);
__replyCode = replyCode;
__message = message;
__isCommand = false;
__command = null;
}
/***
* Returns the string representation of the command type sent (e.g., "STAT"
* or "GET"). If the ProtocolCommandEvent is a reply event, then null
* is returned.
*
* @return The string representation of the command type sent, or null
* if this is a reply event.
***/
public String getCommand()
{
return __command;
}
/***
* Returns the reply code of the received server reply. Undefined if
* this is not a reply event.
*
* @return The reply code of the received server reply. Undefined if
* not a reply event.
***/
public int getReplyCode()
{
return __replyCode;
}
/***
* Returns true if the ProtocolCommandEvent was generated as a result
* of sending a command.
*
* @return true If the ProtocolCommandEvent was generated as a result
* of sending a command. False otherwise.
***/
public boolean isCommand()
{
return __isCommand;
}
/***
* Returns true if the ProtocolCommandEvent was generated as a result
* of receiving a reply.
*
* @return true If the ProtocolCommandEvent was generated as a result
* of receiving a reply. False otherwise.
***/
public boolean isReply()
{
return !isCommand();
}
/***
* Returns the entire message sent to or received from the server.
* Includes the line terminator.
*
* @return The entire message sent to or received from the server.
***/
public String getMessage()
{
return __message;
}
}

@ -0,0 +1,58 @@
/*
* 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 org.apache.commons.net;
import java.util.EventListener;
/***
* There exists a large class of IETF protocols that work by sending an
* ASCII text command and arguments to a server, and then receiving an
* ASCII text reply. For debugging and other purposes, it is extremely
* useful to log or keep track of the contents of the protocol messages.
* The ProtocolCommandListener interface coupled with the
* {@link ProtocolCommandEvent} class facilitate this process.
* <p>
* To receive ProtocolCommandEvents, you merely implement the
* ProtocolCommandListener interface and register the class as a listener
* with a ProtocolCommandEvent source such as
* {@link org.apache.commons.net.ftp.FTPClient}.
*
*
* @see ProtocolCommandEvent
* @see ProtocolCommandSupport
***/
public interface ProtocolCommandListener extends EventListener
{
/***
* This method is invoked by a ProtocolCommandEvent source after
* sending a protocol command to a server.
*
* @param event The ProtocolCommandEvent fired.
***/
public void protocolCommandSent(ProtocolCommandEvent event);
/***
* This method is invoked by a ProtocolCommandEvent source after
* receiving a reply from a server.
*
* @param event The ProtocolCommandEvent fired.
***/
public void protocolReplyReceived(ProtocolCommandEvent event);
}

@ -0,0 +1,135 @@
/*
* 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 org.apache.commons.net;
import java.io.Serializable;
import java.util.EventListener;
import org.apache.commons.net.util.ListenerList;
/***
* ProtocolCommandSupport is a convenience class for managing a list of
* ProtocolCommandListeners and firing ProtocolCommandEvents. You can
* simply delegate ProtocolCommandEvent firing and listener
* registering/unregistering tasks to this class.
*
*
* @see ProtocolCommandEvent
* @see ProtocolCommandListener
***/
public class ProtocolCommandSupport implements Serializable
{
private static final long serialVersionUID = -8017692739988399978L;
private final Object __source;
private final ListenerList __listeners;
/***
* Creates a ProtocolCommandSupport instance using the indicated source
* as the source of ProtocolCommandEvents.
*
* @param source The source to use for all generated ProtocolCommandEvents.
***/
public ProtocolCommandSupport(Object source)
{
__listeners = new ListenerList();
__source = source;
}
/***
* Fires a ProtocolCommandEvent signalling the sending of a command to all
* registered listeners, invoking their
* {@link org.apache.commons.net.ProtocolCommandListener#protocolCommandSent protocolCommandSent() }
* methods.
*
* @param command The string representation of the command type sent, not
* including the arguments (e.g., "STAT" or "GET").
* @param message The entire command string verbatim as sent to the server,
* including all arguments.
***/
public void fireCommandSent(String command, String message)
{
ProtocolCommandEvent event;
event = new ProtocolCommandEvent(__source, command, message);
for (EventListener listener : __listeners)
{
((ProtocolCommandListener)listener).protocolCommandSent(event);
}
}
/***
* Fires a ProtocolCommandEvent signalling the reception of a command reply
* to all registered listeners, invoking their
* {@link org.apache.commons.net.ProtocolCommandListener#protocolReplyReceived protocolReplyReceived() }
* methods.
*
* @param replyCode The integer code indicating the natureof the reply.
* This will be the protocol integer value for protocols
* that use integer reply codes, or the reply class constant
* corresponding to the reply for protocols like POP3 that use
* strings like OK rather than integer codes (i.e., POP3Repy.OK).
* @param message The entire reply as received from the server.
***/
public void fireReplyReceived(int replyCode, String message)
{
ProtocolCommandEvent event;
event = new ProtocolCommandEvent(__source, replyCode, message);
for (EventListener listener : __listeners)
{
((ProtocolCommandListener)listener).protocolReplyReceived(event);
}
}
/***
* Adds a ProtocolCommandListener.
*
* @param listener The ProtocolCommandListener to add.
***/
public void addProtocolCommandListener(ProtocolCommandListener listener)
{
__listeners.addListener(listener);
}
/***
* Removes a ProtocolCommandListener.
*
* @param listener The ProtocolCommandListener to remove.
***/
public void removeProtocolCommandListener(ProtocolCommandListener listener)
{
__listeners.removeListener(listener);
}
/***
* Returns the number of ProtocolCommandListeners currently registered.
*
* @return The number of ProtocolCommandListeners currently registered.
***/
public int getListenerCount()
{
return __listeners.getListenerCount();
}
}

@ -0,0 +1,887 @@
/*
* 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 org.apache.commons.net;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.Socket;
import java.net.SocketException;
import java.nio.charset.Charset;
import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
/**
* The SocketClient provides the basic operations that are required of
* client objects accessing sockets. It is meant to be
* subclassed to avoid having to rewrite the same code over and over again
* to open a socket, close a socket, set timeouts, etc. Of special note
* is the {@link #setSocketFactory setSocketFactory }
* method, which allows you to control the type of Socket the SocketClient
* creates for initiating network connections. This is especially useful
* for adding SSL or proxy support as well as better support for applets. For
* example, you could create a
* {@link SocketFactory} that
* requests browser security capabilities before creating a socket.
* All classes derived from SocketClient should use the
* {@link #_socketFactory_ _socketFactory_ } member variable to
* create Socket and ServerSocket instances rather than instantiating
* them by directly invoking a constructor. By honoring this contract
* you guarantee that a user will always be able to provide his own
* Socket implementations by substituting his own SocketFactory.
* @see SocketFactory
*/
public abstract class SocketClient
{
/**
* The end of line character sequence used by most IETF protocols. That
* is a carriage return followed by a newline: "\r\n"
*/
public static final String NETASCII_EOL = "\r\n";
/** The default SocketFactory shared by all SocketClient instances. */
private static final SocketFactory __DEFAULT_SOCKET_FACTORY =
SocketFactory.getDefault();
/** The default {@link ServerSocketFactory} */
private static final ServerSocketFactory __DEFAULT_SERVER_SOCKET_FACTORY =
ServerSocketFactory.getDefault();
/**
* A ProtocolCommandSupport object used to manage the registering of
* ProtocolCommandListeners and the firing of ProtocolCommandEvents.
*/
private ProtocolCommandSupport __commandSupport;
/** The timeout to use after opening a socket. */
protected int _timeout_;
/** The socket used for the connection. */
protected Socket _socket_;
/** The hostname used for the connection (null = no hostname supplied). */
protected String _hostname_;
/** The default port the client should connect to. */
protected int _defaultPort_;
/** The socket's InputStream. */
protected InputStream _input_;
/** The socket's OutputStream. */
protected OutputStream _output_;
/** The socket's SocketFactory. */
protected SocketFactory _socketFactory_;
/** The socket's ServerSocket Factory. */
protected ServerSocketFactory _serverSocketFactory_;
/** The socket's connect timeout (0 = infinite timeout) */
private static final int DEFAULT_CONNECT_TIMEOUT = 0;
protected int connectTimeout = DEFAULT_CONNECT_TIMEOUT;
/** Hint for SO_RCVBUF size */
private int receiveBufferSize = -1;
/** Hint for SO_SNDBUF size */
private int sendBufferSize = -1;
/** The proxy to use when connecting. */
private Proxy connProxy;
/**
* Charset to use for byte IO.
*/
private Charset charset = Charset.defaultCharset();
/**
* Default constructor for SocketClient. Initializes
* _socket_ to null, _timeout_ to 0, _defaultPort to 0,
* _isConnected_ to false, charset to {@code Charset.defaultCharset()}
* and _socketFactory_ to a shared instance of
* {@link org.apache.commons.net.DefaultSocketFactory}.
*/
public SocketClient()
{
_socket_ = null;
_hostname_ = null;
_input_ = null;
_output_ = null;
_timeout_ = 0;
_defaultPort_ = 0;
_socketFactory_ = __DEFAULT_SOCKET_FACTORY;
_serverSocketFactory_ = __DEFAULT_SERVER_SOCKET_FACTORY;
}
/**
* Because there are so many connect() methods, the _connectAction_()
* method is provided as a means of performing some action immediately
* after establishing a connection, rather than reimplementing all
* of the connect() methods. The last action performed by every
* connect() method after opening a socket is to call this method.
* <p>
* This method sets the timeout on the just opened socket to the default
* timeout set by {@link #setDefaultTimeout setDefaultTimeout() },
* sets _input_ and _output_ to the socket's InputStream and OutputStream
* respectively, and sets _isConnected_ to true.
* <p>
* Subclasses overriding this method should start by calling
* <code> super._connectAction_() </code> first to ensure the
* initialization of the aforementioned protected variables.
* @throws IOException (SocketException) if a problem occurs with the socket
*/
protected void _connectAction_() throws IOException
{
_socket_.setSoTimeout(_timeout_);
_input_ = _socket_.getInputStream();
_output_ = _socket_.getOutputStream();
}
/**
* Opens a Socket connected to a remote host at the specified port and
* originating from the current host at a system assigned port.
* Before returning, {@link #_connectAction_ _connectAction_() }
* is called to perform connection initialization actions.
* <p>
* @param host The remote host.
* @param port The port to connect to on the remote host.
* @throws SocketException If the socket timeout could not be set.
* @throws IOException If the socket could not be opened. In most
* cases you will only want to catch IOException since SocketException is
* derived from it.
*/
public void connect(InetAddress host, int port)
throws SocketException, IOException
{
_hostname_ = null;
_connect(host, port, null, -1);
}
/**
* Opens a Socket connected to a remote host at the specified port and
* originating from the current host at a system assigned port.
* Before returning, {@link #_connectAction_ _connectAction_() }
* is called to perform connection initialization actions.
* <p>
* @param hostname The name of the remote host.
* @param port The port to connect to on the remote host.
* @throws SocketException If the socket timeout could not be set.
* @throws IOException If the socket could not be opened. In most
* cases you will only want to catch IOException since SocketException is
* derived from it.
* @throws java.net.UnknownHostException If the hostname cannot be resolved.
*/
public void connect(String hostname, int port)
throws SocketException, IOException
{
_hostname_ = hostname;
_connect(InetAddress.getByName(hostname), port, null, -1);
}
/**
* Opens a Socket connected to a remote host at the specified port and
* originating from the specified local address and port.
* Before returning, {@link #_connectAction_ _connectAction_() }
* is called to perform connection initialization actions.
* <p>
* @param host The remote host.
* @param port The port to connect to on the remote host.
* @param localAddr The local address to use.
* @param localPort The local port to use.
* @throws SocketException If the socket timeout could not be set.
* @throws IOException If the socket could not be opened. In most
* cases you will only want to catch IOException since SocketException is
* derived from it.
*/
public void connect(InetAddress host, int port,
InetAddress localAddr, int localPort)
throws SocketException, IOException
{
_hostname_ = null;
_connect(host, port, localAddr, localPort);
}
// helper method to allow code to be shared with connect(String,...) methods
private void _connect(InetAddress host, int port, InetAddress localAddr, int localPort)
throws SocketException, IOException
{
_socket_ = _socketFactory_.createSocket();
if (receiveBufferSize != -1) {
_socket_.setReceiveBufferSize(receiveBufferSize);
}
if (sendBufferSize != -1) {
_socket_.setSendBufferSize(sendBufferSize);
}
if (localAddr != null) {
_socket_.bind(new InetSocketAddress(localAddr, localPort));
}
_socket_.connect(new InetSocketAddress(host, port), connectTimeout);
_connectAction_();
}
/**
* Opens a Socket connected to a remote host at the specified port and
* originating from the specified local address and port.
* Before returning, {@link #_connectAction_ _connectAction_() }
* is called to perform connection initialization actions.
* <p>
* @param hostname The name of the remote host.
* @param port The port to connect to on the remote host.
* @param localAddr The local address to use.
* @param localPort The local port to use.
* @throws SocketException If the socket timeout could not be set.
* @throws IOException If the socket could not be opened. In most
* cases you will only want to catch IOException since SocketException is
* derived from it.
* @throws java.net.UnknownHostException If the hostname cannot be resolved.
*/
public void connect(String hostname, int port,
InetAddress localAddr, int localPort)
throws SocketException, IOException
{
_hostname_ = hostname;
_connect(InetAddress.getByName(hostname), port, localAddr, localPort);
}
/**
* Opens a Socket connected to a remote host at the current default port
* and originating from the current host at a system assigned port.
* Before returning, {@link #_connectAction_ _connectAction_() }
* is called to perform connection initialization actions.
* <p>
* @param host The remote host.
* @throws SocketException If the socket timeout could not be set.
* @throws IOException If the socket could not be opened. In most
* cases you will only want to catch IOException since SocketException is
* derived from it.
*/
public void connect(InetAddress host) throws SocketException, IOException
{
_hostname_ = null;
connect(host, _defaultPort_);
}
/**
* Opens a Socket connected to a remote host at the current default
* port and originating from the current host at a system assigned port.
* Before returning, {@link #_connectAction_ _connectAction_() }
* is called to perform connection initialization actions.
* <p>
* @param hostname The name of the remote host.
* @throws SocketException If the socket timeout could not be set.
* @throws IOException If the socket could not be opened. In most
* cases you will only want to catch IOException since SocketException is
* derived from it.
* @throws java.net.UnknownHostException If the hostname cannot be resolved.
*/
public void connect(String hostname) throws SocketException, IOException
{
connect(hostname, _defaultPort_);
}
/**
* Disconnects the socket connection.
* You should call this method after you've finished using the class
* instance and also before you call
* {@link #connect connect() }
* again. _isConnected_ is set to false, _socket_ is set to null,
* _input_ is set to null, and _output_ is set to null.
* <p>
* @throws IOException If there is an error closing the socket.
*/
public void disconnect() throws IOException
{
closeQuietly(_socket_);
closeQuietly(_input_);
closeQuietly(_output_);
_socket_ = null;
_hostname_ = null;
_input_ = null;
_output_ = null;
}
private void closeQuietly(Socket socket) {
if (socket != null){
try {
socket.close();
} catch (IOException e) {
// Ignored
}
}
}
private void closeQuietly(Closeable close){
if (close != null){
try {
close.close();
} catch (IOException e) {
// Ignored
}
}
}
/**
* Returns true if the client is currently connected to a server.
* <p>
* Delegates to {@link Socket#isConnected()}
* @return True if the client is currently connected to a server,
* false otherwise.
*/
public boolean isConnected()
{
if (_socket_ == null) {
return false;
}
return _socket_.isConnected();
}
/**
* Make various checks on the socket to test if it is available for use.
* Note that the only sure test is to use it, but these checks may help
* in some cases.
* @see <a href="https://issues.apache.org/jira/browse/NET-350">NET-350</a>
* @return {@code true} if the socket appears to be available for use
* @since 3.0
*/
public boolean isAvailable(){
if (isConnected()) {
try
{
if (_socket_.getInetAddress() == null) {
return false;
}
if (_socket_.getPort() == 0) {
return false;
}
if (_socket_.getRemoteSocketAddress() == null) {
return false;
}
if (_socket_.isClosed()) {
return false;
}
/* these aren't exact checks (a Socket can be half-open),
but since we usually require two-way data transfer,
we check these here too: */
if (_socket_.isInputShutdown()) {
return false;
}
if (_socket_.isOutputShutdown()) {
return false;
}
/* ignore the result, catch exceptions: */
_socket_.getInputStream();
_socket_.getOutputStream();
}
catch (IOException ioex)
{
return false;
}
return true;
} else {
return false;
}
}
/**
* Sets the default port the SocketClient should connect to when a port
* is not specified. The {@link #_defaultPort_ _defaultPort_ }
* variable stores this value. If never set, the default port is equal
* to zero.
* <p>
* @param port The default port to set.
*/
public void setDefaultPort(int port)
{
_defaultPort_ = port;
}
/**
* Returns the current value of the default port (stored in
* {@link #_defaultPort_ _defaultPort_ }).
* <p>
* @return The current value of the default port.
*/
public int getDefaultPort()
{
return _defaultPort_;
}
/**
* Set the default timeout in milliseconds to use when opening a socket.
* This value is only used previous to a call to
* {@link #connect connect()}
* and should not be confused with {@link #setSoTimeout setSoTimeout()}
* which operates on an the currently opened socket. _timeout_ contains
* the new timeout value.
* <p>
* @param timeout The timeout in milliseconds to use for the socket
* connection.
*/
public void setDefaultTimeout(int timeout)
{
_timeout_ = timeout;
}
/**
* Returns the default timeout in milliseconds that is used when
* opening a socket.
* <p>
* @return The default timeout in milliseconds that is used when
* opening a socket.
*/
public int getDefaultTimeout()
{
return _timeout_;
}
/**
* Set the timeout in milliseconds of a currently open connection.
* Only call this method after a connection has been opened
* by {@link #connect connect()}.
* <p>
* To set the initial timeout, use {@link #setDefaultTimeout(int)} instead.
*
* @param timeout The timeout in milliseconds to use for the currently
* open socket connection.
* @throws SocketException If the operation fails.
* @throws NullPointerException if the socket is not currently open
*/
public void setSoTimeout(int timeout) throws SocketException
{
_socket_.setSoTimeout(timeout);
}
/**
* Set the underlying socket send buffer size.
* <p>
* @param size The size of the buffer in bytes.
* @throws SocketException never thrown, but subclasses might want to do so
* @since 2.0
*/
public void setSendBufferSize(int size) throws SocketException {
sendBufferSize = size;
}
/**
* Get the current sendBuffer size
* @return the size, or -1 if not initialised
* @since 3.0
*/
protected int getSendBufferSize(){
return sendBufferSize;
}
/**
* Sets the underlying socket receive buffer size.
* <p>
* @param size The size of the buffer in bytes.
* @throws SocketException never (but subclasses may wish to do so)
* @since 2.0
*/
public void setReceiveBufferSize(int size) throws SocketException {
receiveBufferSize = size;
}
/**
* Get the current receivedBuffer size
* @return the size, or -1 if not initialised
* @since 3.0
*/
protected int getReceiveBufferSize(){
return receiveBufferSize;
}
/**
* Returns the timeout in milliseconds of the currently opened socket.
* <p>
* @return The timeout in milliseconds of the currently opened socket.
* @throws SocketException If the operation fails.
* @throws NullPointerException if the socket is not currently open
*/
public int getSoTimeout() throws SocketException
{
return _socket_.getSoTimeout();
}
/**
* Enables or disables the Nagle's algorithm (TCP_NODELAY) on the
* currently opened socket.
* <p>
* @param on True if Nagle's algorithm is to be enabled, false if not.
* @throws SocketException If the operation fails.
* @throws NullPointerException if the socket is not currently open
*/
public void setTcpNoDelay(boolean on) throws SocketException
{
_socket_.setTcpNoDelay(on);
}
/**
* Returns true if Nagle's algorithm is enabled on the currently opened
* socket.
* <p>
* @return True if Nagle's algorithm is enabled on the currently opened
* socket, false otherwise.
* @throws SocketException If the operation fails.
* @throws NullPointerException if the socket is not currently open
*/
public boolean getTcpNoDelay() throws SocketException
{
return _socket_.getTcpNoDelay();
}
/**
* Sets the SO_KEEPALIVE flag on the currently opened socket.
*
* From the Javadocs, the default keepalive time is 2 hours (although this is
* implementation dependent). It looks as though the Windows WSA sockets implementation
* allows a specific keepalive value to be set, although this seems not to be the case on
* other systems.
* @param keepAlive If true, keepAlive is turned on
* @throws SocketException if there is a problem with the socket
* @throws NullPointerException if the socket is not currently open
* @since 2.2
*/
public void setKeepAlive(boolean keepAlive) throws SocketException {
_socket_.setKeepAlive(keepAlive);
}
/**
* Returns the current value of the SO_KEEPALIVE flag on the currently opened socket.
* Delegates to {@link Socket#getKeepAlive()}
* @return True if SO_KEEPALIVE is enabled.
* @throws SocketException if there is a problem with the socket
* @throws NullPointerException if the socket is not currently open
* @since 2.2
*/
public boolean getKeepAlive() throws SocketException {
return _socket_.getKeepAlive();
}
/**
* Sets the SO_LINGER timeout on the currently opened socket.
* <p>
* @param on True if linger is to be enabled, false if not.
* @param val The linger timeout (in hundredths of a second?)
* @throws SocketException If the operation fails.
* @throws NullPointerException if the socket is not currently open
*/
public void setSoLinger(boolean on, int val) throws SocketException
{
_socket_.setSoLinger(on, val);
}
/**
* Returns the current SO_LINGER timeout of the currently opened socket.
* <p>
* @return The current SO_LINGER timeout. If SO_LINGER is disabled returns
* -1.
* @throws SocketException If the operation fails.
* @throws NullPointerException if the socket is not currently open
*/
public int getSoLinger() throws SocketException
{
return _socket_.getSoLinger();
}
/**
* Returns the port number of the open socket on the local host used
* for the connection.
* Delegates to {@link Socket#getLocalPort()}
* <p>
* @return The port number of the open socket on the local host used
* for the connection.
* @throws NullPointerException if the socket is not currently open
*/
public int getLocalPort()
{
return _socket_.getLocalPort();
}
/**
* Returns the local address to which the client's socket is bound.
* Delegates to {@link Socket#getLocalAddress()}
* <p>
* @return The local address to which the client's socket is bound.
* @throws NullPointerException if the socket is not currently open
*/
public InetAddress getLocalAddress()
{
return _socket_.getLocalAddress();
}
/**
* Returns the port number of the remote host to which the client is
* connected.
* Delegates to {@link Socket#getPort()}
* <p>
* @return The port number of the remote host to which the client is
* connected.
* @throws NullPointerException if the socket is not currently open
*/
public int getRemotePort()
{
return _socket_.getPort();
}
/**
* @return The remote address to which the client is connected.
* Delegates to {@link Socket#getInetAddress()}
* @throws NullPointerException if the socket is not currently open
*/
public InetAddress getRemoteAddress()
{
return _socket_.getInetAddress();
}
/**
* Verifies that the remote end of the given socket is connected to the
* the same host that the SocketClient is currently connected to. This
* is useful for doing a quick security check when a client needs to
* accept a connection from a server, such as an FTP data connection or
* a BSD R command standard error stream.
* <p>
* @param socket the item to check against
* @return True if the remote hosts are the same, false if not.
*/
public boolean verifyRemote(Socket socket)
{
InetAddress host1, host2;
host1 = socket.getInetAddress();
host2 = getRemoteAddress();
return host1.equals(host2);
}
/**
* Sets the SocketFactory used by the SocketClient to open socket
* connections. If the factory value is null, then a default
* factory is used (only do this to reset the factory after having
* previously altered it).
* Any proxy setting is discarded.
* <p>
* @param factory The new SocketFactory the SocketClient should use.
*/
public void setSocketFactory(SocketFactory factory)
{
if (factory == null) {
_socketFactory_ = __DEFAULT_SOCKET_FACTORY;
} else {
_socketFactory_ = factory;
}
// re-setting the socket factory makes the proxy setting useless,
// so set the field to null so that getProxy() doesn't return a
// Proxy that we're actually not using.
connProxy = null;
}
/**
* Sets the ServerSocketFactory used by the SocketClient to open ServerSocket
* connections. If the factory value is null, then a default
* factory is used (only do this to reset the factory after having
* previously altered it).
* <p>
* @param factory The new ServerSocketFactory the SocketClient should use.
* @since 2.0
*/
public void setServerSocketFactory(ServerSocketFactory factory) {
if (factory == null) {
_serverSocketFactory_ = __DEFAULT_SERVER_SOCKET_FACTORY;
} else {
_serverSocketFactory_ = factory;
}
}
/**
* Sets the connection timeout in milliseconds, which will be passed to the {@link Socket} object's
* connect() method.
* @param connectTimeout The connection timeout to use (in ms)
* @since 2.0
*/
public void setConnectTimeout(int connectTimeout) {
this.connectTimeout = connectTimeout;
}
/**
* Get the underlying socket connection timeout.
* @return timeout (in ms)
* @since 2.0
*/
public int getConnectTimeout() {
return connectTimeout;
}
/**
* Get the underlying {@link ServerSocketFactory}
* @return The server socket factory
* @since 2.2
*/
public ServerSocketFactory getServerSocketFactory() {
return _serverSocketFactory_;
}
/**
* Adds a ProtocolCommandListener.
*
* @param listener The ProtocolCommandListener to add.
* @since 3.0
*/
public void addProtocolCommandListener(ProtocolCommandListener listener) {
getCommandSupport().addProtocolCommandListener(listener);
}
/**
* Removes a ProtocolCommandListener.
*
* @param listener The ProtocolCommandListener to remove.
* @since 3.0
*/
public void removeProtocolCommandListener(ProtocolCommandListener listener) {
getCommandSupport().removeProtocolCommandListener(listener);
}
/**
* If there are any listeners, send them the reply details.
*
* @param replyCode the code extracted from the reply
* @param reply the full reply text
* @since 3.0
*/
protected void fireReplyReceived(int replyCode, String reply) {
if (getCommandSupport().getListenerCount() > 0) {
getCommandSupport().fireReplyReceived(replyCode, reply);
}
}
/**
* If there are any listeners, send them the command details.
*
* @param command the command name
* @param message the complete message, including command name
* @since 3.0
*/
protected void fireCommandSent(String command, String message) {
if (getCommandSupport().getListenerCount() > 0) {
getCommandSupport().fireCommandSent(command, message);
}
}
/**
* Create the CommandSupport instance if required
*/
protected void createCommandSupport(){
__commandSupport = new ProtocolCommandSupport(this);
}
/**
* Subclasses can override this if they need to provide their own
* instance field for backwards compatibilty.
*
* @return the CommandSupport instance, may be {@code null}
* @since 3.0
*/
protected ProtocolCommandSupport getCommandSupport() {
return __commandSupport;
}
/**
* Sets the proxy for use with all the connections.
* The proxy is used for connections established after the
* call to this method.
*
* @param proxy the new proxy for connections.
* @since 3.2
*/
public void setProxy(Proxy proxy) {
setSocketFactory(new DefaultSocketFactory(proxy));
connProxy = proxy;
}
/**
* Gets the proxy for use with all the connections.
* @return the current proxy for connections.
*/
public Proxy getProxy() {
return connProxy;
}
/**
* Gets the charset name.
*
* @return the charset.
* @since 3.3
* @deprecated Since the code now requires Java 1.6 as a mininmum
*/
@Deprecated
public String getCharsetName() {
return charset.name();
}
/**
* Gets the charset.
*
* @return the charset.
* @since 3.3
*/
public Charset getCharset() {
return charset;
}
/**
* Sets the charset.
*
* @param charset the charset.
* @since 3.3
*/
public void setCharset(Charset charset) {
this.charset = charset;
}
/*
* N.B. Fields cannot be pulled up into a super-class without breaking binary compatibility,
* so the abstract method is needed to pass the instance to the methods which were moved here.
*/
}

@ -0,0 +1,35 @@
/*
* 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 org.apache.commons.net.ftp;
/**
* This interface adds the aspect of configurability by means of
* a supplied FTPClientConfig object to other classes in the
* system, especially listing parsers.
*/
public interface Configurable {
/**
* @param config the object containing the configuration data
* @throws IllegalArgumentException if the elements of the
* <code>config</code> are somehow inadequate to configure the
* Configurable object.
*/
public void configure(FTPClientConfig config);
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -0,0 +1,709 @@
/*
* 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 org.apache.commons.net.ftp;
import java.text.DateFormatSymbols;
import java.util.Collection;
import java.util.Locale;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.TreeMap;
/**
* <p>
* This class implements an alternate means of configuring the
* {@link FTPClient FTPClient} object and
* also subordinate objects which it uses. Any class implementing the
* {@link Configurable Configurable }
* interface can be configured by this object.
* </p><p>
* In particular this class was designed primarily to support configuration
* of FTP servers which express file timestamps in formats and languages
* other than those for the US locale, which although it is the most common
* is not universal. Unfortunately, nothing in the FTP spec allows this to
* be determined in an automated way, so manual configuration such as this
* is necessary.
* </p><p>
* This functionality was designed to allow existing clients to work exactly
* as before without requiring use of this component. This component should
* only need to be explicitly invoked by the user of this package for problem
* cases that previous implementations could not solve.
* </p>
* <h3>Examples of use of FTPClientConfig</h3>
* Use cases:
* You are trying to access a server that
* <ul>
* <li>lists files with timestamps that use month names in languages other
* than English</li>
* <li>lists files with timestamps that use date formats other
* than the American English "standard" <code>MM dd yyyy</code></li>
* <li>is in different timezone and you need accurate timestamps for
* dependency checking as in Ant</li>
* </ul>
* <p>
* Unpaged (whole list) access on a UNIX server that uses French month names
* but uses the "standard" <code>MMM d yyyy</code> date formatting
* <pre>
* FTPClient f=FTPClient();
* FTPClientConfig conf = new FTPClientConfig(FTPClientConfig.SYST_UNIX);
* conf.setServerLanguageCode("fr");
* f.configure(conf);
* f.connect(server);
* f.login(username, password);
* FTPFile[] files = listFiles(directory);
* </pre>
* <p>
* Paged access on a UNIX server that uses Danish month names
* and "European" date formatting in Denmark's time zone, when you
* are in some other time zone.
* <pre>
* FTPClient f=FTPClient();
* FTPClientConfig conf = new FTPClientConfig(FTPClientConfig.SYST_UNIX);
* conf.setServerLanguageCode("da");
* conf.setDefaultDateFormat("d MMM yyyy");
* conf.setRecentDateFormat("d MMM HH:mm");
* conf.setTimeZoneId("Europe/Copenhagen");
* f.configure(conf);
* f.connect(server);
* f.login(username, password);
* FTPListParseEngine engine =
* f.initiateListParsing("com.whatever.YourOwnParser", directory);
*
* while (engine.hasNext()) {
* FTPFile[] files = engine.getNext(25); // "page size" you want
* //do whatever you want with these files, display them, etc.
* //expensive FTPFile objects not created until needed.
* }
* </pre>
* <p>
* Unpaged (whole list) access on a VMS server that uses month names
* in a language not {@link #getSupportedLanguageCodes() supported} by the system.
* but uses the "standard" <code>MMM d yyyy</code> date formatting
* <pre>
* FTPClient f=FTPClient();
* FTPClientConfig conf = new FTPClientConfig(FTPClientConfig.SYST_VMS);
* conf.setShortMonthNames(
* "jan|feb|mar|apr|ma\u00ED|j\u00FAn|j\u00FAl|\u00e1g\u00FA|sep|okt|n\u00F3v|des");
* f.configure(conf);
* f.connect(server);
* f.login(username, password);
* FTPFile[] files = listFiles(directory);
* </pre>
* <p>
* Unpaged (whole list) access on a Windows-NT server in a different time zone.
* (Note, since the NT Format uses numeric date formatting, language issues
* are irrelevant here).
* <pre>
* FTPClient f=FTPClient();
* FTPClientConfig conf = new FTPClientConfig(FTPClientConfig.SYST_NT);
* conf.setTimeZoneId("America/Denver");
* f.configure(conf);
* f.connect(server);
* f.login(username, password);
* FTPFile[] files = listFiles(directory);
* </pre>
* Unpaged (whole list) access on a Windows-NT server in a different time zone
* but which has been configured to use a unix-style listing format.
* <pre>
* FTPClient f=FTPClient();
* FTPClientConfig conf = new FTPClientConfig(FTPClientConfig.SYST_UNIX);
* conf.setTimeZoneId("America/Denver");
* f.configure(conf);
* f.connect(server);
* f.login(username, password);
* FTPFile[] files = listFiles(directory);
* </pre>
*
* @since 1.4
* @see Configurable
* @see FTPClient
* @see org.apache.commons.net.ftp.parser.FTPTimestampParserImpl#configure(FTPClientConfig)
* @see org.apache.commons.net.ftp.parser.ConfigurableFTPFileEntryParserImpl
*/
public class FTPClientConfig
{
/**
* Identifier by which a unix-based ftp server is known throughout
* the commons-net ftp system.
*/
public static final 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
*/
public static final String SYST_UNIX_TRIM_LEADING = "UNIX_LTRIM";
/**
* Identifier by which a vms-based ftp server is known throughout
* the commons-net ftp system.
*/
public static final String SYST_VMS = "VMS";
/**
* Identifier by which a WindowsNT-based ftp server is known throughout
* the commons-net ftp system.
*/
public static final String SYST_NT = "WINDOWS";
/**
* Identifier by which an OS/2-based ftp server is known throughout
* the commons-net ftp system.
*/
public static final String SYST_OS2 = "OS/2";
/**
* Identifier by which an OS/400-based ftp server is known throughout
* the commons-net ftp system.
*/
public static final String SYST_OS400 = "OS/400";
/**
* Identifier by which an AS/400-based ftp server is known throughout
* the commons-net ftp system.
*/
public static final String SYST_AS400 = "AS/400";
/**
* Identifier by which an MVS-based ftp server is known throughout
* the commons-net ftp system.
*/
public static final 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
*/
public static final String SYST_L8 = "TYPE: L8";
/**
* Identifier by which an Netware-based ftp server is known throughout
* the commons-net ftp system.
*
* @since 1.5
*/
public static final 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
public static final String SYST_MACOS_PETER = "MACOS PETER"; // NET-436
private final String serverSystemKey;
private String defaultDateFormatStr = null;
private String recentDateFormatStr = null;
private boolean lenientFutureDates = true; // NET-407
private String serverLanguageCode = null;
private String shortMonthNames = null;
private String serverTimeZoneId = null;
private boolean saveUnparseableEntries = false;
/**
* The main constructor for an FTPClientConfig object
* @param systemKey key representing system type of the server being
* connected to. See {@link #getServerSystemKey() serverSystemKey}
* If set to the empty string, then FTPClient uses the system type returned by the server.
* However this is not recommended for general use;
* the correct system type should be set if it is known.
*/
public FTPClientConfig(String systemKey) {
this.serverSystemKey = systemKey;
}
/**
* Convenience constructor mainly for use in testing.
* Constructs a UNIX configuration.
*/
public FTPClientConfig() {
this(SYST_UNIX);
}
/**
* Constructor which allows setting of the format string member fields
* @param systemKey key representing system type of the server being
* connected to. See
* {@link #getServerSystemKey() serverSystemKey}
* @param defaultDateFormatStr See
* {@link #setDefaultDateFormatStr(String) defaultDateFormatStr}
* @param recentDateFormatStr See
* {@link #setRecentDateFormatStr(String) recentDateFormatStr}
* @since 3.6
*/
public FTPClientConfig(String systemKey,
String defaultDateFormatStr,
String recentDateFormatStr)
{
this(systemKey);
this.defaultDateFormatStr = defaultDateFormatStr;
this.recentDateFormatStr = recentDateFormatStr;
}
/**
* Constructor which allows setting of most member fields
* @param systemKey key representing system type of the server being
* connected to. See
* {@link #getServerSystemKey() serverSystemKey}
* @param defaultDateFormatStr See
* {@link #setDefaultDateFormatStr(String) defaultDateFormatStr}
* @param recentDateFormatStr See
* {@link #setRecentDateFormatStr(String) recentDateFormatStr}
* @param serverLanguageCode See
* {@link #setServerLanguageCode(String) serverLanguageCode}
* @param shortMonthNames See
* {@link #setShortMonthNames(String) shortMonthNames}
* @param serverTimeZoneId See
* {@link #setServerTimeZoneId(String) serverTimeZoneId}
*/
public FTPClientConfig(String systemKey,
String defaultDateFormatStr,
String recentDateFormatStr,
String serverLanguageCode,
String shortMonthNames,
String serverTimeZoneId)
{
this(systemKey);
this.defaultDateFormatStr = defaultDateFormatStr;
this.recentDateFormatStr = recentDateFormatStr;
this.serverLanguageCode = serverLanguageCode;
this.shortMonthNames = shortMonthNames;
this.serverTimeZoneId = serverTimeZoneId;
}
/**
* Constructor which allows setting of all member fields
* @param systemKey key representing system type of the server being
* connected to. See
* {@link #getServerSystemKey() serverSystemKey}
* @param defaultDateFormatStr See
* {@link #setDefaultDateFormatStr(String) defaultDateFormatStr}
* @param recentDateFormatStr See
* {@link #setRecentDateFormatStr(String) recentDateFormatStr}
* @param serverLanguageCode See
* {@link #setServerLanguageCode(String) serverLanguageCode}
* @param shortMonthNames See
* {@link #setShortMonthNames(String) shortMonthNames}
* @param serverTimeZoneId See
* {@link #setServerTimeZoneId(String) serverTimeZoneId}
* @param lenientFutureDates See
* {@link #setLenientFutureDates(boolean) lenientFutureDates}
* @param saveUnparseableEntries See
* {@link #setUnparseableEntries(boolean) saveUnparseableEntries}
*/
public FTPClientConfig(String systemKey,
String defaultDateFormatStr,
String recentDateFormatStr,
String serverLanguageCode,
String shortMonthNames,
String serverTimeZoneId,
boolean lenientFutureDates,
boolean saveUnparseableEntries)
{
this(systemKey);
this.defaultDateFormatStr = defaultDateFormatStr;
this.lenientFutureDates = lenientFutureDates;
this.recentDateFormatStr = recentDateFormatStr;
this.saveUnparseableEntries = saveUnparseableEntries;
this.serverLanguageCode = serverLanguageCode;
this.shortMonthNames = shortMonthNames;
this.serverTimeZoneId = serverTimeZoneId;
}
// Copy constructor, intended for use by FTPClient only
FTPClientConfig(String systemKey, FTPClientConfig config) {
this.serverSystemKey = systemKey;
this.defaultDateFormatStr = config.defaultDateFormatStr;
this.lenientFutureDates = config.lenientFutureDates;
this.recentDateFormatStr = config.recentDateFormatStr;
this.saveUnparseableEntries = config.saveUnparseableEntries;
this.serverLanguageCode = config.serverLanguageCode;
this.serverTimeZoneId = config.serverTimeZoneId;
this.shortMonthNames = config.shortMonthNames;
}
/**
* Copy constructor
* @param config source
* @since 3.6
*/
public FTPClientConfig(FTPClientConfig config) {
this.serverSystemKey = config.serverSystemKey;
this.defaultDateFormatStr = config.defaultDateFormatStr;
this.lenientFutureDates = config.lenientFutureDates;
this.recentDateFormatStr = config.recentDateFormatStr;
this.saveUnparseableEntries = config.saveUnparseableEntries;
this.serverLanguageCode = config.serverLanguageCode;
this.serverTimeZoneId = config.serverTimeZoneId;
this.shortMonthNames = config.shortMonthNames;
}
private static final Map<String, Object> LANGUAGE_CODE_MAP = new TreeMap<String, Object>();
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
}
/**
* Getter for the serverSystemKey property. This property
* specifies the general type of server to which the client connects.
* Should be either one of the <code>FTPClientConfig.SYST_*</code> codes
* or else the fully qualified class name of a parser implementing both
* the <code>FTPFileEntryParser</code> and <code>Configurable</code>
* interfaces.
* @return Returns the serverSystemKey property.
*/
public String getServerSystemKey() {
return serverSystemKey;
}
/**
* getter for the {@link #setDefaultDateFormatStr(String) defaultDateFormatStr}
* property.
* @return Returns the defaultDateFormatStr property.
*/
public String getDefaultDateFormatStr() {
return defaultDateFormatStr;
}
/**
* getter for the {@link #setRecentDateFormatStr(String) recentDateFormatStr} property.
* @return Returns the recentDateFormatStr property.
*/
public String getRecentDateFormatStr() {
return recentDateFormatStr;
}
/**
* getter for the {@link #setServerTimeZoneId(String) serverTimeZoneId} property.
* @return Returns the serverTimeZoneId property.
*/
public String getServerTimeZoneId() {
return serverTimeZoneId;
}
/**
* <p>
* getter for the {@link #setShortMonthNames(String) shortMonthNames}
* property.
* </p>
* @return Returns the shortMonthNames.
*/
public String getShortMonthNames() {
return shortMonthNames;
}
/**
* <p>
* getter for the {@link #setServerLanguageCode(String) serverLanguageCode} property.
* </p>
* @return Returns the serverLanguageCode property.
*/
public String getServerLanguageCode() {
return serverLanguageCode;
}
/**
* <p>
* getter for the {@link #setLenientFutureDates(boolean) lenientFutureDates} property.
* </p>
* @return Returns the lenientFutureDates.
* @since 1.5
*/
public boolean isLenientFutureDates() {
return lenientFutureDates;
}
/**
* <p>
* setter for the defaultDateFormatStr property. This property
* specifies the main date format that will be used by a parser configured
* by this configuration to parse file timestamps. If this is not
* specified, such a parser will use as a default value, the most commonly
* used format which will be in as used in <code>en_US</code> locales.
* </p><p>
* This should be in the format described for
* <code>java.text.SimpleDateFormat</code>.
* property.
* </p>
* @param defaultDateFormatStr The defaultDateFormatStr to set.
*/
public void setDefaultDateFormatStr(String defaultDateFormatStr) {
this.defaultDateFormatStr = defaultDateFormatStr;
}
/**
* <p>
* setter for the recentDateFormatStr property. This property
* specifies a secondary date format that will be used by a parser
* configured by this configuration to parse file timestamps, typically
* those less than a year old. If this is not specified, such a parser
* will not attempt to parse using an alternate format.
* </p>
* <p>
* This is used primarily in unix-based systems.
* </p>
* <p>
* This should be in the format described for
* <code>java.text.SimpleDateFormat</code>.
* </p>
* @param recentDateFormatStr The recentDateFormatStr to set.
*/
public void setRecentDateFormatStr(String recentDateFormatStr) {
this.recentDateFormatStr = recentDateFormatStr;
}
/**
* <p>
* setter for the lenientFutureDates property. This boolean property
* (default: false) only has meaning when a
* {@link #setRecentDateFormatStr(String) recentDateFormatStr} property
* has been set. In that case, if this property is set true, then the
* parser, when it encounters a listing parseable with the recent date
* format, will only consider a date to belong to the previous year if
* it is more than one day in the future. This will allow all
* out-of-synch situations (whether based on "slop" - i.e. servers simply
* out of synch with one another or because of time zone differences -
* but in the latter case it is highly recommended to use the
* {@link #setServerTimeZoneId(String) serverTimeZoneId} property
* instead) to resolve correctly.
* </p><p>
* This is used primarily in unix-based systems.
* </p>
* @param lenientFutureDates set true to compensate for out-of-synch
* conditions.
*/
public void setLenientFutureDates(boolean lenientFutureDates) {
this.lenientFutureDates = lenientFutureDates;
}
/**
* <p>
* setter for the serverTimeZoneId property. This property
* allows a time zone to be specified corresponding to that known to be
* used by an FTP server in file listings. This might be particularly
* useful to clients such as Ant that try to use these timestamps for
* dependency checking.
* </p><p>
* This should be one of the identifiers used by
* <code>java.util.TimeZone</code> to refer to time zones, for example,
* <code>America/Chicago</code> or <code>Asia/Rangoon</code>.
* </p>
* @param serverTimeZoneId The serverTimeZoneId to set.
*/
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&#xED;|j&#xFA;n|j&#xFA;l|&#xE1;g&#xFA;|sep|okt|n&#xF3;v|des"</code>.
* </p>
* @param shortMonthNames The value to set to the shortMonthNames property.
*/
public void setShortMonthNames(String shortMonthNames) {
this.shortMonthNames = shortMonthNames;
}
/**
* <p>
* setter for the serverLanguageCode property. This property allows
* user to specify a
* <a href="http://www.ics.uci.edu/pub/ietf/http/related/iso639.txt">
* two-letter ISO-639 language code</a> that will be used to
* configure the set of month names used by the file timestamp parser.
* If neither this nor the {@link #setShortMonthNames(String) shortMonthNames}
* is specified, parsing will assume English month names, which may or
* may not be significant, depending on whether the date format(s)
* specified via {@link #setDefaultDateFormatStr(String) defaultDateFormatStr}
* and/or {@link #setRecentDateFormatStr(String) recentDateFormatStr} are using
* numeric or alphabetic month names.
* </p>
* <p>If the code supplied is not supported here, <code>en_US</code>
* month names will be used. We are supporting here those language
* codes which, when a <code> java.util.Locale</code> is constucted
* using it, and a <code>java.text.SimpleDateFormat</code> is
* constructed using that Locale, the array returned by the
* SimpleDateFormat's <code>getShortMonths()</code> method consists
* solely of three 8-bit ASCII character strings. Additionally,
* languages which do not meet this requirement are included if a
* common alternative set of short month names is known to be used.
* This means that users who can tell us of additional such encodings
* may get them added to the list of supported languages by contacting
* the Apache Commons Net team.
* </p>
* <p><strong>
* Please note that this attribute will NOT be used to determine a
* locale-based date format for the language. </strong>
* Experience has shown that many if not most FTP servers outside the
* United States employ the standard <code>en_US</code> date format
* orderings of <code>MMM d yyyy</code> and <code>MMM d HH:mm</code>
* and attempting to deduce this automatically here would cause more
* problems than it would solve. The date format must be changed
* via the {@link #setDefaultDateFormatStr(String) defaultDateFormatStr} and/or
* {@link #setRecentDateFormatStr(String) recentDateFormatStr} parameters.
* </p>
* @param serverLanguageCode The value to set to the serverLanguageCode property.
*/
public void setServerLanguageCode(String serverLanguageCode) {
this.serverLanguageCode = serverLanguageCode;
}
/**
* 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();
}
/**
* Allow list parsing methods to create basic FTPFile entries if parsing fails.
* <p>
* In this case, the FTPFile will contain only the unparsed entry {@link FTPFile#getRawListing()}
* and {@link FTPFile#isValid()} will return {@code false}
* @param saveUnparseable if true, then create FTPFile entries if parsing fails
* @since 3.4
*/
public void setUnparseableEntries(boolean saveUnparseable) {
this.saveUnparseableEntries = saveUnparseable;
}
/**
* @return true if list parsing should return FTPFile entries even for unparseable response lines
* <p>
* If true, the FTPFile for any unparseable entries will contain only the unparsed entry
* {@link FTPFile#getRawListing()} and {@link FTPFile#isValid()} will return {@code false}
* @since 3.4
*/
public boolean getUnparseableEntries() {
return this.saveUnparseableEntries;
}
}

@ -0,0 +1,116 @@
/*
* 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 org.apache.commons.net.ftp;
/**
* @since 3.3
*/
public enum FTPCmd {
ABOR,
ACCT,
ALLO,
APPE,
CDUP,
CWD,
DELE,
EPRT,
EPSV,
FEAT,
HELP,
LIST,
MDTM,
MFMT,
MKD,
MLSD,
MLST,
MODE,
NLST,
NOOP,
PASS,
PASV,
PORT,
PWD,
QUIT,
REIN,
REST,
RETR,
RMD,
RNFR,
RNTO,
SITE,
SMNT,
STAT,
STOR,
STOU,
STRU,
SYST,
TYPE,
USER,
;
// Aliases
public static final FTPCmd ABORT = ABOR;
public static final FTPCmd ACCOUNT = ACCT;
public static final FTPCmd ALLOCATE = ALLO;
public static final FTPCmd APPEND = APPE;
public static final FTPCmd CHANGE_TO_PARENT_DIRECTORY = CDUP;
public static final FTPCmd CHANGE_WORKING_DIRECTORY = CWD;
public static final FTPCmd DATA_PORT = PORT;
public static final FTPCmd DELETE = DELE;
public static final FTPCmd FEATURES = FEAT;
public static final FTPCmd FILE_STRUCTURE = STRU;
public static final FTPCmd GET_MOD_TIME = MDTM;
public static final FTPCmd LOGOUT = QUIT;
public static final FTPCmd MAKE_DIRECTORY = MKD;
public static final FTPCmd MOD_TIME = MDTM;
public static final FTPCmd NAME_LIST = NLST;
public static final FTPCmd PASSIVE = PASV;
public static final FTPCmd PASSWORD = PASS;
public static final FTPCmd PRINT_WORKING_DIRECTORY = PWD;
public static final FTPCmd REINITIALIZE = REIN;
public static final FTPCmd REMOVE_DIRECTORY = RMD;
public static final FTPCmd RENAME_FROM = RNFR;
public static final FTPCmd RENAME_TO = RNTO;
public static final FTPCmd REPRESENTATION_TYPE = TYPE;
public static final FTPCmd RESTART = REST;
public static final FTPCmd RETRIEVE = RETR;
public static final FTPCmd SET_MOD_TIME = MFMT;
public static final FTPCmd SITE_PARAMETERS = SITE;
public static final FTPCmd STATUS = STAT;
public static final FTPCmd STORE = STOR;
public static final FTPCmd STORE_UNIQUE = STOU;
public static final FTPCmd STRUCTURE_MOUNT = SMNT;
public static final FTPCmd SYSTEM = SYST;
public static final FTPCmd TRANSFER_MODE = MODE;
public static final FTPCmd USERNAME = USER;
/**
* Retrieve the FTP protocol command string corresponding to a specified
* command code.
*
* @return The FTP protcol command string corresponding to a specified
* command code.
*/
public final String getCommand()
{
return this.name();
}
}

@ -0,0 +1,171 @@
/*
* 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 org.apache.commons.net.ftp;
/**
* FTPCommand stores a set of constants for FTP command codes. To interpret
* the meaning of the codes, familiarity with RFC 959 is assumed.
* The mnemonic constant names are transcriptions from the code descriptions
* of RFC 959. For those who think in terms of the actual FTP commands,
* a set of constants such as {@link #USER USER } are provided
* where the constant name is the same as the FTP command.
*
* @deprecated use {@link FTPCmd} instead
*/
@Deprecated
public final class FTPCommand
{
public static final int USER = 0;
public static final int PASS = 1;
public static final int ACCT = 2;
public static final int CWD = 3;
public static final int CDUP = 4;
public static final int SMNT = 5;
public static final int REIN = 6;
public static final int QUIT = 7;
public static final int PORT = 8;
public static final int PASV = 9;
public static final int TYPE = 10;
public static final int STRU = 11;
public static final int MODE = 12;
public static final int RETR = 13;
public static final int STOR = 14;
public static final int STOU = 15;
public static final int APPE = 16;
public static final int ALLO = 17;
public static final int REST = 18;
public static final int RNFR = 19;
public static final int RNTO = 20;
public static final int ABOR = 21;
public static final int DELE = 22;
public static final int RMD = 23;
public static final int MKD = 24;
public static final int PWD = 25;
public static final int LIST = 26;
public static final int NLST = 27;
public static final int SITE = 28;
public static final int SYST = 29;
public static final int STAT = 30;
public static final int HELP = 31;
public static final int NOOP = 32;
/** @since 2.0 */
public static final int MDTM = 33;
/** @since 2.2 */
public static final int FEAT = 34;
/** @since 2.2 */
public static final int MFMT = 35;
/** @since 2.2 */
public static final int EPSV = 36;
/** @since 2.2 */
public static final int EPRT = 37;
/**
* Machine parseable list for a directory
* @since 3.0
*/
public static final int MLSD = 38;
/**
* Machine parseable list for a single file
* @since 3.0
*/
public static final int MLST = 39;
// Must agree with final entry above; used to check array size
private static final int LAST = MLST;
public static final int USERNAME = USER;
public static final int PASSWORD = PASS;
public static final int ACCOUNT = ACCT;
public static final int CHANGE_WORKING_DIRECTORY = CWD;
public static final int CHANGE_TO_PARENT_DIRECTORY = CDUP;
public static final int STRUCTURE_MOUNT = SMNT;
public static final int REINITIALIZE = REIN;
public static final int LOGOUT = QUIT;
public static final int DATA_PORT = PORT;
public static final int PASSIVE = PASV;
public static final int REPRESENTATION_TYPE = TYPE;
public static final int FILE_STRUCTURE = STRU;
public static final int TRANSFER_MODE = MODE;
public static final int RETRIEVE = RETR;
public static final int STORE = STOR;
public static final int STORE_UNIQUE = STOU;
public static final int APPEND = APPE;
public static final int ALLOCATE = ALLO;
public static final int RESTART = REST;
public static final int RENAME_FROM = RNFR;
public static final int RENAME_TO = RNTO;
public static final int ABORT = ABOR;
public static final int DELETE = DELE;
public static final int REMOVE_DIRECTORY = RMD;
public static final int MAKE_DIRECTORY = MKD;
public static final int PRINT_WORKING_DIRECTORY = PWD;
// public static final int LIST = LIST;
public static final int NAME_LIST = NLST;
public static final int SITE_PARAMETERS = SITE;
public static final int SYSTEM = SYST;
public static final int STATUS = STAT;
//public static final int HELP = HELP;
//public static final int NOOP = NOOP;
/** @since 2.0 */
public static final int MOD_TIME = MDTM;
/** @since 2.2 */
public static final int FEATURES = FEAT;
/** @since 2.2 */
public static final int GET_MOD_TIME = MDTM;
/** @since 2.2 */
public static final int SET_MOD_TIME = MFMT;
// Cannot be instantiated
private FTPCommand()
{}
private static final String[] _commands = {
"USER", "PASS", "ACCT", "CWD", "CDUP", "SMNT", "REIN", "QUIT", "PORT",
"PASV", "TYPE", "STRU", "MODE", "RETR", "STOR", "STOU", "APPE", "ALLO",
"REST", "RNFR", "RNTO", "ABOR", "DELE", "RMD", "MKD", "PWD", "LIST",
"NLST", "SITE", "SYST", "STAT", "HELP", "NOOP", "MDTM", "FEAT", "MFMT",
"EPSV", "EPRT", "MLSD", "MLST" };
// default access needed for Unit test
static void checkArray(){
int expectedLength = LAST+1;
if (_commands.length != expectedLength) {
throw new RuntimeException("Incorrect _commands array. Should have length "
+expectedLength+" found "+_commands.length);
}
}
/**
* Retrieve the FTP protocol command string corresponding to a specified
* command code.
*
* @param command The command code.
* @return The FTP protcol command string corresponding to a specified
* command code.
*/
public static final String getCommand(int command)
{
return _commands[command];
}
}

@ -0,0 +1,55 @@
/*
* 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 org.apache.commons.net.ftp;
import java.io.IOException;
/***
* FTPConnectionClosedException is used to indicate the premature or
* unexpected closing of an FTP connection resulting from a
* {@link FTPReply#SERVICE_NOT_AVAILABLE FTPReply.SERVICE_NOT_AVAILABLE }
* response (FTP reply code 421) to a
* failed FTP command. This exception is derived from IOException and
* therefore may be caught either as an IOException or specifically as an
* FTPConnectionClosedException.
*
* @see FTP
* @see FTPClient
***/
public class FTPConnectionClosedException extends IOException
{
private static final long serialVersionUID = 3500547241659379952L;
/*** Constructs a FTPConnectionClosedException with no message ***/
public FTPConnectionClosedException()
{
super();
}
/***
* Constructs a FTPConnectionClosedException with a specified message.
*
* @param message The message explaining the reason for the exception.
***/
public FTPConnectionClosedException(String message)
{
super(message);
}
}

@ -0,0 +1,536 @@
/*
* 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 org.apache.commons.net.ftp;
import java.io.Serializable;
import java.util.Calendar;
import java.util.Date;
import java.util.Formatter;
import java.util.TimeZone;
/***
* The FTPFile class is used to represent information about files stored
* on an FTP server.
*
* @see FTPFileEntryParser
* @see FTPClient#listFiles
***/
public class FTPFile implements Serializable
{
private static final long serialVersionUID = 9010790363003271996L;
/** A constant indicating an FTPFile is a file. ***/
public static final int FILE_TYPE = 0;
/** A constant indicating an FTPFile is a directory. ***/
public static final int DIRECTORY_TYPE = 1;
/** A constant indicating an FTPFile is a symbolic link. ***/
public static final int SYMBOLIC_LINK_TYPE = 2;
/** A constant indicating an FTPFile is of unknown type. ***/
public static final int UNKNOWN_TYPE = 3;
/** A constant indicating user access permissions. ***/
public static final int USER_ACCESS = 0;
/** A constant indicating group access permissions. ***/
public static final int GROUP_ACCESS = 1;
/** A constant indicating world access permissions. ***/
public static final int WORLD_ACCESS = 2;
/** A constant indicating file/directory read permission. ***/
public static final int READ_PERMISSION = 0;
/** A constant indicating file/directory write permission. ***/
public static final int WRITE_PERMISSION = 1;
/**
* A constant indicating file execute permission or directory listing
* permission.
***/
public static final int EXECUTE_PERMISSION = 2;
private int _type, _hardLinkCount;
private long _size;
private String _rawListing, _user, _group, _name, _link;
private Calendar _date;
// If this is null, then list entry parsing failed
private final boolean[] _permissions[]; // e.g. _permissions[USER_ACCESS][READ_PERMISSION]
/*** Creates an empty FTPFile. ***/
public FTPFile()
{
_permissions = new boolean[3][3];
_type = UNKNOWN_TYPE;
// init these to values that do not occur in listings
// so can distinguish which fields are unset
_hardLinkCount = 0; // 0 is invalid as a link count
_size = -1; // 0 is valid, so use -1
_user = "";
_group = "";
_date = null;
_name = null;
}
/**
* Constructor for use by {@link FTPListParseEngine} only.
* Used to create FTPFile entries for failed parses
* @param rawListing line that could not be parsed.
* @since 3.4
*/
FTPFile(String rawListing)
{
_permissions = null; // flag that entry is invalid
_rawListing = rawListing;
_type = UNKNOWN_TYPE;
// init these to values that do not occur in listings
// so can distinguish which fields are unset
_hardLinkCount = 0; // 0 is invalid as a link count
_size = -1; // 0 is valid, so use -1
_user = "";
_group = "";
_date = null;
_name = null;
}
/***
* Set the original FTP server raw listing from which the FTPFile was
* created.
*
* @param rawListing The raw FTP server listing.
***/
public void setRawListing(String rawListing)
{
_rawListing = rawListing;
}
/***
* Get the original FTP server raw listing used to initialize the FTPFile.
*
* @return The original FTP server raw listing used to initialize the
* FTPFile.
***/
public String getRawListing()
{
return _rawListing;
}
/***
* Determine if the file is a directory.
*
* @return True if the file is of type <code>DIRECTORY_TYPE</code>, false if
* not.
***/
public boolean isDirectory()
{
return (_type == DIRECTORY_TYPE);
}
/***
* Determine if the file is a regular file.
*
* @return True if the file is of type <code>FILE_TYPE</code>, false if
* not.
***/
public boolean isFile()
{
return (_type == FILE_TYPE);
}
/***
* Determine if the file is a symbolic link.
*
* @return True if the file is of type <code>UNKNOWN_TYPE</code>, false if
* not.
***/
public boolean isSymbolicLink()
{
return (_type == SYMBOLIC_LINK_TYPE);
}
/***
* Determine if the type of the file is unknown.
*
* @return True if the file is of type <code>UNKNOWN_TYPE</code>, false if
* not.
***/
public boolean isUnknown()
{
return (_type == UNKNOWN_TYPE);
}
/**
* Used to indicate whether an entry is valid or not.
* If the entry is invalid, only the {@link #getRawListing()} method will be useful.
* Other methods may fail.
*
* Used in conjunction with list parsing that preseverves entries that failed to parse.
* @see FTPClientConfig#setUnparseableEntries(boolean)
* @return true if the entry is valid
* @since 3.4
*/
public boolean isValid() {
return (_permissions != null);
}
/***
* Set the type of the file (<code>DIRECTORY_TYPE</code>,
* <code>FILE_TYPE</code>, etc.).
*
* @param type The integer code representing the type of the file.
***/
public void setType(int type)
{
_type = type;
}
/***
* Return the type of the file (one of the <code>_TYPE</code> constants),
* e.g., if it is a directory, a regular file, or a symbolic link.
*
* @return The type of the file.
***/
public int getType()
{
return _type;
}
/***
* Set the name of the file.
*
* @param name The name of the file.
***/
public void setName(String name)
{
_name = name;
}
/***
* Return the name of the file.
*
* @return The name of the file.
***/
public String getName()
{
return _name;
}
/**
* Set the file size in bytes.
* @param size The file size in bytes.
*/
public void setSize(long size)
{
_size = size;
}
/***
* Return the file size in bytes.
*
* @return The file size in bytes.
***/
public long getSize()
{
return _size;
}
/***
* Set the number of hard links to this file. This is not to be
* confused with symbolic links.
*
* @param links The number of hard links to this file.
***/
public void setHardLinkCount(int links)
{
_hardLinkCount = links;
}
/***
* Return the number of hard links to this file. This is not to be
* confused with symbolic links.
*
* @return The number of hard links to this file.
***/
public int getHardLinkCount()
{
return _hardLinkCount;
}
/***
* Set the name of the group owning the file. This may be
* a string representation of the group number.
*
* @param group The name of the group owning the file.
***/
public void setGroup(String group)
{
_group = group;
}
/***
* Returns the name of the group owning the file. Sometimes this will be
* a string representation of the group number.
*
* @return The name of the group owning the file.
***/
public String getGroup()
{
return _group;
}
/***
* Set the name of the user owning the file. This may be
* a string representation of the user number;
*
* @param user The name of the user owning the file.
***/
public void setUser(String user)
{
_user = user;
}
/***
* Returns the name of the user owning the file. Sometimes this will be
* a string representation of the user number.
*
* @return The name of the user owning the file.
***/
public String getUser()
{
return _user;
}
/***
* If the FTPFile is a symbolic link, use this method to set the name of the
* file being pointed to by the symbolic link.
*
* @param link The file pointed to by the symbolic link.
***/
public void setLink(String link)
{
_link = link;
}
/***
* If the FTPFile is a symbolic link, this method returns the name of the
* file being pointed to by the symbolic link. Otherwise it returns null.
*
* @return The file pointed to by the symbolic link (null if the FTPFile
* is not a symbolic link).
***/
public String getLink()
{
return _link;
}
/***
* Set the file timestamp. This usually the last modification time.
* The parameter is not cloned, so do not alter its value after calling
* this method.
*
* @param date A Calendar instance representing the file timestamp.
***/
public void setTimestamp(Calendar date)
{
_date = date;
}
/***
* Returns the file timestamp. This usually the last modification time.
*
* @return A Calendar instance representing the file timestamp.
***/
public Calendar getTimestamp()
{
return _date;
}
/***
* Set if the given access group (one of the <code> _ACCESS </code>
* constants) has the given access permission (one of the
* <code> _PERMISSION </code> constants) to the file.
*
* @param access The access group (one of the <code> _ACCESS </code>
* constants)
* @param permission The access permission (one of the
* <code> _PERMISSION </code> constants)
* @param value True if permission is allowed, false if not.
* @throws ArrayIndexOutOfBoundsException if either of the parameters is out of range
***/
public void setPermission(int access, int permission, boolean value)
{
_permissions[access][permission] = value;
}
/***
* Determines if the given access group (one of the <code> _ACCESS </code>
* constants) has the given access permission (one of the
* <code> _PERMISSION </code> constants) to the file.
*
* @param access The access group (one of the <code> _ACCESS </code>
* constants)
* @param permission The access permission (one of the
* <code> _PERMISSION </code> constants)
* @throws ArrayIndexOutOfBoundsException if either of the parameters is out of range
* @return true if {@link #isValid()} is {@code true &&} the associated permission is set;
* {@code false} otherwise.
***/
public boolean hasPermission(int access, int permission)
{
if (_permissions == null) {
return false;
}
return _permissions[access][permission];
}
/***
* Returns a string representation of the FTPFile information.
*
* @return A string representation of the FTPFile information.
*/
@Override
public String toString()
{
return getRawListing();
}
/***
* Returns a string representation of the FTPFile information.
* This currently mimics the Unix listing format.
* This method uses the timezone of the Calendar entry, which is
* the server time zone (if one was provided) otherwise it is
* the local time zone.
* <p>
* Note: if the instance is not valid {@link #isValid()}, no useful
* information can be returned. In this case, use {@link #getRawListing()}
* instead.
*
* @return A string representation of the FTPFile information.
* @since 3.0
*/
public String toFormattedString()
{
return toFormattedString(null);
}
/**
* Returns a string representation of the FTPFile information.
* This currently mimics the Unix listing format.
* This method allows the Calendar time zone to be overridden.
* <p>
* Note: if the instance is not valid {@link #isValid()}, no useful
* information can be returned. In this case, use {@link #getRawListing()}
* instead.
* @param timezone the timezone to use for displaying the time stamp
* If {@code null}, then use the Calendar entry timezone
* @return A string representation of the FTPFile information.
* @since 3.4
*/
public String toFormattedString(final String timezone)
{
if (!isValid()) {
return "[Invalid: could not parse file entry]";
}
StringBuilder sb = new StringBuilder();
Formatter fmt = new Formatter(sb);
sb.append(formatType());
sb.append(permissionToString(USER_ACCESS));
sb.append(permissionToString(GROUP_ACCESS));
sb.append(permissionToString(WORLD_ACCESS));
fmt.format(" %4d", Integer.valueOf(getHardLinkCount()));
fmt.format(" %-8s %-8s", getUser(), getGroup());
fmt.format(" %8d", Long.valueOf(getSize()));
Calendar timestamp = getTimestamp();
if (timestamp != null) {
if (timezone != null) {
TimeZone newZone = TimeZone.getTimeZone(timezone);
if (!newZone.equals(timestamp.getTimeZone())){
Date original = timestamp.getTime();
Calendar newStamp = Calendar.getInstance(newZone);
newStamp.setTime(original);
timestamp = newStamp;
}
}
fmt.format(" %1$tY-%1$tm-%1$td", timestamp);
// Only display time units if they are present
if (timestamp.isSet(Calendar.HOUR_OF_DAY)) {
fmt.format(" %1$tH", timestamp);
if (timestamp.isSet(Calendar.MINUTE)) {
fmt.format(":%1$tM", timestamp);
if (timestamp.isSet(Calendar.SECOND)) {
fmt.format(":%1$tS", timestamp);
if (timestamp.isSet(Calendar.MILLISECOND)) {
fmt.format(".%1$tL", timestamp);
}
}
}
fmt.format(" %1$tZ", timestamp);
}
}
sb.append(' ');
sb.append(getName());
fmt.close();
return sb.toString();
}
private char formatType(){
switch(_type) {
case FILE_TYPE:
return '-';
case DIRECTORY_TYPE:
return 'd';
case SYMBOLIC_LINK_TYPE:
return 'l';
default:
return '?';
}
}
private String permissionToString(int access ){
StringBuilder sb = new StringBuilder();
if (hasPermission(access, READ_PERMISSION)) {
sb.append('r');
} else {
sb.append('-');
}
if (hasPermission(access, WRITE_PERMISSION)) {
sb.append('w');
} else {
sb.append('-');
}
if (hasPermission(access, EXECUTE_PERMISSION)) {
sb.append('x');
} else {
sb.append('-');
}
return sb.toString();
}
}

@ -0,0 +1,134 @@
/*
* 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 org.apache.commons.net.ftp;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.List;
/**
* FTPFileEntryParser defines the interface for parsing a single FTP file
* listing and converting that information into an
* {@link FTPFile} instance.
* Sometimes you will want to parse unusual listing formats, in which
* case you would create your own implementation of FTPFileEntryParser and
* if necessary, subclass FTPFile.
* <p>
* Here are some examples showing how to use one of the classes that
* implement this interface.
* <p>
*
* The first example uses the <code>FTPClient.listFiles()</code>
* API to pull the whole list from the subfolder <code>subfolder</code> in
* one call, attempting to automatically detect the parser type. This
* method, without a parserKey parameter, indicates that autodection should
* be used.
*
* <pre>
* FTPClient f=FTPClient();
* f.connect(server);
* f.login(username, password);
* FTPFile[] files = f.listFiles("subfolder");
* </pre>
*
* The second example uses the <code>FTPClient.listFiles()</code>
* API to pull the whole list from the current working directory in one call,
* but specifying by classname the parser to be used. For this particular
* parser class, this approach is necessary since there is no way to
* autodetect this server type.
*
* <pre>
* FTPClient f=FTPClient();
* f.connect(server);
* f.login(username, password);
* FTPFile[] files = f.listFiles(
* "org.apache.commons.net.ftp.parser.EnterpriseUnixFTPFileEntryParser",
* ".");
* </pre>
*
* The third example uses the <code>FTPClient.listFiles()</code>
* API to pull a single file listing in an arbitrary directory in one call,
* specifying by KEY the parser to be used, in this case, VMS.
*
* <pre>
* FTPClient f=FTPClient();
* f.connect(server);
* f.login(username, password);
* FTPFile[] files = f.listFiles("VMS", "subfolder/foo.java");
* </pre>
*
* For an alternative approach, see the {@link FTPListParseEngine} class
* which provides iterative access.
*
* @version $Id: FTPFileEntryParser.java 1747119 2016-06-07 02:22:24Z ggregory $
* @see FTPFile
* @see FTPClient#listFiles()
*/
public interface FTPFileEntryParser
{
/**
* Parses a line of an FTP server file listing and converts it into a usable
* format in the form of an <code> FTPFile </code> instance. If the
* file listing line doesn't describe a file, <code> null </code> should be
* returned, otherwise a <code> FTPFile </code> instance representing the
* files in the directory is returned.
*
* @param listEntry A line of text from the file listing
* @return An FTPFile instance corresponding to the supplied entry
*/
FTPFile parseFTPEntry(String listEntry);
/**
* Reads the next entry using the supplied BufferedReader object up to
* whatever delemits one entry from the next. Implementors must define
* this for the particular ftp system being parsed. In many but not all
* cases, this can be defined simply by calling BufferedReader.readLine().
*
* @param reader The BufferedReader object from which entries are to be
* read.
*
* @return A string representing the next ftp entry or null if none found.
* @throws IOException thrown on any IO Error reading from the reader.
*/
String readNextEntry(BufferedReader reader) throws IOException;
/**
* This method is a hook for those implementors (such as
* VMSVersioningFTPEntryParser, and possibly others) which need to
* perform some action upon the FTPFileList after it has been created
* from the server stream, but before any clients see the list.
*
* The default implementation can be a no-op.
*
* @param original Original list after it has been created from the server stream
*
* @return Original list as processed by this method.
*/
List<String> preParse(List<String> original);
}
/* Emacs configuration
* Local variables: **
* mode: java **
* c-basic-offset: 4 **
* indent-tabs-mode: nil **
* End: **
*/

@ -0,0 +1,79 @@
/*
* 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 org.apache.commons.net.ftp;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.List;
/**
* This abstract class implements both the older FTPFileListParser and
* newer FTPFileEntryParser interfaces with default functionality.
* All the classes in the parser subpackage inherit from this.
*
*/
public abstract class FTPFileEntryParserImpl
implements FTPFileEntryParser
{
/**
* The constructor for a FTPFileEntryParserImpl object.
*/
public FTPFileEntryParserImpl()
{
}
/**
* Reads the next entry using the supplied BufferedReader object up to
* whatever delimits one entry from the next. This default implementation
* simply calls BufferedReader.readLine().
*
* @param reader The BufferedReader object from which entries are to be
* read.
*
* @return A string representing the next ftp entry or null if none found.
* @throws IOException thrown on any IO Error reading from the reader.
*/
@Override
public String readNextEntry(BufferedReader reader) throws IOException
{
return reader.readLine();
}
/**
* This method is a hook for those implementors (such as
* VMSVersioningFTPEntryParser, and possibly others) which need to
* perform some action upon the FTPFileList after it has been created
* from the server stream, but before any clients see the list.
*
* This default implementation does nothing.
*
* @param original Original list after it has been created from the server stream
*
* @return <code>original</code> unmodified.
*/
@Override
public List<String> preParse(List<String> original) {
return original;
}
}
/* Emacs configuration
* Local variables: **
* mode: java **
* c-basic-offset: 4 **
* indent-tabs-mode: nil **
* End: **
*/

@ -0,0 +1,33 @@
/*
* 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 org.apache.commons.net.ftp;
/**
* Perform filtering on FTPFile entries.
* @since 2.2
*/
public interface FTPFileFilter {
/**
* Checks if an FTPFile entry should be included or not.
*
* @param file entry to be checked for inclusion. May be <code>null</code>.
* @return <code>true</code> if the file is to be included, <code>false</code> otherwise
*/
public boolean accept(FTPFile file);
}

@ -0,0 +1,57 @@
/*
* 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 org.apache.commons.net.ftp;
/**
* Implements some simple FTPFileFilter classes.
* @since 2.2
*/
public class FTPFileFilters {
/**
* Accepts all FTPFile entries, including null.
*/
public static final FTPFileFilter ALL = new FTPFileFilter() {
@Override
public boolean accept(FTPFile file) {
return true;
}
};
/**
* Accepts all non-null FTPFile entries.
*/
public static final FTPFileFilter NON_NULL = new FTPFileFilter() {
@Override
public boolean accept(FTPFile file) {
return file != null;
}
};
/**
* Accepts all (non-null) FTPFile directory entries.
*/
public static final FTPFileFilter DIRECTORIES = new FTPFileFilter() {
@Override
public boolean accept(FTPFile file) {
return file != null && file.isDirectory();
}
};
}

@ -0,0 +1,202 @@
/*
* 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 org.apache.commons.net.ftp;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.net.Inet6Address;
import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.net.util.Base64;
/**
* Experimental attempt at FTP client that tunnels over an HTTP proxy connection.
*
* @since 2.2
*/
public class FTPHTTPClient extends FTPClient {
private final String proxyHost;
private final int proxyPort;
private final String proxyUsername;
private final String proxyPassword;
private static final byte[] CRLF={'\r', '\n'};
private final Base64 base64 = new Base64();
private String tunnelHost; // Save the host when setting up a tunnel (needed for EPSV)
public FTPHTTPClient(String proxyHost, int proxyPort, String proxyUser, String proxyPass) {
this.proxyHost = proxyHost;
this.proxyPort = proxyPort;
this.proxyUsername = proxyUser;
this.proxyPassword = proxyPass;
this.tunnelHost = null;
}
public FTPHTTPClient(String proxyHost, int proxyPort) {
this(proxyHost, proxyPort, null, null);
}
/**
* {@inheritDoc}
*
* @throws IllegalStateException if connection mode is not passive
* @deprecated (3.3) Use {@link FTPClient#_openDataConnection_(FTPCmd, String)} instead
*/
// Kept to maintain binary compatibility
// Not strictly necessary, but Clirr complains even though there is a super-impl
@Override
@Deprecated
protected Socket _openDataConnection_(int command, String arg)
throws IOException {
return super._openDataConnection_(command, arg);
}
/**
* {@inheritDoc}
*
* @throws IllegalStateException if connection mode is not passive
* @since 3.1
*/
@Override
protected Socket _openDataConnection_(String command, String arg)
throws IOException {
//Force local passive mode, active mode not supported by through proxy
if (getDataConnectionMode() != PASSIVE_LOCAL_DATA_CONNECTION_MODE) {
throw new IllegalStateException("Only passive connection mode supported");
}
final boolean isInet6Address = getRemoteAddress() instanceof Inet6Address;
String passiveHost = null;
boolean attemptEPSV = isUseEPSVwithIPv4() || isInet6Address;
if (attemptEPSV && epsv() == FTPReply.ENTERING_EPSV_MODE) {
_parseExtendedPassiveModeReply(_replyLines.get(0));
passiveHost = this.tunnelHost;
} else {
if (isInet6Address) {
return null; // Must use EPSV for IPV6
}
// If EPSV failed on IPV4, revert to PASV
if (pasv() != FTPReply.ENTERING_PASSIVE_MODE) {
return null;
}
_parsePassiveModeReply(_replyLines.get(0));
passiveHost = this.getPassiveHost();
}
Socket socket = _socketFactory_.createSocket(proxyHost, proxyPort);
InputStream is = socket.getInputStream();
OutputStream os = socket.getOutputStream();
tunnelHandshake(passiveHost, this.getPassivePort(), is, os);
if ((getRestartOffset() > 0) && !restart(getRestartOffset())) {
socket.close();
return null;
}
if (!FTPReply.isPositivePreliminary(sendCommand(command, arg))) {
socket.close();
return null;
}
return socket;
}
@Override
public void connect(String host, int port) throws SocketException, IOException {
_socket_ = _socketFactory_.createSocket(proxyHost, proxyPort);
_input_ = _socket_.getInputStream();
_output_ = _socket_.getOutputStream();
Reader socketIsReader;
try {
socketIsReader = tunnelHandshake(host, port, _input_, _output_);
}
catch (Exception e) {
IOException ioe = new IOException("Could not connect to " + host+ " using port " + port);
ioe.initCause(e);
throw ioe;
}
super._connectAction_(socketIsReader);
}
private BufferedReader tunnelHandshake(String host, int port, InputStream input, OutputStream output) throws IOException,
UnsupportedEncodingException {
final String connectString = "CONNECT " + host + ":" + port + " HTTP/1.1";
final String hostString = "Host: " + host + ":" + port;
this.tunnelHost = host;
output.write(connectString.getBytes("UTF-8")); // TODO what is the correct encoding?
output.write(CRLF);
output.write(hostString.getBytes("UTF-8"));
output.write(CRLF);
if (proxyUsername != null && proxyPassword != null) {
final String auth = proxyUsername + ":" + proxyPassword;
final String header = "Proxy-Authorization: Basic "
+ base64.encodeToString(auth.getBytes("UTF-8"));
output.write(header.getBytes("UTF-8"));
}
output.write(CRLF);
List<String> response = new ArrayList<String>();
BufferedReader reader = new BufferedReader(
new InputStreamReader(input, getCharset()));
for (String line = reader.readLine(); line != null
&& line.length() > 0; line = reader.readLine()) {
response.add(line);
}
int size = response.size();
if (size == 0) {
throw new IOException("No response from proxy");
}
String code = null;
String resp = response.get(0);
if (resp.startsWith("HTTP/") && resp.length() >= 12) {
code = resp.substring(9, 12);
} else {
throw new IOException("Invalid response from proxy: " + resp);
}
if (!"200".equals(code)) {
StringBuilder msg = new StringBuilder();
msg.append("HTTPTunnelConnector: connection failed\r\n");
msg.append("Response received from the proxy:\r\n");
for (String line : response) {
msg.append(line);
msg.append("\r\n");
}
throw new IOException(msg.toString());
}
return reader;
}
}

@ -0,0 +1,329 @@
/*
* 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 org.apache.commons.net.ftp;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import org.apache.commons.net.util.Charsets;
/**
* This class handles the entire process of parsing a listing of
* file entries from the server.
* <p>
* This object defines a two-part parsing mechanism.
* <p>
* The first part is comprised of reading the raw input into an internal
* list of strings. Every item in this list corresponds to an actual
* file. All extraneous matter emitted by the server will have been
* removed by the end of this phase. This is accomplished in conjunction
* with the FTPFileEntryParser associated with this engine, by calling
* its methods <code>readNextEntry()</code> - which handles the issue of
* what delimits one entry from another, usually but not always a line
* feed and <code>preParse()</code> - which handles removal of
* extraneous matter such as the preliminary lines of a listing, removal
* of duplicates on versioning systems, etc.
* <p>
* The second part is composed of the actual parsing, again in conjunction
* with the particular parser used by this engine. This is controlled
* by an iterator over the internal list of strings. This may be done
* either in block mode, by calling the <code>getNext()</code> and
* <code>getPrevious()</code> methods to provide "paged" output of less
* than the whole list at one time, or by calling the
* <code>getFiles()</code> method to return the entire list.
* <p>
* Examples:
* <p>
* Paged access:
* <pre>
* FTPClient f=FTPClient();
* f.connect(server);
* f.login(username, password);
* FTPListParseEngine engine = f.initiateListParsing(directory);
*
* while (engine.hasNext()) {
* FTPFile[] files = engine.getNext(25); // "page size" you want
* //do whatever you want with these files, display them, etc.
* //expensive FTPFile objects not created until needed.
* }
* </pre>
* <p>
* For unpaged access, simply use FTPClient.listFiles(). That method
* uses this class transparently.
* @version $Id: FTPListParseEngine.java 1747119 2016-06-07 02:22:24Z ggregory $
*/
public class FTPListParseEngine {
private List<String> entries = new LinkedList<String>();
private ListIterator<String> _internalIterator = entries.listIterator();
private final FTPFileEntryParser parser;
// Should invalid files (parse failures) be allowed?
private final boolean saveUnparseableEntries;
public FTPListParseEngine(FTPFileEntryParser parser) {
this(parser, null);
}
/**
* Intended for use by FTPClient only
* @since 3.4
*/
FTPListParseEngine(FTPFileEntryParser parser, FTPClientConfig configuration) {
this.parser = parser;
if (configuration != null) {
this.saveUnparseableEntries = configuration.getUnparseableEntries();
} else {
this.saveUnparseableEntries = false;
}
}
/**
* handle the initial reading and preparsing of the list returned by
* the server. After this method has completed, this object will contain
* a list of unparsed entries (Strings) each referring to a unique file
* on the server.
*
* @param stream input stream provided by the server socket.
* @param encoding the encoding to be used for reading the stream
*
* @throws IOException
* thrown on any failure to read from the sever.
*/
public void readServerList(InputStream stream, String encoding)
throws IOException
{
this.entries = new LinkedList<String>();
readStream(stream, encoding);
this.parser.preParse(this.entries);
resetIterator();
}
/**
* Internal method for reading the input into the <code>entries</code> list.
* After this method has completed, <code>entries</code> will contain a
* collection of entries (as defined by
* <code>FTPFileEntryParser.readNextEntry()</code>), but this may contain
* various non-entry preliminary lines from the server output, duplicates,
* and other data that will not be part of the final listing.
*
* @param stream The socket stream on which the input will be read.
* @param encoding The encoding to use.
*
* @throws IOException
* thrown on any failure to read the stream
*/
private void readStream(InputStream stream, String encoding) throws IOException
{
BufferedReader reader = new BufferedReader(
new InputStreamReader(stream, Charsets.toCharset(encoding)));
String line = this.parser.readNextEntry(reader);
while (line != null)
{
this.entries.add(line);
line = this.parser.readNextEntry(reader);
}
reader.close();
}
/**
* Returns an array of at most <code>quantityRequested</code> FTPFile
* objects starting at this object's internal iterator's current position.
* If fewer than <code>quantityRequested</code> such
* elements are available, the returned array will have a length equal
* to the number of entries at and after after the current position.
* If no such entries are found, this array will have a length of 0.
*
* After this method is called this object's internal iterator is advanced
* by a number of positions equal to the size of the array returned.
*
* @param quantityRequested
* the maximum number of entries we want to get.
*
* @return an array of at most <code>quantityRequested</code> FTPFile
* objects starting at the current position of this iterator within its
* list and at least the number of elements which exist in the list at
* and after its current position.
* <p><b>
* NOTE:</b> This array may contain null members if any of the
* individual file listings failed to parse. The caller should
* check each entry for null before referencing it.
*/
public FTPFile[] getNext(int quantityRequested) {
List<FTPFile> tmpResults = new LinkedList<FTPFile>();
int count = quantityRequested;
while (count > 0 && this._internalIterator.hasNext()) {
String entry = this._internalIterator.next();
FTPFile temp = this.parser.parseFTPEntry(entry);
if (temp == null && saveUnparseableEntries) {
temp = new FTPFile(entry);
}
tmpResults.add(temp);
count--;
}
return tmpResults.toArray(new FTPFile[tmpResults.size()]);
}
/**
* Returns an array of at most <code>quantityRequested</code> FTPFile
* objects starting at this object's internal iterator's current position,
* and working back toward the beginning.
*
* If fewer than <code>quantityRequested</code> such
* elements are available, the returned array will have a length equal
* to the number of entries at and after after the current position.
* If no such entries are found, this array will have a length of 0.
*
* After this method is called this object's internal iterator is moved
* back by a number of positions equal to the size of the array returned.
*
* @param quantityRequested
* the maximum number of entries we want to get.
*
* @return an array of at most <code>quantityRequested</code> FTPFile
* objects starting at the current position of this iterator within its
* list and at least the number of elements which exist in the list at
* and after its current position. This array will be in the same order
* as the underlying list (not reversed).
* <p><b>
* NOTE:</b> This array may contain null members if any of the
* individual file listings failed to parse. The caller should
* check each entry for null before referencing it.
*/
public FTPFile[] getPrevious(int quantityRequested) {
List<FTPFile> tmpResults = new LinkedList<FTPFile>();
int count = quantityRequested;
while (count > 0 && this._internalIterator.hasPrevious()) {
String entry = this._internalIterator.previous();
FTPFile temp = this.parser.parseFTPEntry(entry);
if (temp == null && saveUnparseableEntries) {
temp = new FTPFile(entry);
}
tmpResults.add(0,temp);
count--;
}
return tmpResults.toArray(new FTPFile[tmpResults.size()]);
}
/**
* Returns an array of FTPFile objects containing the whole list of
* files returned by the server as read by this object's parser.
*
* @return an array of FTPFile objects containing the whole list of
* files returned by the server as read by this object's parser.
* None of the entries will be null
* @throws IOException - not ever thrown, may be removed in a later release
*/
public FTPFile[] getFiles()
throws IOException // TODO remove; not actually thrown
{
return getFiles(FTPFileFilters.NON_NULL);
}
/**
* Returns an array of FTPFile objects containing the whole list of
* files returned by the server as read by this object's parser.
* The files are filtered before being added to the array.
*
* @param filter FTPFileFilter, must not be <code>null</code>.
*
* @return an array of FTPFile objects containing the whole list of
* files returned by the server as read by this object's parser.
* <p><b>
* NOTE:</b> This array may contain null members if any of the
* individual file listings failed to parse. The caller should
* check each entry for null before referencing it, or use the
* a filter such as {@link FTPFileFilters#NON_NULL} which does not
* allow null entries.
* @since 2.2
* @throws IOException - not ever thrown, may be removed in a later release
*/
public FTPFile[] getFiles(FTPFileFilter filter)
throws IOException // TODO remove; not actually thrown
{
List<FTPFile> tmpResults = new ArrayList<FTPFile>();
Iterator<String> iter = this.entries.iterator();
while (iter.hasNext()) {
String entry = iter.next();
FTPFile temp = this.parser.parseFTPEntry(entry);
if (temp == null && saveUnparseableEntries) {
temp = new FTPFile(entry);
}
if (filter.accept(temp)){
tmpResults.add(temp);
}
}
return tmpResults.toArray(new FTPFile[tmpResults.size()]);
}
/**
* convenience method to allow clients to know whether this object's
* internal iterator's current position is at the end of the list.
*
* @return true if internal iterator is not at end of list, false
* otherwise.
*/
public boolean hasNext() {
return _internalIterator.hasNext();
}
/**
* convenience method to allow clients to know whether this object's
* internal iterator's current position is at the beginning of the list.
*
* @return true if internal iterator is not at beginning of list, false
* otherwise.
*/
public boolean hasPrevious() {
return _internalIterator.hasPrevious();
}
/**
* resets this object's internal iterator to the beginning of the list.
*/
public void resetIterator() {
this._internalIterator = this.entries.listIterator();
}
// DEPRECATED METHODS - for API compatibility only - DO NOT USE
/**
* Do not use.
* @param stream the stream from which to read
* @throws IOException on error
* @deprecated use {@link #readServerList(InputStream, String)} instead
*/
@Deprecated
public void readServerList(InputStream stream)
throws IOException
{
readServerList(stream, null);
}
}

@ -0,0 +1,201 @@
/*
* 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 org.apache.commons.net.ftp;
/***
* FTPReply stores a set of constants for FTP reply codes. To interpret
* the meaning of the codes, familiarity with RFC 959 is assumed.
* The mnemonic constant names are transcriptions from the code descriptions
* of RFC 959.
* <p>
* TODO replace with an enum
***/
public final class FTPReply
{
public static final int RESTART_MARKER = 110;
public static final int SERVICE_NOT_READY = 120;
public static final int DATA_CONNECTION_ALREADY_OPEN = 125;
public static final int FILE_STATUS_OK = 150;
public static final int COMMAND_OK = 200;
public static final int COMMAND_IS_SUPERFLUOUS = 202;
public static final int SYSTEM_STATUS = 211;
public static final int DIRECTORY_STATUS = 212;
public static final int FILE_STATUS = 213;
public static final int HELP_MESSAGE = 214;
public static final int NAME_SYSTEM_TYPE = 215;
public static final int SERVICE_READY = 220;
public static final int SERVICE_CLOSING_CONTROL_CONNECTION = 221;
public static final int DATA_CONNECTION_OPEN = 225;
public static final int CLOSING_DATA_CONNECTION = 226;
public static final int ENTERING_PASSIVE_MODE = 227;
/** @since 2.2 */
public static final int ENTERING_EPSV_MODE = 229;
public static final int USER_LOGGED_IN = 230;
public static final int FILE_ACTION_OK = 250;
public static final int PATHNAME_CREATED = 257;
public static final int NEED_PASSWORD = 331;
public static final int NEED_ACCOUNT = 332;
public static final int FILE_ACTION_PENDING = 350;
public static final int SERVICE_NOT_AVAILABLE = 421;
public static final int CANNOT_OPEN_DATA_CONNECTION = 425;
public static final int TRANSFER_ABORTED = 426;
public static final int FILE_ACTION_NOT_TAKEN = 450;
public static final int ACTION_ABORTED = 451;
public static final int INSUFFICIENT_STORAGE = 452;
public static final int UNRECOGNIZED_COMMAND = 500;
public static final int SYNTAX_ERROR_IN_ARGUMENTS = 501;
public static final int COMMAND_NOT_IMPLEMENTED = 502;
public static final int BAD_COMMAND_SEQUENCE = 503;
public static final int COMMAND_NOT_IMPLEMENTED_FOR_PARAMETER = 504;
public static final int NOT_LOGGED_IN = 530;
public static final int NEED_ACCOUNT_FOR_STORING_FILES = 532;
public static final int FILE_UNAVAILABLE = 550;
public static final int PAGE_TYPE_UNKNOWN = 551;
public static final int STORAGE_ALLOCATION_EXCEEDED = 552;
public static final int FILE_NAME_NOT_ALLOWED = 553;
// FTPS Reply Codes
/** @since 2.0 */
public static final int SECURITY_DATA_EXCHANGE_COMPLETE = 234;
/** @since 2.0 */
public static final int SECURITY_DATA_EXCHANGE_SUCCESSFULLY = 235;
/** @since 2.0 */
public static final int SECURITY_MECHANISM_IS_OK = 334;
/** @since 2.0 */
public static final int SECURITY_DATA_IS_ACCEPTABLE = 335;
/** @since 2.0 */
public static final int UNAVAILABLE_RESOURCE = 431;
/** @since 2.2 */
public static final int BAD_TLS_NEGOTIATION_OR_DATA_ENCRYPTION_REQUIRED = 522;
/** @since 2.0 */
public static final int DENIED_FOR_POLICY_REASONS = 533;
/** @since 2.0 */
public static final int REQUEST_DENIED = 534;
/** @since 2.0 */
public static final int FAILED_SECURITY_CHECK = 535;
/** @since 2.0 */
public static final int REQUESTED_PROT_LEVEL_NOT_SUPPORTED = 536;
// IPv6 error codes
// Note this is also used as an FTPS error code reply
/** @since 2.2 */
public static final int EXTENDED_PORT_FAILURE = 522;
// Cannot be instantiated
private FTPReply()
{}
/***
* Determine if a reply code is a positive preliminary response. All
* codes beginning with a 1 are positive preliminary responses.
* Postitive preliminary responses are used to indicate tentative success.
* No further commands can be issued to the FTP server after a positive
* preliminary response until a follow up response is received from the
* server.
*
* @param reply The reply code to test.
* @return True if a reply code is a postive preliminary response, false
* if not.
***/
public static boolean isPositivePreliminary(int reply)
{
return (reply >= 100 && reply < 200);
}
/***
* Determine if a reply code is a positive completion response. All
* codes beginning with a 2 are positive completion responses.
* The FTP server will send a positive completion response on the final
* successful completion of a command.
*
* @param reply The reply code to test.
* @return True if a reply code is a postive completion response, false
* if not.
***/
public static boolean isPositiveCompletion(int reply)
{
return (reply >= 200 && reply < 300);
}
/***
* Determine if a reply code is a positive intermediate response. All
* codes beginning with a 3 are positive intermediate responses.
* The FTP server will send a positive intermediate response on the
* successful completion of one part of a multi-part sequence of
* commands. For example, after a successful USER command, a positive
* intermediate response will be sent to indicate that the server is
* ready for the PASS command.
*
* @param reply The reply code to test.
* @return True if a reply code is a postive intermediate response, false
* if not.
***/
public static boolean isPositiveIntermediate(int reply)
{
return (reply >= 300 && reply < 400);
}
/***
* Determine if a reply code is a negative transient response. All
* codes beginning with a 4 are negative transient responses.
* The FTP server will send a negative transient response on the
* failure of a command that can be reattempted with success.
*
* @param reply The reply code to test.
* @return True if a reply code is a negative transient response, false
* if not.
***/
public static boolean isNegativeTransient(int reply)
{
return (reply >= 400 && reply < 500);
}
/***
* Determine if a reply code is a negative permanent response. All
* codes beginning with a 5 are negative permanent responses.
* The FTP server will send a negative permanent response on the
* failure of a command that cannot be reattempted with success.
*
* @param reply The reply code to test.
* @return True if a reply code is a negative permanent response, false
* if not.
***/
public static boolean isNegativePermanent(int reply)
{
return (reply >= 500 && reply < 600);
}
/**
* Determine if a reply code is a protected response.
* @param reply The reply code to test.
* @return True if a reply code is a protected response, false
* if not.
* @since 3.0
*/
public static boolean isProtectedReplyCode(int reply)
{
// actually, only 3 protected reply codes are
// defined in RFC 2228: 631, 632 and 633.
return (reply >= 600 && reply < 700);
}
}

@ -0,0 +1,925 @@
/*
* 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 org.apache.commons.net.ftp;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import org.apache.commons.net.util.Base64;
import org.apache.commons.net.util.SSLContextUtils;
import org.apache.commons.net.util.SSLSocketUtils;
import org.apache.commons.net.util.TrustManagerUtils;
/**
* FTP over SSL processing. If desired, the JVM property -Djavax.net.debug=all can be used to
* see wire-level SSL details.
*
* Warning: the hostname is not verified against the certificate by default, use
* {@link #setHostnameVerifier(HostnameVerifier)} or {@link #setEndpointCheckingEnabled(boolean)}
* (on Java 1.7+) to enable verification. Verification is only performed on client mode connections.
* @version $Id: FTPSClient.java 1747829 2016-06-11 00:57:57Z sebb $
* @since 2.0
*/
public class FTPSClient extends FTPClient {
// From http://www.iana.org/assignments/port-numbers
// ftps-data 989/tcp ftp protocol, data, over TLS/SSL
// ftps-data 989/udp ftp protocol, data, over TLS/SSL
// ftps 990/tcp ftp protocol, control, over TLS/SSL
// ftps 990/udp ftp protocol, control, over TLS/SSL
public static final int DEFAULT_FTPS_DATA_PORT = 989;
public static final int DEFAULT_FTPS_PORT = 990;
/** The value that I can set in PROT command (C = Clear, P = Protected) */
private static final String[] PROT_COMMAND_VALUE = {"C","E","S","P"};
/** Default PROT Command */
private static final String DEFAULT_PROT = "C";
/** Default secure socket protocol name, i.e. TLS */
private static final String DEFAULT_PROTOCOL = "TLS";
/** The AUTH (Authentication/Security Mechanism) command. */
private static final String CMD_AUTH = "AUTH";
/** The ADAT (Authentication/Security Data) command. */
private static final String CMD_ADAT = "ADAT";
/** The PROT (Data Channel Protection Level) command. */
private static final String CMD_PROT = "PROT";
/** The PBSZ (Protection Buffer Size) command. */
private static final String CMD_PBSZ = "PBSZ";
/** The MIC (Integrity Protected Command) command. */
private static final String CMD_MIC = "MIC";
/** The CONF (Confidentiality Protected Command) command. */
private static final String CMD_CONF = "CONF";
/** The ENC (Privacy Protected Command) command. */
private static final String CMD_ENC = "ENC";
/** The CCC (Clear Command Channel) command. */
private static final String CMD_CCC = "CCC";
/** The security mode. (True - Implicit Mode / False - Explicit Mode) */
private final boolean isImplicit;
/** The secure socket protocol to be used, e.g. SSL/TLS. */
private final String protocol;
/** The AUTH Command value */
private String auth = DEFAULT_PROTOCOL;
/** The context object. */
private SSLContext context;
/** The socket object. */
private Socket plainSocket;
/** Controls whether a new SSL session may be established by this socket. Default true. */
private boolean isCreation = true;
/** The use client mode flag. */
private boolean isClientMode = true;
/** The need client auth flag. */
private boolean isNeedClientAuth = false;
/** The want client auth flag. */
private boolean isWantClientAuth = false;
/** The cipher suites */
private String[] suites = null;
/** The protocol versions */
private String[] protocols = null;
/** The FTPS {@link TrustManager} implementation, default validate only
* {@link TrustManagerUtils#getValidateServerCertificateTrustManager()}.
*/
private TrustManager trustManager = TrustManagerUtils.getValidateServerCertificateTrustManager();
/** The {@link KeyManager}, default null (i.e. use system default). */
private KeyManager keyManager = null;
/** The {@link HostnameVerifier} to use post-TLS, default null (i.e. no verification). */
private HostnameVerifier hostnameVerifier = null;
/** Use Java 1.7+ HTTPS Endpoint Identification Algorithim. */
private boolean tlsEndpointChecking;
/**
* Constructor for FTPSClient, calls {@link #FTPSClient(String, boolean)}.
*
* Sets protocol to {@link #DEFAULT_PROTOCOL} - i.e. TLS - and security mode to explicit (isImplicit = false)
*/
public FTPSClient() {
this(DEFAULT_PROTOCOL, false);
}
/**
* Constructor for FTPSClient, using {@link #DEFAULT_PROTOCOL} - i.e. TLS
* Calls {@link #FTPSClient(String, boolean)}
* @param isImplicit The security mode (Implicit/Explicit).
*/
public FTPSClient(boolean isImplicit) {
this(DEFAULT_PROTOCOL, isImplicit);
}
/**
* Constructor for FTPSClient, using explict mode, calls {@link #FTPSClient(String, boolean)}.
*
* @param protocol the protocol to use
*/
public FTPSClient(String protocol) {
this(protocol, false);
}
/**
* Constructor for FTPSClient allowing specification of protocol
* and security mode. If isImplicit is true, the port is set to
* {@link #DEFAULT_FTPS_PORT} i.e. 990.
* The default TrustManager is set from {@link TrustManagerUtils#getValidateServerCertificateTrustManager()}
* @param protocol the protocol
* @param isImplicit The security mode(Implicit/Explicit).
*/
public FTPSClient(String protocol, boolean isImplicit) {
super();
this.protocol = protocol;
this.isImplicit = isImplicit;
if (isImplicit) {
setDefaultPort(DEFAULT_FTPS_PORT);
}
}
/**
* Constructor for FTPSClient, using {@link #DEFAULT_PROTOCOL} - i.e. TLS
* The default TrustManager is set from {@link TrustManagerUtils#getValidateServerCertificateTrustManager()}
* @param isImplicit The security mode(Implicit/Explicit).
* @param context A pre-configured SSL Context
*/
public FTPSClient(boolean isImplicit, SSLContext context) {
this(DEFAULT_PROTOCOL, isImplicit);
this.context = context;
}
/**
* Constructor for FTPSClient, using {@link #DEFAULT_PROTOCOL} - i.e. TLS
* and isImplicit {@code false}
* Calls {@link #FTPSClient(boolean, SSLContext)}
* @param context A pre-configured SSL Context
*/
public FTPSClient(SSLContext context) {
this(false, context);
}
/**
* Set AUTH command use value.
* This processing is done before connected processing.
* @param auth AUTH command use value.
*/
public void setAuthValue(String auth) {
this.auth = auth;
}
/**
* Return AUTH command use value.
* @return AUTH command use value.
*/
public String getAuthValue() {
return this.auth;
}
/**
* Because there are so many connect() methods,
* the _connectAction_() method is provided as a means of performing
* some action immediately after establishing a connection,
* rather than reimplementing all of the connect() methods.
* @throws IOException If it throw by _connectAction_.
* @see org.apache.commons.net.SocketClient#_connectAction_()
*/
@Override
protected void _connectAction_() throws IOException {
// Implicit mode.
if (isImplicit) {
sslNegotiation();
}
super._connectAction_();
// Explicit mode.
if (!isImplicit) {
execAUTH();
sslNegotiation();
}
}
/**
* AUTH command.
* @throws SSLException If it server reply code not equal "234" and "334".
* @throws IOException If an I/O error occurs while either sending
* the command.
*/
protected void execAUTH() throws SSLException, IOException {
int replyCode = sendCommand(CMD_AUTH, auth);
if (FTPReply.SECURITY_MECHANISM_IS_OK == replyCode) {
// replyCode = 334
// I carry out an ADAT command.
} else if (FTPReply.SECURITY_DATA_EXCHANGE_COMPLETE != replyCode) {
throw new SSLException(getReplyString());
}
}
/**
* Performs a lazy init of the SSL context
* @throws IOException
*/
private void initSslContext() throws IOException {
if (context == null) {
context = SSLContextUtils.createSSLContext(protocol, getKeyManager(), getTrustManager());
}
}
/**
* SSL/TLS negotiation. Acquires an SSL socket of a control
* connection and carries out handshake processing.
* @throws IOException If server negotiation fails
*/
protected void sslNegotiation() throws IOException {
plainSocket = _socket_;
initSslContext();
SSLSocketFactory ssf = context.getSocketFactory();
String host = (_hostname_ != null) ? _hostname_ : getRemoteAddress().getHostAddress();
int port = _socket_.getPort();
SSLSocket socket =
(SSLSocket) ssf.createSocket(_socket_, host, port, false);
socket.setEnableSessionCreation(isCreation);
socket.setUseClientMode(isClientMode);
// client mode
if (isClientMode) {
if (tlsEndpointChecking) {
SSLSocketUtils.enableEndpointNameVerification(socket);
}
} else { // server mode
socket.setNeedClientAuth(isNeedClientAuth);
socket.setWantClientAuth(isWantClientAuth);
}
if (protocols != null) {
socket.setEnabledProtocols(protocols);
}
if (suites != null) {
socket.setEnabledCipherSuites(suites);
}
socket.startHandshake();
// TODO the following setup appears to duplicate that in the super class methods
_socket_ = socket;
_controlInput_ = new BufferedReader(new InputStreamReader(
socket .getInputStream(), getControlEncoding()));
_controlOutput_ = new BufferedWriter(new OutputStreamWriter(
socket.getOutputStream(), getControlEncoding()));
if (isClientMode) {
if (hostnameVerifier != null && !hostnameVerifier.verify(host, socket.getSession())) {
throw new SSLHandshakeException("Hostname doesn't match certificate");
}
}
}
/**
* Get the {@link KeyManager} instance.
* @return The {@link KeyManager} instance
*/
private KeyManager getKeyManager() {
return keyManager;
}
/**
* Set a {@link KeyManager} to use
*
* @param keyManager The KeyManager implementation to set.
* @see org.apache.commons.net.util.KeyManagerUtils
*/
public void setKeyManager(KeyManager keyManager) {
this.keyManager = keyManager;
}
/**
* Controls whether a new SSL session may be established by this socket.
* @param isCreation The established socket flag.
*/
public void setEnabledSessionCreation(boolean isCreation) {
this.isCreation = isCreation;
}
/**
* Returns true if new SSL sessions may be established by this socket.
* When the underlying {@link Socket} instance is not SSL-enabled (i.e. an
* instance of {@link SSLSocket} with {@link SSLSocket}{@link #getEnableSessionCreation()}) enabled,
* this returns False.
* @return true - Indicates that sessions may be created;
* this is the default.
* false - indicates that an existing session must be resumed.
*/
public boolean getEnableSessionCreation() {
if (_socket_ instanceof SSLSocket) {
return ((SSLSocket)_socket_).getEnableSessionCreation();
}
return false;
}
/**
* Configures the socket to require client authentication.
* @param isNeedClientAuth The need client auth flag.
*/
public void setNeedClientAuth(boolean isNeedClientAuth) {
this.isNeedClientAuth = isNeedClientAuth;
}
/**
* Returns true if the socket will require client authentication.
* When the underlying {@link Socket} is not an {@link SSLSocket} instance, returns false.
* @return true - If the server mode socket should request
* that the client authenticate itself.
*/
public boolean getNeedClientAuth() {
if (_socket_ instanceof SSLSocket) {
return ((SSLSocket)_socket_).getNeedClientAuth();
}
return false;
}
/**
* Configures the socket to request client authentication,
* but only if such a request is appropriate to the cipher
* suite negotiated.
* @param isWantClientAuth The want client auth flag.
*/
public void setWantClientAuth(boolean isWantClientAuth) {
this.isWantClientAuth = isWantClientAuth;
}
/**
* Returns true if the socket will request client authentication.
* When the underlying {@link Socket} is not an {@link SSLSocket} instance, returns false.
* @return true - If the server mode socket should request
* that the client authenticate itself.
*/
public boolean getWantClientAuth() {
if (_socket_ instanceof SSLSocket) {
return ((SSLSocket)_socket_).getWantClientAuth();
}
return false;
}
/**
* Configures the socket to use client (or server) mode in its first
* handshake.
* @param isClientMode The use client mode flag.
*/
public void setUseClientMode(boolean isClientMode) {
this.isClientMode = isClientMode;
}
/**
* Returns true if the socket is set to use client mode
* in its first handshake.
* When the underlying {@link Socket} is not an {@link SSLSocket} instance, returns false.
* @return true - If the socket should start its first handshake
* in "client" mode.
*/
public boolean getUseClientMode() {
if (_socket_ instanceof SSLSocket) {
return ((SSLSocket)_socket_).getUseClientMode();
}
return false;
}
/**
* Controls which particular cipher suites are enabled for use on this
* connection. Called before server negotiation.
* @param cipherSuites The cipher suites.
*/
public void setEnabledCipherSuites(String[] cipherSuites) {
suites = new String[cipherSuites.length];
System.arraycopy(cipherSuites, 0, suites, 0, cipherSuites.length);
}
/**
* Returns the names of the cipher suites which could be enabled
* for use on this connection.
* When the underlying {@link Socket} is not an {@link SSLSocket} instance, returns null.
* @return An array of cipher suite names, or <code>null</code>
*/
public String[] getEnabledCipherSuites() {
if (_socket_ instanceof SSLSocket) {
return ((SSLSocket)_socket_).getEnabledCipherSuites();
}
return null;
}
/**
* Controls which particular protocol versions are enabled for use on this
* connection. I perform setting before a server negotiation.
* @param protocolVersions The protocol versions.
*/
public void setEnabledProtocols(String[] protocolVersions) {
protocols = new String[protocolVersions.length];
System.arraycopy(protocolVersions, 0, protocols, 0, protocolVersions.length);
}
/**
* Returns the names of the protocol versions which are currently
* enabled for use on this connection.
* When the underlying {@link Socket} is not an {@link SSLSocket} instance, returns null.
* @return An array of protocols, or <code>null</code>
*/
public String[] getEnabledProtocols() {
if (_socket_ instanceof SSLSocket) {
return ((SSLSocket)_socket_).getEnabledProtocols();
}
return null;
}
/**
* PBSZ command. pbsz value: 0 to (2^32)-1 decimal integer.
* @param pbsz Protection Buffer Size.
* @throws SSLException If the server reply code does not equal "200".
* @throws IOException If an I/O error occurs while sending
* the command.
* @see #parsePBSZ(long)
*/
public void execPBSZ(long pbsz) throws SSLException, IOException {
if (pbsz < 0 || 4294967295L < pbsz) { // 32-bit unsigned number
throw new IllegalArgumentException();
}
int status = sendCommand(CMD_PBSZ, String.valueOf(pbsz));
if (FTPReply.COMMAND_OK != status) {
throw new SSLException(getReplyString());
}
}
/**
* PBSZ command. pbsz value: 0 to (2^32)-1 decimal integer.
* Issues the command and parses the response to return the negotiated value.
*
* @param pbsz Protection Buffer Size.
* @throws SSLException If the server reply code does not equal "200".
* @throws IOException If an I/O error occurs while sending
* the command.
* @return the negotiated value.
* @see #execPBSZ(long)
* @since 3.0
*/
public long parsePBSZ(long pbsz) throws SSLException, IOException {
execPBSZ(pbsz);
long minvalue = pbsz;
String remainder = extractPrefixedData("PBSZ=", getReplyString());
if (remainder != null) {
long replysz = Long.parseLong(remainder);
if (replysz < minvalue) {
minvalue = replysz;
}
}
return minvalue;
}
/**
* PROT command.
* <ul>
* <li>C - Clear</li>
* <li>S - Safe(SSL protocol only)</li>
* <li>E - Confidential(SSL protocol only)</li>
* <li>P - Private</li>
* </ul>
* <b>N.B.</b> the method calls
* {@link #setSocketFactory(javax.net.SocketFactory)} and
* {@link #setServerSocketFactory(javax.net.ServerSocketFactory)}
*
* @param prot Data Channel Protection Level, if {@code null}, use {@link #DEFAULT_PROT}.
* @throws SSLException If the server reply code does not equal {@code 200}.
* @throws IOException If an I/O error occurs while sending
* the command.
*/
public void execPROT(String prot) throws SSLException, IOException {
if (prot == null) {
prot = DEFAULT_PROT;
}
if (!checkPROTValue(prot)) {
throw new IllegalArgumentException();
}
if (FTPReply.COMMAND_OK != sendCommand(CMD_PROT, prot)) {
throw new SSLException(getReplyString());
}
if (DEFAULT_PROT.equals(prot)) {
setSocketFactory(null);
setServerSocketFactory(null);
} else {
setSocketFactory(new FTPSSocketFactory(context));
setServerSocketFactory(new FTPSServerSocketFactory(context));
initSslContext();
}
}
/**
* Check the value that can be set in PROT Command value.
* @param prot Data Channel Protection Level.
* @return True - A set point is right / False - A set point is not right
*/
private boolean checkPROTValue(String prot) {
for (String element : PROT_COMMAND_VALUE)
{
if (element.equals(prot)) {
return true;
}
}
return false;
}
/**
* Send an FTP command.
* A successful CCC (Clear Command Channel) command causes the underlying {@link SSLSocket}
* instance to be assigned to a plain {@link Socket}
* @param command The FTP command.
* @return server reply.
* @throws IOException If an I/O error occurs while sending the command.
* @throws SSLException if a CCC command fails
* @see FTP#sendCommand(String)
*/
// Would like to remove this method, but that will break any existing clients that are using CCC
@Override
public int sendCommand(String command, String args) throws IOException {
int repCode = super.sendCommand(command, args);
/* If CCC is issued, restore socket i/o streams to unsecured versions */
if (CMD_CCC.equals(command)) {
if (FTPReply.COMMAND_OK == repCode) {
_socket_.close();
_socket_ = plainSocket;
_controlInput_ = new BufferedReader(
new InputStreamReader(
_socket_ .getInputStream(), getControlEncoding()));
_controlOutput_ = new BufferedWriter(
new OutputStreamWriter(
_socket_.getOutputStream(), getControlEncoding()));
} else {
throw new SSLException(getReplyString());
}
}
return repCode;
}
/**
* Returns a socket of the data connection.
* Wrapped as an {@link SSLSocket}, which carries out handshake processing.
* @param command The int representation of the FTP command to send.
* @param arg The arguments to the FTP command.
* If this parameter is set to null, then the command is sent with
* no arguments.
* @return corresponding to the established data connection.
* Null is returned if an FTP protocol error is reported at any point
* during the establishment and initialization of the connection.
* @throws IOException If there is any problem with the connection.
* @see FTPClient#_openDataConnection_(int, String)
* @deprecated (3.3) Use {@link FTPClient#_openDataConnection_(FTPCmd, String)} instead
*/
@Override
// Strictly speaking this is not needed, but it works round a Clirr bug
// So rather than invoke the parent code, we do it here
@Deprecated
protected Socket _openDataConnection_(int command, String arg)
throws IOException {
return _openDataConnection_(FTPCommand.getCommand(command), arg);
}
/**
* Returns a socket of the data connection.
* Wrapped as an {@link SSLSocket}, which carries out handshake processing.
* @param command The textual representation of the FTP command to send.
* @param arg The arguments to the FTP command.
* If this parameter is set to null, then the command is sent with
* no arguments.
* @return corresponding to the established data connection.
* Null is returned if an FTP protocol error is reported at any point
* during the establishment and initialization of the connection.
* @throws IOException If there is any problem with the connection.
* @see FTPClient#_openDataConnection_(int, String)
* @since 3.2
*/
@Override
protected Socket _openDataConnection_(String command, String arg)
throws IOException {
Socket socket = super._openDataConnection_(command, arg);
_prepareDataSocket_(socket);
if (socket instanceof SSLSocket) {
SSLSocket sslSocket = (SSLSocket)socket;
sslSocket.setUseClientMode(isClientMode);
sslSocket.setEnableSessionCreation(isCreation);
// server mode
if (!isClientMode) {
sslSocket.setNeedClientAuth(isNeedClientAuth);
sslSocket.setWantClientAuth(isWantClientAuth);
}
if (suites != null) {
sslSocket.setEnabledCipherSuites(suites);
}
if (protocols != null) {
sslSocket.setEnabledProtocols(protocols);
}
sslSocket.startHandshake();
}
return socket;
}
/**
* Performs any custom initialization for a newly created SSLSocket (before
* the SSL handshake happens).
* Called by {@link #_openDataConnection_(int, String)} immediately
* after creating the socket.
* The default implementation is a no-op
* @param socket the socket to set up
* @throws IOException on error
* @since 3.1
*/
protected void _prepareDataSocket_(Socket socket)
throws IOException {
}
/**
* Get the currently configured {@link TrustManager}.
*
* @return A TrustManager instance.
*/
public TrustManager getTrustManager() {
return trustManager;
}
/**
* Override the default {@link TrustManager} to use; if set to {@code null},
* the default TrustManager from the JVM will be used.
*
* @param trustManager The TrustManager implementation to set, may be {@code null}
* @see org.apache.commons.net.util.TrustManagerUtils
*/
public void setTrustManager(TrustManager trustManager) {
this.trustManager = trustManager;
}
/**
* Get the currently configured {@link HostnameVerifier}.
* The verifier is only used on client mode connections.
* @return A HostnameVerifier instance.
* @since 3.4
*/
public HostnameVerifier getHostnameVerifier()
{
return hostnameVerifier;
}
/**
* Override the default {@link HostnameVerifier} to use.
* The verifier is only used on client mode connections.
* @param newHostnameVerifier The HostnameVerifier implementation to set or <code>null</code> to disable.
* @since 3.4
*/
public void setHostnameVerifier(HostnameVerifier newHostnameVerifier)
{
hostnameVerifier = newHostnameVerifier;
}
/**
* Return whether or not endpoint identification using the HTTPS algorithm
* on Java 1.7+ is enabled. The default behaviour is for this to be disabled.
*
* This check is only performed on client mode connections.
*
* @return True if enabled, false if not.
* @since 3.4
*/
public boolean isEndpointCheckingEnabled()
{
return tlsEndpointChecking;
}
/**
* Automatic endpoint identification checking using the HTTPS algorithm
* is supported on Java 1.7+. The default behaviour is for this to be disabled.
*
* This check is only performed on client mode connections.
*
* @param enable Enable automatic endpoint identification checking using the HTTPS algorithm on Java 1.7+.
* @since 3.4
*/
public void setEndpointCheckingEnabled(boolean enable)
{
tlsEndpointChecking = enable;
}
/**
* Closes the connection to the FTP server and restores
* connection parameters to the default values.
* <p>
* Calls {@code setSocketFactory(null)} and {@code setServerSocketFactory(null)}
* to reset the factories that may have been changed during the session,
* e.g. by {@link #execPROT(String)}
* @throws IOException If an error occurs while disconnecting.
* @since 3.0
*/
@Override
public void disconnect() throws IOException
{
super.disconnect();
if (plainSocket != null) {
plainSocket.close();
}
setSocketFactory(null);
setServerSocketFactory(null);
}
/**
* Send the AUTH command with the specified mechanism.
* @param mechanism The mechanism name to send with the command.
* @return server reply.
* @throws IOException If an I/O error occurs while sending
* the command.
* @since 3.0
*/
public int execAUTH(String mechanism) throws IOException
{
return sendCommand(CMD_AUTH, mechanism);
}
/**
* Send the ADAT command with the specified authentication data.
* @param data The data to send with the command.
* @return server reply.
* @throws IOException If an I/O error occurs while sending
* the command.
* @since 3.0
*/
public int execADAT(byte[] data) throws IOException
{
if (data != null)
{
return sendCommand(CMD_ADAT, Base64.encodeBase64StringUnChunked(data));
}
else
{
return sendCommand(CMD_ADAT);
}
}
/**
* Send the CCC command to the server.
* The CCC (Clear Command Channel) command causes the underlying {@link SSLSocket} instance to be assigned
* to a plain {@link Socket} instances
* @return server reply.
* @throws IOException If an I/O error occurs while sending
* the command.
* @since 3.0
*/
public int execCCC() throws IOException
{
int repCode = sendCommand(CMD_CCC);
// This will be performed by sendCommand(String, String)
// if (FTPReply.isPositiveCompletion(repCode)) {
// _socket_.close();
// _socket_ = plainSocket;
// _controlInput_ = new BufferedReader(
// new InputStreamReader(
// _socket_.getInputStream(), getControlEncoding()));
// _controlOutput_ = new BufferedWriter(
// new OutputStreamWriter(
// _socket_.getOutputStream(), getControlEncoding()));
// }
return repCode;
}
/**
* Send the MIC command with the specified data.
* @param data The data to send with the command.
* @return server reply.
* @throws IOException If an I/O error occurs while sending
* the command.
* @since 3.0
*/
public int execMIC(byte[] data) throws IOException
{
if (data != null)
{
return sendCommand(CMD_MIC, Base64.encodeBase64StringUnChunked(data));
}
else
{
return sendCommand(CMD_MIC, ""); // perhaps "=" or just sendCommand(String)?
}
}
/**
* Send the CONF command with the specified data.
* @param data The data to send with the command.
* @return server reply.
* @throws IOException If an I/O error occurs while sending
* the command.
* @since 3.0
*/
public int execCONF(byte[] data) throws IOException
{
if (data != null)
{
return sendCommand(CMD_CONF, Base64.encodeBase64StringUnChunked(data));
}
else
{
return sendCommand(CMD_CONF, ""); // perhaps "=" or just sendCommand(String)?
}
}
/**
* Send the ENC command with the specified data.
* @param data The data to send with the command.
* @return server reply.
* @throws IOException If an I/O error occurs while sending
* the command.
* @since 3.0
*/
public int execENC(byte[] data) throws IOException
{
if (data != null)
{
return sendCommand(CMD_ENC, Base64.encodeBase64StringUnChunked(data));
}
else
{
return sendCommand(CMD_ENC, ""); // perhaps "=" or just sendCommand(String)?
}
}
/**
* Parses the given ADAT response line and base64-decodes the data.
* @param reply The ADAT reply to parse.
* @return the data in the reply, base64-decoded.
* @since 3.0
*/
public byte[] parseADATReply(String reply)
{
if (reply == null) {
return null;
} else {
return Base64.decodeBase64(extractPrefixedData("ADAT=", reply));
}
}
/**
* Extract the data from a reply with a prefix, e.g. PBSZ=1234 => 1234
* @param prefix the prefix to find
* @param reply where to find the prefix
* @return the remainder of the string after the prefix, or null if the prefix was not present.
*/
private String extractPrefixedData(String prefix, String reply) {
int idx = reply.indexOf(prefix);
if (idx == -1) {
return null;
}
// N.B. Cannot use trim before substring as leading space would affect the offset.
return reply.substring(idx+prefix.length()).trim();
}
// DEPRECATED - for API compatibility only - DO NOT USE
/** @deprecated - not used - may be removed in a future release */
@Deprecated
public static String KEYSTORE_ALGORITHM;
/** @deprecated - not used - may be removed in a future release */
@Deprecated
public static String TRUSTSTORE_ALGORITHM;
/** @deprecated - not used - may be removed in a future release */
@Deprecated
public static String PROVIDER;
/** @deprecated - not used - may be removed in a future release */
@Deprecated
public static String STORE_TYPE;
}
/* kate: indent-width 4; replace-tabs on; */

@ -0,0 +1,52 @@
/*
* 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 org.apache.commons.net.ftp;
/**
* FTPS-specific commands.
* @since 2.0
* @deprecated 3.0 DO NOT USE
*/
@Deprecated
public final class FTPSCommand {
public static final int AUTH = 0;
public static final int ADAT = 1;
public static final int PBSZ = 2;
public static final int PROT = 3;
public static final int CCC = 4;
public static final int AUTHENTICATION_SECURITY_MECHANISM = AUTH;
public static final int AUTHENTICATION_SECURITY_DATA = ADAT;
public static final int PROTECTION_BUFFER_SIZE = PBSZ;
public static final int DATA_CHANNEL_PROTECTION_LEVEL = PROT;
public static final int CLEAR_COMMAND_CHANNEL = CCC;
private static final String[] _commands = {"AUTH","ADAT","PBSZ","PROT","CCC"};
/**
* Retrieve the FTPS command string corresponding to a specified
* command code.
*
* @param command The command code.
* @return The FTPS command string corresponding to a specified
* command code.
*/
public static final String getCommand(int command) {
return _commands[command];
}
}

@ -0,0 +1,74 @@
/*
* 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 org.apache.commons.net.ftp;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import javax.net.ServerSocketFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocket;
/**
* Server socket factory for FTPS connections.
* @since 2.2
*/
public class FTPSServerSocketFactory extends ServerSocketFactory {
/** Factory for secure socket factories */
private final SSLContext context;
public FTPSServerSocketFactory(SSLContext context) {
this.context = context;
}
// Override the default superclass method
@Override
public ServerSocket createServerSocket() throws IOException {
return init(this.context.getServerSocketFactory().createServerSocket());
}
@Override
public ServerSocket createServerSocket(int port) throws IOException {
return init(this.context.getServerSocketFactory().createServerSocket(port));
}
@Override
public ServerSocket createServerSocket(int port, int backlog) throws IOException {
return init(this.context.getServerSocketFactory().createServerSocket(port, backlog));
}
@Override
public ServerSocket createServerSocket(int port, int backlog, InetAddress ifAddress) throws IOException {
return init(this.context.getServerSocketFactory().createServerSocket(port, backlog, ifAddress));
}
/**
* Sets the socket so newly accepted connections will use SSL client mode.
*
* @param socket the SSLServerSocket to initialise
* @return the socket
* @throws ClassCastException if socket is not an instance of SSLServerSocket
*/
public ServerSocket init(ServerSocket socket) {
((SSLServerSocket) socket).setUseClientMode(true);
return socket;
}
}

@ -0,0 +1,112 @@
/*
* 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 org.apache.commons.net.ftp;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import javax.net.SocketFactory;
import javax.net.ssl.SSLContext;
/**
*
* Implementation of org.apache.commons.net.SocketFactory
*
* @since 2.0
*/
public class FTPSSocketFactory extends SocketFactory {
private final SSLContext context;
public FTPSSocketFactory(SSLContext context) {
this.context = context;
}
// Override the default implementation
@Override
public Socket createSocket() throws IOException{
return this.context.getSocketFactory().createSocket();
}
@Override
public Socket createSocket(String address, int port) throws UnknownHostException, IOException {
return this.context.getSocketFactory().createSocket(address, port);
}
@Override
public Socket createSocket(InetAddress address, int port) throws IOException {
return this.context.getSocketFactory().createSocket(address, port);
}
@Override
public Socket createSocket(String address, int port, InetAddress localAddress, int localPort)
throws UnknownHostException, IOException {
return this.context.getSocketFactory().createSocket(address, port, localAddress, localPort);
}
@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
return this.context.getSocketFactory().createSocket(address, port, localAddress, localPort);
}
// DEPRECATED METHODS - for API compatibility only - DO NOT USE
/** @param port the port
* @return the socket
* @throws IOException on error
* @deprecated (2.2) use {@link FTPSServerSocketFactory#createServerSocket(int) instead} */
@Deprecated
public java.net.ServerSocket createServerSocket(int port) throws IOException {
return this.init(this.context.getServerSocketFactory().createServerSocket(port));
}
/** @param port the port
* @param backlog the backlog
* @return the socket
* @throws IOException on error
* @deprecated (2.2) use {@link FTPSServerSocketFactory#createServerSocket(int, int) instead} */
@Deprecated
public java.net.ServerSocket createServerSocket(int port, int backlog) throws IOException {
return this.init(this.context.getServerSocketFactory().createServerSocket(port, backlog));
}
/** @param port the port
* @param backlog the backlog
* @param ifAddress the interface
* @return the socket
* @throws IOException on error
* @deprecated (2.2) use {@link FTPSServerSocketFactory#createServerSocket(int, int, InetAddress) instead} */
@Deprecated
public java.net.ServerSocket createServerSocket(int port, int backlog, InetAddress ifAddress) throws IOException {
return this.init(this.context.getServerSocketFactory().createServerSocket(port, backlog, ifAddress));
}
/** @param socket the socket
* @return the socket
* @throws IOException on error
* @deprecated (2.2) use {@link FTPSServerSocketFactory#init(java.net.ServerSocket)} */
@Deprecated
public java.net.ServerSocket init(java.net.ServerSocket socket) throws IOException {
((javax.net.ssl.SSLServerSocket) socket).setUseClientMode(true);
return socket;
}
}

@ -0,0 +1,60 @@
/*
* 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 org.apache.commons.net.ftp;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.X509TrustManager;
/**
* Do not use.
* @since 2.0
* @deprecated 3.0 use
* {@link org.apache.commons.net.util.TrustManagerUtils#getValidateServerCertificateTrustManager()
* TrustManagerUtils#getValidateServerCertificateTrustManager()} instead
*/
@Deprecated
public class FTPSTrustManager implements X509TrustManager
{
private static final X509Certificate[] EMPTY_X509CERTIFICATE_ARRAY = new X509Certificate[]{};
/**
* No-op
*/
@Override
public void checkClientTrusted(X509Certificate[] certificates, String authType)
{
return;
}
@Override
public void checkServerTrusted(X509Certificate[] certificates, String authType) throws CertificateException
{
for (X509Certificate certificate : certificates)
{
certificate.checkValidity();
}
}
@Override
public X509Certificate[] getAcceptedIssuers()
{
return EMPTY_X509CERTIFICATE_ARRAY;
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save