3.2.12
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@ -12,4 +12,5 @@
|
||||
.gradle
|
||||
/.idea
|
||||
.idea
|
||||
/cache
|
||||
/cache
|
||||
*.log
|
@ -23,5 +23,7 @@ dependencies {
|
||||
testCompile 'junit:junit:4.12'
|
||||
compile 'com.android.support:appcompat-v7:23.1.1'
|
||||
compile project(':AriaAnnotations')
|
||||
// compile project(':AriaFtpPlug')
|
||||
|
||||
}
|
||||
apply from: 'bintray-release.gradle'
|
||||
|
@ -43,7 +43,7 @@ import com.arialyy.aria.core.upload.UploadTask;
|
||||
* <code>
|
||||
* //下载
|
||||
* Aria.download(this)
|
||||
* .load(DOWNLOAD_URL) //下载地址,必填
|
||||
* .load(URL) //下载地址,必填
|
||||
* //文件保存路径,必填
|
||||
* .setDownloadPath(Environment.getExternalStorageDirectory().getPath() + "/test.apk")
|
||||
* .start();
|
||||
|
@ -28,6 +28,7 @@ import android.support.v4.app.Fragment;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.widget.PopupWindow;
|
||||
import com.arialyy.aria.core.common.QueueMod;
|
||||
import com.arialyy.aria.core.download.DownloadReceiver;
|
||||
import com.arialyy.aria.core.command.ICmd;
|
||||
import com.arialyy.aria.core.inf.IReceiver;
|
||||
@ -37,10 +38,10 @@ import com.arialyy.aria.util.CommonUtil;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.parsers.SAXParser;
|
||||
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 UPLOAD_TEMP_DIR = "/Aria/temp/upload/";
|
||||
@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;
|
||||
private List<ICmd> mCommands = new ArrayList<>();
|
||||
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) {
|
||||
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) {
|
||||
mDConfig.setQueueMod(mod.tag);
|
||||
|
@ -228,7 +228,7 @@ class ConfigHelper extends DefaultHandler {
|
||||
}
|
||||
if (num < 1) {
|
||||
Log.e(TAG, "下载线程数不能小于 1");
|
||||
num = 3;
|
||||
num = 1;
|
||||
}
|
||||
if (isDownloadConfig) {
|
||||
mDownloadConfig.threadNum = num;
|
||||
|
@ -17,7 +17,7 @@ package com.arialyy.aria.core.command.group;
|
||||
|
||||
import android.text.TextUtils;
|
||||
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.AbsTaskEntity;
|
||||
import com.arialyy.aria.core.inf.IEntity;
|
||||
|
@ -18,7 +18,7 @@ package com.arialyy.aria.core.command.normal;
|
||||
|
||||
import android.text.TextUtils;
|
||||
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.IEntity;
|
||||
import com.arialyy.aria.core.inf.AbsTaskEntity;
|
||||
|
398
Aria/src/main/java/com/arialyy/aria/core/common/AbsFileer.java
Normal file
398
Aria/src/main/java/com/arialyy/aria/core/common/AbsFileer.java
Normal file
@ -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.
|
||||
*/
|
||||
|
||||
package com.arialyy.aria.core.download.downloader;
|
||||
package com.arialyy.aria.core.common;
|
||||
|
||||
/**
|
||||
* Created by lyy on 2016/10/31.
|
||||
* 抽象的下载接口
|
||||
*/
|
||||
public interface IDownloadUtil {
|
||||
public interface IUtil {
|
||||
|
||||
/**
|
||||
* 获取文件大小
|
||||
@ -37,25 +37,30 @@ public interface IDownloadUtil {
|
||||
*
|
||||
* @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
|
||||
* 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.Method;
|
||||
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.
|
||||
@ -19,7 +19,7 @@ public enum QueueMod {
|
||||
*/
|
||||
NOW("now");
|
||||
|
||||
String tag;
|
||||
public String tag;
|
||||
|
||||
public String getTag() {
|
||||
return tag;
|
@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.arialyy.aria.core;
|
||||
package com.arialyy.aria.core.common;
|
||||
|
||||
/**
|
||||
* Created by lyy on 2017/1/23.
|
@ -13,32 +13,32 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.arialyy.aria.core.download.downloader;
|
||||
package com.arialyy.aria.core.common;
|
||||
|
||||
/**
|
||||
* Created by lyy on 2017/1/18.
|
||||
* 下载状态常量
|
||||
*/
|
||||
final class StateConstance {
|
||||
int CANCEL_NUM = 0;
|
||||
int STOP_NUM = 0;
|
||||
int FAIL_NUM = 0;
|
||||
int CONNECT_TIME_OUT; //连接超时时间
|
||||
int READ_TIME_OUT; //流读取的超时时间
|
||||
int COMPLETE_THREAD_NUM = 0;
|
||||
int THREAD_NUM;
|
||||
long CURRENT_LOCATION = 0;
|
||||
boolean isDownloading = false;
|
||||
boolean isCancel = false;
|
||||
boolean isStop = false;
|
||||
public class StateConstance {
|
||||
public int CANCEL_NUM = 0;
|
||||
public int STOP_NUM = 0;
|
||||
public int FAIL_NUM = 0;
|
||||
public int CONNECT_TIME_OUT; //连接超时时间
|
||||
public int READ_TIME_OUT; //流读取的超时时间
|
||||
public int COMPLETE_THREAD_NUM = 0;
|
||||
public int THREAD_NUM;
|
||||
public long CURRENT_LOCATION = 0;
|
||||
public boolean isRunning = false;
|
||||
public boolean isCancel = false;
|
||||
public boolean isStop = false;
|
||||
|
||||
StateConstance() {
|
||||
public StateConstance() {
|
||||
}
|
||||
|
||||
void cleanState() {
|
||||
public void cleanState() {
|
||||
isCancel = false;
|
||||
isStop = false;
|
||||
isDownloading = true;
|
||||
isRunning = true;
|
||||
CURRENT_LOCATION = 0;
|
||||
CANCEL_NUM = 0;
|
||||
STOP_NUM = 0;
|
||||
@ -48,28 +48,28 @@ final class StateConstance {
|
||||
/**
|
||||
* 所有子线程是否都已经停止下载
|
||||
*/
|
||||
boolean isStop() {
|
||||
public boolean isStop() {
|
||||
return STOP_NUM == THREAD_NUM;
|
||||
}
|
||||
|
||||
/**
|
||||
* 所有子线程是否都已经下载失败
|
||||
*/
|
||||
boolean isFail() {
|
||||
public boolean isFail() {
|
||||
return FAIL_NUM == THREAD_NUM;
|
||||
}
|
||||
|
||||
/**
|
||||
* 所有子线程是否都已经完成下载
|
||||
*/
|
||||
boolean isComplete() {
|
||||
public boolean isComplete() {
|
||||
return COMPLETE_THREAD_NUM == THREAD_NUM;
|
||||
}
|
||||
|
||||
/**
|
||||
* 所有子线程是否都已经取消下载
|
||||
*/
|
||||
boolean isCancel() {
|
||||
public boolean isCancel() {
|
||||
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;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
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.IDownloadListener;
|
||||
import com.arialyy.aria.core.inf.IEntity;
|
||||
import com.arialyy.aria.core.inf.IEventListener;
|
||||
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>>
|
||||
implements IEventListener {
|
||||
class BaseDListener<ENTITY extends AbsEntity, TASK extends AbsTask<ENTITY>>
|
||||
implements IDownloadListener {
|
||||
private WeakReference<Handler> outHandler;
|
||||
private long mLastLen = 0; //上一次发送长度
|
||||
private boolean isFirst = true;
|
||||
@ -38,7 +40,7 @@ class BaseListener<ENTITY extends AbsEntity, TASK extends AbsTask<ENTITY>>
|
||||
private boolean isConvertSpeed = false;
|
||||
boolean isWait = false;
|
||||
|
||||
BaseListener(TASK task, Handler outHandler) {
|
||||
BaseDListener(TASK task, Handler outHandler) {
|
||||
this.outHandler = new WeakReference<>(outHandler);
|
||||
this.mTask = new WeakReference<>(task).get();
|
||||
this.mEntity = this.mTask.getEntity();
|
||||
@ -59,6 +61,10 @@ class BaseListener<ENTITY extends AbsEntity, TASK extends AbsTask<ENTITY>>
|
||||
sendInState2Target(ISchedulers.POST_PRE);
|
||||
}
|
||||
|
||||
@Override public void supportBreakpoint(boolean support) {
|
||||
|
||||
}
|
||||
|
||||
@Override public void onStart(long startLocation) {
|
||||
saveData(IEntity.STATE_RUNNING, startLocation);
|
||||
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 {
|
||||
private String downloadUrl = ""; //下载路径
|
||||
@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 = "";
|
||||
|
||||
@Override public String getKey() {
|
||||
return downloadUrl;
|
||||
return getUrl();
|
||||
}
|
||||
|
||||
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() {
|
||||
return md5Code;
|
||||
}
|
||||
@ -120,13 +89,11 @@ public class DownloadEntity extends AbsNormalEntity implements Parcelable {
|
||||
this.groupName = groupName;
|
||||
}
|
||||
|
||||
public String getDownloadUrl() {
|
||||
return downloadUrl;
|
||||
}
|
||||
|
||||
public DownloadEntity setDownloadUrl(String downloadUrl) {
|
||||
this.downloadUrl = downloadUrl;
|
||||
return this;
|
||||
/**
|
||||
* {@link #getUrl()}
|
||||
*/
|
||||
@Deprecated public String getDownloadUrl() {
|
||||
return getUrl();
|
||||
}
|
||||
|
||||
public String getDownloadPath() {
|
||||
@ -142,44 +109,42 @@ public class DownloadEntity extends AbsNormalEntity implements Parcelable {
|
||||
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() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override public void writeToParcel(Parcel dest, int flags) {
|
||||
super.writeToParcel(dest, flags);
|
||||
dest.writeString(this.downloadUrl);
|
||||
dest.writeString(this.downloadPath);
|
||||
dest.writeByte(this.isRedirect ? (byte) 1 : (byte) 0);
|
||||
dest.writeString(this.redirectUrl);
|
||||
dest.writeString(this.groupName);
|
||||
dest.writeString(this.md5Code);
|
||||
dest.writeString(this.disposition);
|
||||
dest.writeString(this.serverFileName);
|
||||
}
|
||||
|
||||
@Override public String toString() {
|
||||
return "DownloadEntity{"
|
||||
+ "downloadPath='"
|
||||
+ downloadPath
|
||||
+ '\''
|
||||
+ ", groupName='"
|
||||
+ groupName
|
||||
+ '\''
|
||||
+ ", md5Code='"
|
||||
+ md5Code
|
||||
+ '\''
|
||||
+ ", disposition='"
|
||||
+ disposition
|
||||
+ '\''
|
||||
+ ", serverFileName='"
|
||||
+ serverFileName
|
||||
+ '\''
|
||||
+ '}';
|
||||
}
|
||||
|
||||
protected DownloadEntity(Parcel in) {
|
||||
super(in);
|
||||
this.downloadUrl = in.readString();
|
||||
this.downloadPath = in.readString();
|
||||
this.isRedirect = in.readByte() != 0;
|
||||
this.redirectUrl = in.readString();
|
||||
this.groupName = in.readString();
|
||||
this.md5Code = in.readString();
|
||||
this.disposition = in.readString();
|
||||
|
@ -47,7 +47,7 @@ public class DownloadGroupEntity extends AbsGroupEntity {
|
||||
return subtask;
|
||||
}
|
||||
|
||||
void setSubTasks(List<DownloadEntity> subTasks) {
|
||||
public void setSubTasks(List<DownloadEntity> subTasks) {
|
||||
this.subtask = subTasks;
|
||||
}
|
||||
|
||||
@ -63,7 +63,7 @@ public class DownloadGroupEntity extends AbsGroupEntity {
|
||||
return urls;
|
||||
}
|
||||
|
||||
void setUrls(List<String> urls) {
|
||||
public void setUrls(List<String> urls) {
|
||||
this.urls = urls;
|
||||
}
|
||||
|
||||
|
@ -16,14 +16,13 @@
|
||||
package com.arialyy.aria.core.download;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
import com.arialyy.aria.core.download.downloader.IDownloadGroupListener;
|
||||
|
||||
/**
|
||||
* Created by Aria.Lao on 2017/7/20.
|
||||
* 任务组下载事件
|
||||
*/
|
||||
class DownloadGroupListener extends BaseListener<DownloadGroupEntity, DownloadGroupTask>
|
||||
class DownloadGroupListener extends BaseDListener<DownloadGroupEntity, DownloadGroupTask>
|
||||
implements IDownloadGroupListener {
|
||||
private final String TAG = "DownloadGroupListener";
|
||||
|
||||
|
@ -15,32 +15,18 @@
|
||||
*/
|
||||
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.util.CheckUtil;
|
||||
import com.arialyy.aria.util.CommonUtil;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Created by AriaL on 2017/6/29.
|
||||
* 下载任务组
|
||||
*/
|
||||
public class DownloadGroupTarget
|
||||
extends AbsDownloadTarget<DownloadGroupTarget, DownloadGroupEntity, DownloadGroupTaskEntity> {
|
||||
private List<String> mUrls = new ArrayList<>();
|
||||
extends BaseGroupTarget<DownloadGroupTarget, DownloadGroupTaskEntity> {
|
||||
private final String TAG = "DownloadGroupTarget";
|
||||
/**
|
||||
* 子任务文件名
|
||||
*/
|
||||
private List<String> mSubTaskFileName = new ArrayList<>();
|
||||
private String mGroupName;
|
||||
/**
|
||||
* 是否已经设置了文件路径
|
||||
*/
|
||||
private boolean isSetDirPathed = false;
|
||||
|
||||
DownloadGroupTarget(DownloadGroupEntity groupEntity, String targetName) {
|
||||
this.mTargetName = targetName;
|
||||
@ -71,31 +57,6 @@ public class DownloadGroupTarget
|
||||
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)}进行下载操作,那么你需要设置任务组的下载地址
|
||||
*/
|
||||
@ -107,136 +68,4 @@ public class DownloadGroupTarget
|
||||
mEntity.update();
|
||||
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 com.arialyy.aria.core.AriaManager;
|
||||
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.AbsTaskEntity;
|
||||
import com.arialyy.aria.core.scheduler.ISchedulers;
|
||||
import com.arialyy.aria.util.CheckUtil;
|
||||
|
||||
@ -30,7 +32,7 @@ import com.arialyy.aria.util.CheckUtil;
|
||||
public class DownloadGroupTask extends AbsGroupTask<DownloadGroupTaskEntity, DownloadGroupEntity> {
|
||||
private final String TAG = "DownloadGroupTask";
|
||||
private DownloadGroupListener mListener;
|
||||
private IDownloadUtil mUtil;
|
||||
private IUtil mUtil;
|
||||
|
||||
private DownloadGroupTask(DownloadGroupTaskEntity taskEntity, Handler outHandler) {
|
||||
mTaskEntity = taskEntity;
|
||||
@ -38,33 +40,40 @@ public class DownloadGroupTask extends AbsGroupTask<DownloadGroupTaskEntity, Dow
|
||||
mOutHandler = outHandler;
|
||||
mContext = AriaManager.APP;
|
||||
mListener = new DownloadGroupListener(this, mOutHandler);
|
||||
mUtil = new DownloadGroupUtil(mListener, mTaskEntity);
|
||||
switch (taskEntity.requestType) {
|
||||
case AbsTaskEntity.HTTP:
|
||||
mUtil = new DownloadGroupUtil(mListener, mTaskEntity);
|
||||
break;
|
||||
case AbsTaskEntity.FTP_DIR:
|
||||
mUtil = new FtpDirDownloadUtil(mListener, mTaskEntity);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override public boolean isRunning() {
|
||||
return mUtil.isDownloading();
|
||||
return mUtil.isRunning();
|
||||
}
|
||||
|
||||
@Override public void start() {
|
||||
mUtil.startDownload();
|
||||
mUtil.start();
|
||||
}
|
||||
|
||||
@Override public void stop() {
|
||||
if (!mUtil.isDownloading()) {
|
||||
if (!mUtil.isRunning()) {
|
||||
if (mOutHandler != null) {
|
||||
mOutHandler.obtainMessage(ISchedulers.STOP, this).sendToTarget();
|
||||
}
|
||||
}
|
||||
mUtil.stopDownload();
|
||||
mUtil.stop();
|
||||
}
|
||||
|
||||
@Override public void cancel() {
|
||||
if (!mUtil.isDownloading()) {
|
||||
if (!mUtil.isRunning()) {
|
||||
if (mOutHandler != null) {
|
||||
mOutHandler.obtainMessage(ISchedulers.CANCEL, this).sendToTarget();
|
||||
}
|
||||
}
|
||||
mUtil.cancelDownload();
|
||||
mUtil.cancel();
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
@ -16,13 +16,13 @@
|
||||
package com.arialyy.aria.core.download;
|
||||
|
||||
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.
|
||||
* 普通任务下载的事件监听器
|
||||
*/
|
||||
class DownloadListener extends BaseListener<DownloadEntity, DownloadTask>
|
||||
class DownloadListener extends BaseDListener<DownloadEntity, DownloadTask>
|
||||
implements IDownloadListener {
|
||||
DownloadListener(DownloadTask task, Handler 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.DownloadSchedulers;
|
||||
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.util.CheckUtil;
|
||||
import com.arialyy.aria.util.CommonUtil;
|
||||
@ -58,11 +58,11 @@ public class DownloadReceiver extends AbsReceiver {
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载下载地址
|
||||
* 加载Http、https单任务下载地址
|
||||
*/
|
||||
public DownloadTarget load(@NonNull String downloadUrl) {
|
||||
CheckUtil.checkDownloadUrl(downloadUrl);
|
||||
return new DownloadTarget(downloadUrl, targetName);
|
||||
public DownloadTarget load(@NonNull String url) {
|
||||
CheckUtil.checkDownloadUrl(url);
|
||||
return new DownloadTarget(url, targetName);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -73,6 +73,14 @@ public class DownloadReceiver extends AbsReceiver {
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载ftp文件夹下载地址
|
||||
*/
|
||||
public FtpDirDownloadTarget loadFtpDir(@NonNull String dirUrl) {
|
||||
CheckUtil.checkDownloadUrl(dirUrl);
|
||||
return new FtpDirDownloadTarget(dirUrl, targetName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将当前类注册到Aria
|
||||
*/
|
||||
@ -147,7 +163,7 @@ public class DownloadReceiver extends AbsReceiver {
|
||||
*/
|
||||
public DownloadEntity getDownloadEntity(String downloadUrl) {
|
||||
CheckUtil.checkDownloadUrl(downloadUrl);
|
||||
return DbEntity.findFirst(DownloadEntity.class, "downloadUrl=? and isGroupChild='false'",
|
||||
return DbEntity.findFirst(DownloadEntity.class, "url=? and isGroupChild='false'",
|
||||
downloadUrl);
|
||||
}
|
||||
|
||||
@ -169,11 +185,20 @@ public class DownloadReceiver extends AbsReceiver {
|
||||
return DbEntity.findFirst(DownloadGroupTaskEntity.class, "key=?", hashCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过任务组key,获取任务组实体
|
||||
* 如果是http,key为所有子任务下载地址拼接后取md5
|
||||
* 如果是ftp,key为ftp服务器的文件夹路径
|
||||
*/
|
||||
public DownloadGroupTaskEntity getDownloadGroupTask(String key) {
|
||||
return DbEntity.findFirst(DownloadGroupTaskEntity.class, "key=?", key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载任务是否存在
|
||||
*/
|
||||
@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
|
||||
extends AbsDownloadTarget<DownloadTarget, DownloadEntity, DownloadTaskEntity> {
|
||||
|
||||
protected String url;
|
||||
DownloadTarget(DownloadEntity entity, String targetName) {
|
||||
this(entity.getDownloadUrl(), targetName);
|
||||
this(entity.getUrl(), targetName);
|
||||
}
|
||||
|
||||
DownloadTarget(String url, String targetName) {
|
||||
this.url = url;
|
||||
mTargetName = targetName;
|
||||
DownloadEntity entity = getEntity(url);
|
||||
mTaskEntity = DbEntity.findFirst(DownloadTaskEntity.class, "key=? and isGroupTask='false'",
|
||||
@ -59,11 +60,11 @@ public class DownloadTarget
|
||||
*/
|
||||
private DownloadEntity getEntity(String downloadUrl) {
|
||||
DownloadEntity entity =
|
||||
DownloadEntity.findFirst(DownloadEntity.class, "downloadUrl=? and isGroupChild='false'",
|
||||
DownloadEntity.findFirst(DownloadEntity.class, "url=? and isGroupChild='false'",
|
||||
downloadUrl);
|
||||
if (entity == null) {
|
||||
entity = new DownloadEntity();
|
||||
entity.setDownloadUrl(downloadUrl);
|
||||
entity.setUrl(downloadUrl);
|
||||
entity.setGroupChild(false);
|
||||
entity.save();
|
||||
}
|
||||
@ -92,7 +93,7 @@ public class DownloadTarget
|
||||
* 下载任务是否存在
|
||||
*/
|
||||
@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.util.Log;
|
||||
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.inf.AbsNormalTask;
|
||||
import com.arialyy.aria.core.inf.IEntity;
|
||||
@ -33,7 +34,7 @@ public class DownloadTask extends AbsNormalTask<DownloadEntity> {
|
||||
public static final String TAG = "DownloadTask";
|
||||
|
||||
private DownloadListener mListener;
|
||||
private SimpleDownloadUtil mUtil;
|
||||
private IUtil mUtil;
|
||||
|
||||
private DownloadTask(DownloadTaskEntity taskEntity, Handler outHandler) {
|
||||
mEntity = taskEntity.getEntity();
|
||||
@ -62,11 +63,11 @@ public class DownloadTask extends AbsNormalTask<DownloadEntity> {
|
||||
* @see DownloadTask#getKey()
|
||||
*/
|
||||
@Deprecated public String getDownloadUrl() {
|
||||
return mEntity.getDownloadUrl();
|
||||
return mEntity.getUrl();
|
||||
}
|
||||
|
||||
@Override public String getKey() {
|
||||
return getDownloadUrl();
|
||||
return mEntity.getUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -75,7 +76,7 @@ public class DownloadTask extends AbsNormalTask<DownloadEntity> {
|
||||
* @see DownloadTask#isRunning()
|
||||
*/
|
||||
@Deprecated public boolean isDownloading() {
|
||||
return mUtil.isDownloading();
|
||||
return mUtil.isRunning();
|
||||
}
|
||||
|
||||
@Override public boolean isRunning() {
|
||||
@ -107,10 +108,10 @@ public class DownloadTask extends AbsNormalTask<DownloadEntity> {
|
||||
*/
|
||||
@Override public void start() {
|
||||
mListener.isWait = false;
|
||||
if (mUtil.isDownloading()) {
|
||||
if (mUtil.isRunning()) {
|
||||
Log.d(TAG, "任务正在下载");
|
||||
} else {
|
||||
mUtil.startDownload();
|
||||
mUtil.start();
|
||||
}
|
||||
}
|
||||
|
||||
@ -123,8 +124,8 @@ public class DownloadTask extends AbsNormalTask<DownloadEntity> {
|
||||
|
||||
private void stop(boolean isWait) {
|
||||
mListener.isWait = isWait;
|
||||
if (mUtil.isDownloading()) {
|
||||
mUtil.stopDownload();
|
||||
if (mUtil.isRunning()) {
|
||||
mUtil.stop();
|
||||
} else {
|
||||
mEntity.setState(isWait ? IEntity.STATE_WAIT : IEntity.STATE_STOP);
|
||||
mEntity.update();
|
||||
@ -138,14 +139,12 @@ public class DownloadTask extends AbsNormalTask<DownloadEntity> {
|
||||
* 取消下载
|
||||
*/
|
||||
@Override public void cancel() {
|
||||
if (!mEntity.isComplete()) {
|
||||
if (!mUtil.isDownloading()) {
|
||||
if (mOutHandler != null) {
|
||||
mOutHandler.obtainMessage(ISchedulers.CANCEL, this).sendToTarget();
|
||||
}
|
||||
if (!mUtil.isRunning()) {
|
||||
if (mOutHandler != null) {
|
||||
mOutHandler.obtainMessage(ISchedulers.CANCEL, this).sendToTarget();
|
||||
}
|
||||
mUtil.cancelDownload();
|
||||
}
|
||||
mUtil.cancel();
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
@ -16,6 +16,7 @@
|
||||
package com.arialyy.aria.core.download;
|
||||
|
||||
import com.arialyy.aria.core.inf.AbsTaskEntity;
|
||||
import com.arialyy.aria.orm.Ignore;
|
||||
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.SSLContext;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import org.apache.commons.net.ftp.FTPClient;
|
||||
|
||||
/**
|
||||
* Created by lyy on 2017/1/18.
|
||||
* 链接帮助类
|
||||
*/
|
||||
class ConnectionHelp {
|
||||
|
||||
/**
|
||||
* 处理链接
|
||||
*
|
||||
|
@ -16,19 +16,11 @@
|
||||
package com.arialyy.aria.core.download.downloader;
|
||||
|
||||
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.DownloadTaskEntity;
|
||||
import com.arialyy.aria.core.inf.IEntity;
|
||||
import com.arialyy.aria.orm.DbEntity;
|
||||
import com.arialyy.aria.util.CommonUtil;
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
@ -36,153 +28,37 @@ import java.util.concurrent.Executors;
|
||||
* 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 long mTotalSize = 0;
|
||||
private long mCurrentLocation = 0;
|
||||
private ExecutorService mInfoPool;
|
||||
private ExecutorService mExePool;
|
||||
private IDownloadGroupListener mListener;
|
||||
private DownloadGroupTaskEntity mTaskEntity;
|
||||
private boolean isRunning = true;
|
||||
private Timer mTimer;
|
||||
/**
|
||||
* 初始化完成的任务书数
|
||||
*/
|
||||
private int mInitNum = 0;
|
||||
/**
|
||||
* 初始化失败的任务数
|
||||
*/
|
||||
private int mInitFailNum = 0;
|
||||
/**
|
||||
* 保存所有没有下载完成的任务,key为下载地址
|
||||
*/
|
||||
private Map<String, DownloadTaskEntity> mExeMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* 下载失败的映射表,key为下载地址
|
||||
*/
|
||||
private Map<String, DownloadTaskEntity> mFailMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* 下载器映射表,key为下载地址
|
||||
*/
|
||||
private Map<String, Downloader> mDownloaderMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* 文件信息回调组
|
||||
*/
|
||||
private SparseArray<FileInfoThread.OnFileInfoCallback> mFileInfoCallbacks = new SparseArray<>();
|
||||
/**
|
||||
* 该任务组对应的所有任务
|
||||
*/
|
||||
private Map<String, DownloadTaskEntity> mTasksMap = new HashMap<>();
|
||||
//已经完成的任务数
|
||||
private int mCompleteNum = 0;
|
||||
//失败的任务数
|
||||
private int mFailNum = 0;
|
||||
//实际的下载任务数
|
||||
private int mActualTaskNum = 0;
|
||||
private SparseArray<OnFileInfoCallback> mFileInfoCallbacks = new SparseArray<>();
|
||||
|
||||
public DownloadGroupUtil(IDownloadGroupListener listener, DownloadGroupTaskEntity taskEntity) {
|
||||
mListener = listener;
|
||||
mTaskEntity = taskEntity;
|
||||
super(listener, taskEntity);
|
||||
mInfoPool = Executors.newCachedThreadPool();
|
||||
mExePool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
|
||||
mActualTaskNum = mTaskEntity.entity.getSubTask().size();
|
||||
List<DownloadTaskEntity> tasks =
|
||||
DbEntity.findDatas(DownloadTaskEntity.class, "groupName=?", mTaskEntity.key);
|
||||
if (tasks != null && !tasks.isEmpty()) {
|
||||
for (DownloadTaskEntity te : tasks) {
|
||||
mTasksMap.put(te.getEntity().getDownloadUrl(), te);
|
||||
}
|
||||
}
|
||||
for (DownloadEntity entity : mTaskEntity.entity.getSubTask()) {
|
||||
File file = new File(entity.getDownloadPath());
|
||||
if (entity.getState() == IEntity.STATE_COMPLETE && file.exists()) {
|
||||
mCompleteNum++;
|
||||
mInitNum++;
|
||||
mCurrentLocation += entity.getFileSize();
|
||||
} else {
|
||||
mExeMap.put(entity.getDownloadUrl(), createChildDownloadTask(entity));
|
||||
mCurrentLocation += entity.getCurrentProgress();
|
||||
}
|
||||
mTotalSize += entity.getFileSize();
|
||||
}
|
||||
}
|
||||
|
||||
@Override public long getFileSize() {
|
||||
return mTotalSize;
|
||||
}
|
||||
|
||||
@Override public long getCurrentLocation() {
|
||||
return mCurrentLocation;
|
||||
}
|
||||
|
||||
@Override public boolean isDownloading() {
|
||||
return isRunning;
|
||||
}
|
||||
|
||||
@Override public void cancelDownload() {
|
||||
closeTimer(false);
|
||||
mListener.onCancel();
|
||||
@Override public void onCancel() {
|
||||
super.onCancel();
|
||||
if (!mInfoPool.isShutdown()) {
|
||||
mInfoPool.shutdown();
|
||||
}
|
||||
if (!mExePool.isShutdown()) {
|
||||
mExePool.shutdown();
|
||||
}
|
||||
|
||||
Set<String> keys = mDownloaderMap.keySet();
|
||||
for (String key : keys) {
|
||||
Downloader dt = mDownloaderMap.get(key);
|
||||
if (dt != null) {
|
||||
dt.cancelDownload();
|
||||
}
|
||||
}
|
||||
delDownloadInfo();
|
||||
mTaskEntity.deleteData();
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除所有子任务的下载信息
|
||||
*/
|
||||
private void delDownloadInfo() {
|
||||
List<DownloadTaskEntity> tasks =
|
||||
DbEntity.findDatas(DownloadTaskEntity.class, "groupName=?", mTaskEntity.key);
|
||||
if (tasks == null || tasks.isEmpty()) return;
|
||||
for (DownloadTaskEntity taskEntity : tasks) {
|
||||
CommonUtil.delDownloadTaskConfig(taskEntity.removeFile, taskEntity);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void stopDownload() {
|
||||
closeTimer(false);
|
||||
mListener.onStop(mCurrentLocation);
|
||||
@Override protected void onStop() {
|
||||
super.onStop();
|
||||
if (!mInfoPool.isShutdown()) {
|
||||
mInfoPool.shutdown();
|
||||
}
|
||||
if (!mExePool.isShutdown()) {
|
||||
mExePool.shutdown();
|
||||
}
|
||||
|
||||
Set<String> keys = mDownloaderMap.keySet();
|
||||
for (String key : keys) {
|
||||
Downloader dt = mDownloaderMap.get(key);
|
||||
if (dt != null) {
|
||||
dt.stopDownload();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void startDownload() {
|
||||
isRunning = true;
|
||||
mFailNum = 0;
|
||||
@Override protected void onStart() {
|
||||
super.onStart();
|
||||
Set<String> keys = mExeMap.keySet();
|
||||
mListener.onPre();
|
||||
int i = 0;
|
||||
for (String key : keys) {
|
||||
DownloadTaskEntity taskEntity = mExeMap.get(key);
|
||||
@ -199,19 +75,14 @@ public class DownloadGroupUtil implements IDownloadUtil {
|
||||
if (i == mExeMap.size()) startRunningFlow();
|
||||
}
|
||||
|
||||
@Override public void resumeDownload() {
|
||||
startDownload();
|
||||
mListener.onResume(mCurrentLocation);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建文件信息获取线程
|
||||
*/
|
||||
private FileInfoThread createFileInfoThread(DownloadTaskEntity taskEntity) {
|
||||
FileInfoThread.OnFileInfoCallback callback = mFileInfoCallbacks.get(taskEntity.hashCode());
|
||||
private HttpFileInfoThread createFileInfoThread(DownloadTaskEntity taskEntity) {
|
||||
OnFileInfoCallback callback = mFileInfoCallbacks.get(taskEntity.hashCode());
|
||||
|
||||
if (callback == null) {
|
||||
callback = new FileInfoThread.OnFileInfoCallback() {
|
||||
callback = new OnFileInfoCallback() {
|
||||
int failNum = 0;
|
||||
|
||||
@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;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
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.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.util.BufferedRandomAccessFile;
|
||||
import com.arialyy.aria.util.CommonUtil;
|
||||
import java.io.File;
|
||||
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.
|
||||
* 文件下载器
|
||||
*/
|
||||
class Downloader implements Runnable, IDownloadUtil {
|
||||
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;
|
||||
class Downloader extends AbsFileer<DownloadEntity, DownloadTaskEntity> {
|
||||
|
||||
Downloader(IDownloadListener listener, DownloadTaskEntity taskEntity) {
|
||||
mListener = listener;
|
||||
mTaskEntity = taskEntity;
|
||||
mEntity = mTaskEntity.getEntity();
|
||||
mContext = AriaManager.APP;
|
||||
mConstance = new StateConstance();
|
||||
super(listener, taskEntity);
|
||||
}
|
||||
|
||||
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() {
|
||||
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() {
|
||||
@Override protected void checkTask() {
|
||||
if (!mTaskEntity.isSupportBP) {
|
||||
isNewTask = true;
|
||||
return;
|
||||
@ -215,7 +54,7 @@ class Downloader implements Runnable, IDownloadUtil {
|
||||
CommonUtil.createFile(mConfigFile.getPath());
|
||||
} else if (!mTempFile.exists()) {
|
||||
isNewTask = true;
|
||||
} else if (DbEntity.findFirst(DownloadEntity.class, "downloadUrl=?", mEntity.getDownloadUrl())
|
||||
} else if (DbEntity.findFirst(DownloadEntity.class, "url=?", mEntity.getDownloadUrl())
|
||||
== null) {
|
||||
isNewTask = true;
|
||||
} else {
|
||||
@ -223,155 +62,16 @@ class Downloader implements Runnable, IDownloadUtil {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查记录文件,如果是新任务返回{@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) {
|
||||
@Override protected void handleNewTask() {
|
||||
CommonUtil.createFile(mTempFile.getPath());
|
||||
BufferedRandomAccessFile file = null;
|
||||
try {
|
||||
file = new BufferedRandomAccessFile(new File(mTempFile.getPath()), "rwd", 8192);
|
||||
//设置文件长度
|
||||
file.setLength(fileLength);
|
||||
file.setLength(mEntity.getFileSize());
|
||||
} catch (IOException e) {
|
||||
failDownload("下载失败【downloadUrl:"
|
||||
+ mEntity.getDownloadUrl()
|
||||
+ mEntity.getUrl()
|
||||
+ "】\n【filePath:"
|
||||
+ mEntity.getDownloadPath()
|
||||
+ "】\n"
|
||||
@ -387,33 +87,14 @@ class Downloader implements Runnable, IDownloadUtil {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理不支持断点的下载
|
||||
*/
|
||||
private void handleNoSupportBreakpointDownload() {
|
||||
ChildThreadConfigEntity entity = new ChildThreadConfigEntity();
|
||||
long len = mEntity.getFileSize();
|
||||
entity.FILE_SIZE = len;
|
||||
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);
|
||||
}
|
||||
|
||||
private void failDownload(String errorMsg) {
|
||||
closeTimer();
|
||||
Log.e(TAG, errorMsg);
|
||||
mConstance.isDownloading = false;
|
||||
mListener.onFail();
|
||||
@Override protected AbsThreadTask selectThreadTask(SubThreadConfig<DownloadTaskEntity> config) {
|
||||
switch (mTaskEntity.requestType) {
|
||||
case AbsTaskEntity.FTP:
|
||||
case AbsTaskEntity.FTP_DIR:
|
||||
return new FtpThreadTask(mConstance, (IDownloadListener) mListener, config);
|
||||
case AbsTaskEntity.HTTP:
|
||||
return new HttpThreadTask(mConstance, (IDownloadListener) mListener, config);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -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.download.DownloadEntity;
|
||||
import com.arialyy.aria.core.download.DownloadTaskEntity;
|
||||
import com.arialyy.aria.core.download.downloader.ConnectionHelp;
|
||||
import com.arialyy.aria.util.CommonUtil;
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
@ -29,30 +30,14 @@ import java.net.URLDecoder;
|
||||
/**
|
||||
* 下载文件信息获取
|
||||
*/
|
||||
class FileInfoThread implements Runnable {
|
||||
private final String TAG = "FileInfoThread";
|
||||
class HttpFileInfoThread implements Runnable {
|
||||
private final String TAG = "HttpFileInfoThread";
|
||||
private DownloadEntity mEntity;
|
||||
private DownloadTaskEntity mTaskEntity;
|
||||
private int mConnectTimeOut;
|
||||
private OnFileInfoCallback onFileInfoListener;
|
||||
|
||||
interface OnFileInfoCallback {
|
||||
/**
|
||||
* 处理完成
|
||||
*
|
||||
* @param code 状态码
|
||||
*/
|
||||
void onComplete(String url, int code);
|
||||
|
||||
/**
|
||||
* 请求失败
|
||||
*
|
||||
* @param errorMsg 错误信息
|
||||
*/
|
||||
void onFail(String url, String errorMsg);
|
||||
}
|
||||
|
||||
FileInfoThread(DownloadTaskEntity taskEntity, OnFileInfoCallback callback) {
|
||||
HttpFileInfoThread(DownloadTaskEntity taskEntity, OnFileInfoCallback callback) {
|
||||
this.mTaskEntity = taskEntity;
|
||||
mEntity = taskEntity.getEntity();
|
||||
mConnectTimeOut =
|
||||
@ -63,7 +48,7 @@ class FileInfoThread implements Runnable {
|
||||
@Override public void run() {
|
||||
HttpURLConnection conn = null;
|
||||
try {
|
||||
URL url = new URL(mEntity.getDownloadUrl());
|
||||
URL url = new URL(mEntity.getUrl());
|
||||
conn = ConnectionHelp.handleConnection(url);
|
||||
conn = ConnectionHelp.setConnectParam(mTaskEntity, conn);
|
||||
conn.setRequestProperty("Range", "bytes=" + 0 + "-");
|
||||
@ -72,7 +57,7 @@ class FileInfoThread implements Runnable {
|
||||
handleConnect(conn);
|
||||
} catch (IOException e) {
|
||||
failDownload("下载失败【downloadUrl:"
|
||||
+ mEntity.getDownloadUrl()
|
||||
+ mEntity.getUrl()
|
||||
+ "】\n【filePath:"
|
||||
+ mEntity.getDownloadPath()
|
||||
+ "】\n"
|
||||
@ -87,7 +72,8 @@ class FileInfoThread implements Runnable {
|
||||
private void handleConnect(HttpURLConnection conn) throws IOException {
|
||||
long len = conn.getContentLength();
|
||||
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();
|
||||
boolean isComplete = false;
|
||||
@ -117,7 +103,7 @@ class FileInfoThread implements Runnable {
|
||||
mTaskEntity.isSupportBP = false;
|
||||
isComplete = true;
|
||||
} else if (code == HttpURLConnection.HTTP_NOT_FOUND) {
|
||||
failDownload("任务【" + mEntity.getDownloadUrl() + "】下载失败,错误码:404");
|
||||
failDownload("任务【" + mEntity.getUrl() + "】下载失败,错误码:404");
|
||||
} else if (code == HttpURLConnection.HTTP_MOVED_TEMP
|
||||
|| code == HttpURLConnection.HTTP_MOVED_PERM
|
||||
|| code == HttpURLConnection.HTTP_SEE_OTHER) {
|
||||
@ -126,11 +112,11 @@ class FileInfoThread implements Runnable {
|
||||
mEntity.setRedirectUrl(mTaskEntity.redirectUrl);
|
||||
handle302Turn(conn);
|
||||
} else {
|
||||
failDownload("任务【" + mEntity.getDownloadUrl() + "】下载失败,错误码:" + code);
|
||||
failDownload("任务【" + mEntity.getUrl() + "】下载失败,错误码:" + code);
|
||||
}
|
||||
if (isComplete) {
|
||||
if (onFileInfoListener != null) {
|
||||
onFileInfoListener.onComplete(mEntity.getDownloadUrl(), code);
|
||||
onFileInfoListener.onComplete(mEntity.getUrl(), code);
|
||||
}
|
||||
mEntity.update();
|
||||
mTaskEntity.update();
|
||||
@ -145,7 +131,7 @@ class FileInfoThread implements Runnable {
|
||||
Log.d(TAG, "30x跳转,location【 " + mTaskEntity.redirectUrlKey + "】" + "新url为【" + newUrl + "】");
|
||||
if (TextUtils.isEmpty(newUrl) || newUrl.equalsIgnoreCase("null")) {
|
||||
if (onFileInfoListener != null) {
|
||||
onFileInfoListener.onFail(mEntity.getDownloadUrl(), "获取重定向链接失败");
|
||||
onFileInfoListener.onFail(mEntity.getUrl(), "获取重定向链接失败");
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -168,7 +154,7 @@ class FileInfoThread implements Runnable {
|
||||
*/
|
||||
private boolean checkLen(long len) {
|
||||
if (len < 0) {
|
||||
failDownload("任务【" + mEntity.getDownloadUrl() + "】下载失败,文件长度小于0");
|
||||
failDownload("任务【" + mEntity.getUrl() + "】下载失败,文件长度小于0");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@ -177,7 +163,7 @@ class FileInfoThread implements Runnable {
|
||||
private void failDownload(String errorMsg) {
|
||||
Log.e(TAG, errorMsg);
|
||||
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;
|
||||
|
||||
import com.arialyy.aria.core.download.DownloadEntity;
|
||||
import com.arialyy.aria.core.inf.IDownloadListener;
|
||||
import com.arialyy.aria.core.inf.IEventListener;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import com.arialyy.aria.core.common.IUtil;
|
||||
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.
|
||||
* 简单的下载工具
|
||||
* HTTP单任务下载工具
|
||||
*/
|
||||
public class SimpleDownloadUtil implements IDownloadUtil, Runnable {
|
||||
public class SimpleDownloadUtil implements IUtil, Runnable {
|
||||
private static final String TAG = "SimpleDownloadUtil";
|
||||
private IDownloadListener mListener;
|
||||
private Downloader mDT;
|
||||
private Downloader mDownloader;
|
||||
private DownloadTaskEntity mTaskEntity;
|
||||
|
||||
public SimpleDownloadUtil(DownloadTaskEntity entity, IDownloadListener downloadListener) {
|
||||
mTaskEntity = entity;
|
||||
mListener = downloadListener;
|
||||
mDT = new Downloader(downloadListener, entity);
|
||||
mDownloader = new Downloader(downloadListener, entity);
|
||||
}
|
||||
|
||||
@Override public long getFileSize() {
|
||||
return mDT.getFileSize();
|
||||
return mDownloader.getFileSize();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前下载位置
|
||||
*/
|
||||
@Override public long getCurrentLocation() {
|
||||
return mDT.getCurrentLocation();
|
||||
return mDownloader.getCurrentLocation();
|
||||
}
|
||||
|
||||
@Override public boolean isDownloading() {
|
||||
return mDT.isDownloading();
|
||||
@Override public boolean isRunning() {
|
||||
return mDownloader.isRunning();
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消下载
|
||||
*/
|
||||
@Override public void cancelDownload() {
|
||||
mDT.cancelDownload();
|
||||
@Override public void cancel() {
|
||||
mDownloader.cancel();
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止下载
|
||||
*/
|
||||
@Override public void stopDownload() {
|
||||
mDT.stopDownload();
|
||||
@Override public void stop() {
|
||||
mDownloader.stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* 多线程断点续传下载文件,开始下载
|
||||
*/
|
||||
@Override public void startDownload() {
|
||||
mListener.onPre();
|
||||
@Override public void start() {
|
||||
new Thread(this).start();
|
||||
}
|
||||
|
||||
@Override public void resumeDownload() {
|
||||
startDownload();
|
||||
@Override public void resume() {
|
||||
start();
|
||||
}
|
||||
|
||||
public void setMaxSpeed(double maxSpeed) {
|
||||
mDT.setMaxSpeed(maxSpeed);
|
||||
mDownloader.setMaxSpeed(maxSpeed);
|
||||
}
|
||||
|
||||
private void failDownload(String msg) {
|
||||
Log.e(TAG, msg);
|
||||
mListener.onFail();
|
||||
}
|
||||
|
||||
@Override public void run() {
|
||||
if (TextUtils.isEmpty(mTaskEntity.redirectUrl)) {
|
||||
new Thread(new FileInfoThread(mTaskEntity, new FileInfoThread.OnFileInfoCallback() {
|
||||
@Override public void onComplete(String url, int code) {
|
||||
mDT.startDownload();
|
||||
}
|
||||
|
||||
@Override public void onFail(String url, String errorMsg) {
|
||||
failDownload(errorMsg);
|
||||
}
|
||||
})).start();
|
||||
mListener.onPre();
|
||||
if (mTaskEntity.getEntity().getFileSize() <= 1) {
|
||||
new Thread(createInfoThread()).start();
|
||||
} else {
|
||||
mDT.startDownload();
|
||||
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) {
|
||||
mDownloader.start();
|
||||
}
|
||||
|
||||
@Override public void onFail(String url, String errorMsg) {
|
||||
failDownload(errorMsg);
|
||||
}
|
||||
});
|
||||
case AbsTaskEntity.HTTP:
|
||||
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 {
|
||||
|
||||
/**
|
||||
* 服务器地址
|
||||
*/
|
||||
private String url = "";
|
||||
|
||||
/**
|
||||
* 文件名
|
||||
*/
|
||||
@ -35,6 +40,16 @@ public abstract class AbsNormalEntity extends AbsEntity implements Parcelable {
|
||||
*/
|
||||
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() {
|
||||
return isGroupChild;
|
||||
@ -52,6 +67,22 @@ public abstract class AbsNormalEntity extends AbsEntity implements Parcelable {
|
||||
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() {
|
||||
}
|
||||
|
||||
@ -61,13 +92,19 @@ public abstract class AbsNormalEntity extends AbsEntity implements Parcelable {
|
||||
|
||||
@Override public void writeToParcel(Parcel dest, int flags) {
|
||||
super.writeToParcel(dest, flags);
|
||||
dest.writeString(this.url);
|
||||
dest.writeString(this.fileName);
|
||||
dest.writeByte(this.isGroupChild ? (byte) 1 : (byte) 0);
|
||||
dest.writeByte(this.isRedirect ? (byte) 1 : (byte) 0);
|
||||
dest.writeString(this.redirectUrl);
|
||||
}
|
||||
|
||||
protected AbsNormalEntity(Parcel in) {
|
||||
super(in);
|
||||
this.url = in.readString();
|
||||
this.fileName = in.readString();
|
||||
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.util.Log;
|
||||
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.util.CommonUtil;
|
||||
import java.util.Map;
|
||||
@ -157,7 +157,7 @@ public abstract class AbsTarget<TARGET extends AbsTarget, ENTITY extends AbsEnti
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始下载
|
||||
* 开始任务
|
||||
*/
|
||||
@Override public void start() {
|
||||
AriaManager.getInstance(AriaManager.APP)
|
||||
@ -166,7 +166,7 @@ public abstract class AbsTarget<TARGET extends AbsTarget, ENTITY extends AbsEnti
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止下载
|
||||
* 停止任务
|
||||
*
|
||||
* @see #stop()
|
||||
*/
|
||||
@ -181,7 +181,7 @@ public abstract class AbsTarget<TARGET extends AbsTarget, ENTITY extends AbsEnti
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复下载
|
||||
* 恢复任务
|
||||
*/
|
||||
@Override public void resume() {
|
||||
AriaManager.getInstance(AriaManager.APP)
|
||||
@ -190,7 +190,7 @@ public abstract class AbsTarget<TARGET extends AbsTarget, ENTITY extends AbsEnti
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消下载
|
||||
* 删除任务
|
||||
*/
|
||||
@Override public void cancel() {
|
||||
AriaManager.getInstance(AriaManager.APP)
|
||||
@ -198,6 +198,19 @@ public abstract class AbsTarget<TARGET extends AbsTarget, ENTITY extends AbsEnti
|
||||
.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中的后缀名
|
||||
*
|
||||
|
@ -15,7 +15,7 @@
|
||||
*/
|
||||
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.Ignore;
|
||||
import com.arialyy.aria.orm.Primary;
|
||||
@ -26,17 +26,46 @@ import java.util.Map;
|
||||
* Created by lyy on 2017/2/23.
|
||||
*/
|
||||
public abstract class AbsTaskEntity<ENTITY extends AbsEntity> extends DbEntity {
|
||||
/**
|
||||
* HTTP下载
|
||||
*/
|
||||
public static final int HTTP = 0x11;
|
||||
/**
|
||||
* FTP当文件下载
|
||||
*/
|
||||
public static final int FTP = 0x12;
|
||||
/**
|
||||
* FTP文件夹下载,为避免登录过多,子任务由单线程进行处理
|
||||
*/
|
||||
public static final int FTP_DIR = 0x13;
|
||||
|
||||
/**
|
||||
* Task实体对应的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 请求头
|
||||
*/
|
||||
public Map<String, String> headers = new HashMap<>();
|
||||
|
||||
/**
|
||||
* 字符编码,默认为"utf-8"
|
||||
*/
|
||||
public String charSet = "utf-8";
|
||||
|
||||
/**
|
||||
* 网络请求类型
|
||||
*/
|
||||
|
@ -15,12 +15,66 @@
|
||||
*/
|
||||
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.
|
||||
* 任务组超类
|
||||
*/
|
||||
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> {
|
||||
|
||||
/**
|
||||
* 设置上传路径
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
|
||||
package com.arialyy.aria.core.download.downloader;
|
||||
|
||||
import com.arialyy.aria.core.inf.IEventListener;
|
||||
package com.arialyy.aria.core.inf;
|
||||
|
||||
/**
|
||||
* 下载监听
|
||||
*/
|
||||
public interface IDownloadListener extends IEventListener {
|
||||
|
||||
/**
|
||||
* 预处理完成,准备下载---开始下载之间
|
||||
*/
|
||||
void onPostPre(long fileSize);
|
||||
|
||||
/**
|
||||
* 支持断点回调
|
||||
*
|
@ -26,11 +26,6 @@ public interface IEventListener {
|
||||
*/
|
||||
void onPre();
|
||||
|
||||
/**
|
||||
* 预处理完成,准备下载---开始下载之间
|
||||
*/
|
||||
void onPostPre(long fileSize);
|
||||
|
||||
/**
|
||||
* 开始
|
||||
*/
|
||||
|
@ -16,7 +16,7 @@
|
||||
package com.arialyy.aria.core.inf;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import com.arialyy.aria.core.RequestEnum;
|
||||
import com.arialyy.aria.core.common.RequestEnum;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
|
@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.arialyy.aria.core.upload;
|
||||
package com.arialyy.aria.core.inf;
|
||||
|
||||
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.
|
||||
* 上传监听
|
||||
*/
|
||||
interface IUploadListener extends IEventListener {
|
||||
public interface IUploadListener extends IEventListener {
|
||||
|
||||
}
|
@ -60,7 +60,7 @@ public class DownloadTaskQueue
|
||||
}
|
||||
|
||||
@Override public String getKey(DownloadEntity entity) {
|
||||
return entity.getDownloadUrl();
|
||||
return entity.getUrl();
|
||||
}
|
||||
|
||||
@Override public int getConfigMaxNum() {
|
||||
|
@ -25,45 +25,45 @@ public interface ISchedulerListener<TASK extends ITask> {
|
||||
* 预处理,有时有些地址链接比较慢,这时可以先在这个地方出来一些界面上的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();
|
||||
}
|
||||
|
||||
@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) {
|
||||
return new UploadEntity(source);
|
||||
}
|
||||
|
@ -15,6 +15,8 @@
|
||||
*/
|
||||
package com.arialyy.aria.core.upload;
|
||||
|
||||
import com.arialyy.aria.core.inf.IUploadListener;
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ package com.arialyy.aria.core.upload;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
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.download.DownloadTaskEntity;
|
||||
import com.arialyy.aria.core.inf.AbsReceiver;
|
||||
@ -35,24 +35,33 @@ import java.util.Set;
|
||||
* 上传功能接收器
|
||||
*/
|
||||
public class UploadReceiver extends AbsReceiver<UploadEntity> {
|
||||
private static final String TAG = "DownloadReceiver";
|
||||
private static final String TAG = "UploadReceiver";
|
||||
public ISchedulerListener<UploadTask> listener;
|
||||
|
||||
/**
|
||||
* 加载任务
|
||||
* 加载HTTP单文件上传任务
|
||||
*
|
||||
* @param filePath 文件地址
|
||||
* @param filePath 文件路径
|
||||
*/
|
||||
public UploadTarget load(@NonNull String filePath) {
|
||||
CheckUtil.checkUploadPath(filePath);
|
||||
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) {
|
||||
CheckUtil.checkUploadPath(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.queue.UploadTaskQueue;
|
||||
import com.arialyy.aria.orm.DbEntity;
|
||||
import java.io.File;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Created by lyy on 2017/2/28.
|
||||
* http 当文件上传
|
||||
*/
|
||||
public class UploadTarget extends AbsUploadTarget<UploadTarget, UploadEntity, UploadTaskEntity> {
|
||||
|
||||
@ -38,21 +40,11 @@ public class UploadTarget extends AbsUploadTarget<UploadTarget, UploadEntity, Up
|
||||
mTaskEntity.entity = getUploadEntity(filePath);
|
||||
}
|
||||
mEntity = mTaskEntity.entity;
|
||||
}
|
||||
|
||||
private 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;
|
||||
File file = new File(filePath);
|
||||
mEntity.setFileSize(file.length());
|
||||
mEntity = mTaskEntity.entity;
|
||||
//http暂时不支持断点上传
|
||||
mTaskEntity.isSupportBP = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -63,16 +55,6 @@ public class UploadTarget extends AbsUploadTarget<UploadTarget, UploadEntity, Up
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置上传路径
|
||||
*
|
||||
* @param uploadUrl 上传路径
|
||||
*/
|
||||
public UploadTarget setUploadUrl(@NonNull String uploadUrl) {
|
||||
mTaskEntity.uploadUrl = uploadUrl;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置服务器需要的附件key
|
||||
*
|
||||
@ -92,19 +74,4 @@ public class UploadTarget extends AbsUploadTarget<UploadTarget, UploadEntity, Up
|
||||
mTaskEntity.contentType = contentType;
|
||||
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
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.arialyy.aria.core.upload;
|
||||
package com.arialyy.aria.core.upload.uploader;
|
||||
|
||||
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.File;
|
||||
import java.io.FileInputStream;
|
||||
@ -32,55 +37,34 @@ import java.util.Set;
|
||||
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 {
|
||||
private static final String TAG = "UploadUtil";
|
||||
class HttpThreadTask extends AbsThreadTask<UploadEntity, UploadTaskEntity> {
|
||||
private final String TAG = "HttpThreadTask";
|
||||
|
||||
private final String BOUNDARY = UUID.randomUUID().toString(); // 边界标识 随机生成
|
||||
private final String PREFIX = "--", LINE_END = "\r\n";
|
||||
private UploadEntity mUploadEntity;
|
||||
private UploadTaskEntity mTaskEntity;
|
||||
private IUploadListener mListener;
|
||||
private HttpURLConnection mHttpConn;
|
||||
private long mCurrentLocation = 0;
|
||||
private boolean isCancel = false;
|
||||
private boolean isRunning = false;
|
||||
private OutputStream mOutputStream;
|
||||
|
||||
UploadUtil(UploadTaskEntity taskEntity, IUploadListener listener) {
|
||||
mTaskEntity = taskEntity;
|
||||
CheckUtil.checkTaskEntity(taskEntity);
|
||||
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;
|
||||
HttpThreadTask(StateConstance constance, IUploadListener listener,
|
||||
SubThreadConfig<UploadTaskEntity> uploadInfo) {
|
||||
super(constance, listener, uploadInfo);
|
||||
}
|
||||
|
||||
@Override public void run() {
|
||||
File uploadFile = new File(mUploadEntity.getFilePath());
|
||||
File uploadFile = new File(mEntity.getFilePath());
|
||||
if (!uploadFile.exists()) {
|
||||
Log.e(TAG, "【" + mUploadEntity.getFilePath() + "】,文件不存在。");
|
||||
Log.e(TAG, "【" + mEntity.getFilePath() + "】,文件不存在。");
|
||||
fail();
|
||||
return;
|
||||
}
|
||||
|
||||
mListener.onPre();
|
||||
URL url;
|
||||
try {
|
||||
url = new URL(mTaskEntity.uploadUrl);
|
||||
url = new URL(mEntity.getUrl());
|
||||
mHttpConn = (HttpURLConnection) url.openConnection();
|
||||
mHttpConn.setUseCaches(false);
|
||||
mHttpConn.setDoOutput(true);
|
||||
@ -97,19 +81,14 @@ final class UploadUtil implements Runnable {
|
||||
for (String key : keys) {
|
||||
mHttpConn.setRequestProperty(key, mTaskEntity.headers.get(key));
|
||||
}
|
||||
|
||||
mOutputStream = mHttpConn.getOutputStream();
|
||||
mListener.onPostPre(uploadFile.length());
|
||||
|
||||
PrintWriter writer =
|
||||
new PrintWriter(new OutputStreamWriter(mOutputStream, mTaskEntity.charset), true);
|
||||
|
||||
new PrintWriter(new OutputStreamWriter(mOutputStream, mTaskEntity.charSet), true);
|
||||
//添加文件上传表单字段
|
||||
keys = mTaskEntity.formFields.keySet();
|
||||
for (String key : keys) {
|
||||
addFormField(writer, key, mTaskEntity.formFields.get(key));
|
||||
}
|
||||
mListener.onStart(0);
|
||||
uploadFile(writer, mTaskEntity.attachment, uploadFile);
|
||||
Log.d(TAG, finish(writer) + "");
|
||||
} catch (IOException e) {
|
||||
@ -118,13 +97,10 @@ final class UploadUtil implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
boolean isRunning() {
|
||||
return isRunning;
|
||||
}
|
||||
|
||||
private void fail() {
|
||||
try {
|
||||
mListener.onFail();
|
||||
STATE.isRunning = false;
|
||||
if (mOutputStream != null) {
|
||||
mOutputStream.close();
|
||||
}
|
||||
@ -143,7 +119,7 @@ final class UploadUtil implements Runnable {
|
||||
.append("\"")
|
||||
.append(LINE_END);
|
||||
writer.append("Content-Type: text/plain; charset=")
|
||||
.append(mTaskEntity.charset)
|
||||
.append(mTaskEntity.charSet)
|
||||
.append(LINE_END);
|
||||
writer.append(LINE_END);
|
||||
writer.append(value).append(LINE_END);
|
||||
@ -178,10 +154,9 @@ final class UploadUtil implements Runnable {
|
||||
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||
mCurrentLocation += bytesRead;
|
||||
mOutputStream.write(buffer, 0, bytesRead);
|
||||
if (isCancel) {
|
||||
if (STATE.isCancel) {
|
||||
break;
|
||||
}
|
||||
isRunning = true;
|
||||
mListener.onProgress(mCurrentLocation);
|
||||
}
|
||||
|
||||
@ -190,12 +165,13 @@ final class UploadUtil implements Runnable {
|
||||
inputStream.close();
|
||||
writer.append(LINE_END);
|
||||
writer.flush();
|
||||
isRunning = false;
|
||||
if (isCancel) {
|
||||
if (STATE.isCancel) {
|
||||
mListener.onCancel();
|
||||
STATE.isRunning = false;
|
||||
return;
|
||||
}
|
||||
mListener.onComplete();
|
||||
STATE.isRunning = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -221,7 +197,7 @@ final class UploadUtil implements Runnable {
|
||||
mHttpConn.disconnect();
|
||||
} else {
|
||||
Log.w(TAG, "state_code = " + status);
|
||||
mListener.onFail();
|
||||
fail();
|
||||
}
|
||||
|
||||
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 {
|
||||
static Map<String, Class> mapping = new HashMap<>();
|
||||
static String DB_NAME;
|
||||
static int VERSION = 11;
|
||||
static int VERSION = 12;
|
||||
|
||||
static {
|
||||
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.UploadTaskEntity;
|
||||
import com.arialyy.aria.exception.FileException;
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
@ -77,6 +78,14 @@ public class CheckUtil {
|
||||
if (TextUtils.isEmpty(downloadUrl)) throw new IllegalArgumentException("下载链接不能为null");
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测下载链接是否为null
|
||||
*/
|
||||
public static void checkUploadUrl(String downloadUrl) {
|
||||
if (TextUtils.isEmpty(downloadUrl)) throw new IllegalArgumentException("上传地址不能为null");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 检测下载链接组是否为null
|
||||
*/
|
||||
@ -100,6 +109,8 @@ public class CheckUtil {
|
||||
*/
|
||||
public static void checkUploadPath(String uploadPath) {
|
||||
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();
|
||||
if (entity1 == null) {
|
||||
Log.e(TAG, "下载实体不能为空");
|
||||
} else if (checkType && TextUtils.isEmpty(entity1.getDownloadUrl())) {
|
||||
} else if (checkType && TextUtils.isEmpty(entity1.getUrl())) {
|
||||
Log.e(TAG, "下载链接不能为空");
|
||||
} else if (checkType && TextUtils.isEmpty(entity1.getDownloadPath())) {
|
||||
Log.e(TAG, "保存路径不能为空");
|
||||
@ -167,7 +178,7 @@ public class CheckUtil {
|
||||
private static void checkDownloadTaskEntity(DownloadEntity entity) {
|
||||
if (entity == null) {
|
||||
throw new NullPointerException("下载实体不能为空");
|
||||
} else if (TextUtils.isEmpty(entity.getDownloadUrl())) {
|
||||
} else if (TextUtils.isEmpty(entity.getUrl())) {
|
||||
throw new IllegalArgumentException("下载链接不能为空");
|
||||
} else if (TextUtils.isEmpty(entity.getFileName())) {
|
||||
throw new FileException("文件名不能为null");
|
||||
|
@ -37,6 +37,7 @@ import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
@ -63,6 +64,18 @@ import java.lang.reflect.WildcardType;
|
||||
public class 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}如果任务已经完成,只删除任务数据库记录
|
||||
*/
|
||||
public static void delUploadTaskConfig(boolean removeFile, UploadTaskEntity tEntity) {
|
||||
@ -147,15 +160,8 @@ public class CommonUtil {
|
||||
if (file.exists()) {
|
||||
file.delete();
|
||||
}
|
||||
} else {
|
||||
if (!uEntity.isComplete()) {
|
||||
if (file.exists()) {
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
File config = new File(
|
||||
AriaManager.APP.getFilesDir().getPath() + "/temp/" + uEntity.getFileName() + ".properties");
|
||||
File config = new File(getFileConfig(false, uEntity.getFileName()));
|
||||
if (config.exists()) {
|
||||
config.delete();
|
||||
}
|
||||
@ -184,8 +190,7 @@ public class CommonUtil {
|
||||
}
|
||||
}
|
||||
|
||||
File config = new File(
|
||||
AriaManager.APP.getFilesDir().getPath() + "/temp/" + dEntity.getFileName() + ".properties");
|
||||
File config = new File(getFileConfig(true, dEntity.getFileName()));
|
||||
if (config.exists()) {
|
||||
config.delete();
|
||||
}
|
||||
@ -599,6 +604,16 @@ public class CommonUtil {
|
||||
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 新的下载文件名
|
||||
*/
|
||||
public static void renameDownloadConfig(String oldName, String newName) {
|
||||
renameConfig(AriaManager.APP.getFilesDir().getPath() + AriaManager.DOWNLOAD_TEMP_DIR, oldName,
|
||||
newName);
|
||||
renameConfig(true, oldName, newName);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -621,14 +635,13 @@ public class CommonUtil {
|
||||
* @param newName 新的上传文件名
|
||||
*/
|
||||
public static void renameUploadConfig(String oldName, String newName) {
|
||||
renameConfig(AriaManager.APP.getFilesDir().getPath() + AriaManager.UPLOAD_TEMP_DIR, oldName,
|
||||
newName);
|
||||
renameConfig(false, oldName, 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;
|
||||
File oldFile = new File(basePath + oldName + ".properties");
|
||||
File newFile = new File(basePath + newName + ".properties");
|
||||
File oldFile = new File(getFileConfig(isDownload, oldName));
|
||||
File newFile = new File(getFileConfig(isDownload, oldName));
|
||||
if (!oldFile.exists()) {
|
||||
createFile(newFile.getPath());
|
||||
} 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
887
Aria/src/main/java/org/apache/commons/net/SocketClient.java
Normal file
887
Aria/src/main/java/org/apache/commons/net/SocketClient.java
Normal file
@ -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);
|
||||
}
|
1870
Aria/src/main/java/org/apache/commons/net/ftp/FTP.java
Normal file
1870
Aria/src/main/java/org/apache/commons/net/ftp/FTP.java
Normal file
File diff suppressed because it is too large
Load Diff
3980
Aria/src/main/java/org/apache/commons/net/ftp/FTPClient.java
Normal file
3980
Aria/src/main/java/org/apache/commons/net/ftp/FTPClient.java
Normal file
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í|jún|júl|ágú|sep|okt|nóv|des"</code>.
|
||||
* </p>
|
||||
* @param shortMonthNames The value to set to the shortMonthNames property.
|
||||
*/
|
||||
public void setShortMonthNames(String shortMonthNames) {
|
||||
this.shortMonthNames = shortMonthNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* <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;
|
||||
}
|
||||
|
||||
}
|
116
Aria/src/main/java/org/apache/commons/net/ftp/FTPCmd.java
Normal file
116
Aria/src/main/java/org/apache/commons/net/ftp/FTPCmd.java
Normal file
@ -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();
|
||||
}
|
||||
|
||||
}
|
171
Aria/src/main/java/org/apache/commons/net/ftp/FTPCommand.java
Normal file
171
Aria/src/main/java/org/apache/commons/net/ftp/FTPCommand.java
Normal file
@ -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);
|
||||
}
|
||||
|
||||
}
|
536
Aria/src/main/java/org/apache/commons/net/ftp/FTPFile.java
Normal file
536
Aria/src/main/java/org/apache/commons/net/ftp/FTPFile.java
Normal file
@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
202
Aria/src/main/java/org/apache/commons/net/ftp/FTPHTTPClient.java
Normal file
202
Aria/src/main/java/org/apache/commons/net/ftp/FTPHTTPClient.java
Normal file
@ -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);
|
||||
}
|
||||
|
||||
}
|
201
Aria/src/main/java/org/apache/commons/net/ftp/FTPReply.java
Normal file
201
Aria/src/main/java/org/apache/commons/net/ftp/FTPReply.java
Normal file
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
925
Aria/src/main/java/org/apache/commons/net/ftp/FTPSClient.java
Normal file
925
Aria/src/main/java/org/apache/commons/net/ftp/FTPSClient.java
Normal file
@ -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
Reference in New Issue
Block a user