loader 重构
This commit is contained in:
@@ -0,0 +1,425 @@
|
||||
/*
|
||||
* 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.ftp;
|
||||
|
||||
import android.net.TrafficStats;
|
||||
import android.os.Process;
|
||||
import android.text.TextUtils;
|
||||
import aria.apache.commons.net.ftp.FTP;
|
||||
import aria.apache.commons.net.ftp.FTPClient;
|
||||
import aria.apache.commons.net.ftp.FTPClientConfig;
|
||||
import aria.apache.commons.net.ftp.FTPFile;
|
||||
import aria.apache.commons.net.ftp.FTPReply;
|
||||
import aria.apache.commons.net.ftp.FTPSClient;
|
||||
import com.arialyy.aria.core.AriaConfig;
|
||||
import com.arialyy.aria.core.FtpUrlEntity;
|
||||
import com.arialyy.aria.core.common.AbsEntity;
|
||||
import com.arialyy.aria.core.common.FtpConnectionMode;
|
||||
import com.arialyy.aria.core.loader.IInfoTask;
|
||||
import com.arialyy.aria.core.loader.ILoaderVisitor;
|
||||
import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
|
||||
import com.arialyy.aria.exception.AriaIOException;
|
||||
import com.arialyy.aria.exception.TaskException;
|
||||
import com.arialyy.aria.util.ALog;
|
||||
import com.arialyy.aria.util.CheckUtil;
|
||||
import com.arialyy.aria.util.CommonUtil;
|
||||
import com.arialyy.aria.util.SSLContextUtil;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.UUID;
|
||||
import javax.net.ssl.SSLContext;
|
||||
|
||||
/**
|
||||
* Created by Aria.Lao on 2017/7/25. 获取ftp文件夹信息
|
||||
*/
|
||||
public abstract class AbsFtpInfoTask<ENTITY extends AbsEntity, TASK_WRAPPER extends AbsTaskWrapper<ENTITY>>
|
||||
implements Runnable, IInfoTask {
|
||||
|
||||
protected final String TAG = CommonUtil.getClassName(getClass());
|
||||
protected ENTITY mEntity;
|
||||
protected TASK_WRAPPER mTaskWrapper;
|
||||
protected FtpTaskOption mTaskOption;
|
||||
private int mConnectTimeOut;
|
||||
protected long mSize = 0;
|
||||
protected String charSet = "UTF-8";
|
||||
protected boolean isUpload = false;
|
||||
protected Callback callback;
|
||||
|
||||
public AbsFtpInfoTask(TASK_WRAPPER taskWrapper) {
|
||||
mTaskWrapper = taskWrapper;
|
||||
mEntity = taskWrapper.getEntity();
|
||||
mTaskOption = (FtpTaskOption) taskWrapper.getTaskOption();
|
||||
mConnectTimeOut = AriaConfig.getInstance().getDConfig().getConnectTimeOut();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求的远程文件路径
|
||||
*
|
||||
* @return 远程文件路径
|
||||
*/
|
||||
protected abstract String getRemotePath();
|
||||
|
||||
@Override public void setCallback(Callback callback) {
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
|
||||
TrafficStats.setThreadStatsTag(UUID.randomUUID().toString().hashCode());
|
||||
FTPClient client = null;
|
||||
try {
|
||||
client = createFtpClient();
|
||||
if (client == null) {
|
||||
ALog.e(TAG, String.format("任务【%s】失败", mTaskOption.getUrlEntity().url));
|
||||
return;
|
||||
}
|
||||
String remotePath = CommonUtil.convertFtpChar(charSet, getRemotePath());
|
||||
|
||||
FTPFile[] files = client.listFiles(getRemotePath());
|
||||
boolean isExist = files.length != 0;
|
||||
if (!isExist && !isUpload) {
|
||||
int i = remotePath.lastIndexOf(File.separator);
|
||||
FTPFile[] files1;
|
||||
if (i == -1) {
|
||||
files1 = client.listFiles();
|
||||
} else {
|
||||
files1 = client.listFiles(remotePath.substring(0, i + 1));
|
||||
}
|
||||
if (files1.length > 0) {
|
||||
ALog.i(TAG,
|
||||
String.format("路径【%s】下的文件列表 ===================================", getRemotePath()));
|
||||
for (FTPFile file : files1) {
|
||||
ALog.d(TAG, file.toString());
|
||||
}
|
||||
ALog.i(TAG,
|
||||
"================================= --end-- ===================================");
|
||||
} else {
|
||||
ALog.w(TAG, String.format("获取文件列表失败,msg:%s", client.getReplyString()));
|
||||
}
|
||||
closeClient(client);
|
||||
|
||||
failDownload(client,
|
||||
String.format("文件不存在,url: %s, remotePath:%s", mTaskOption.getUrlEntity().url,
|
||||
remotePath), null, false);
|
||||
return;
|
||||
}
|
||||
|
||||
// 处理拦截功能
|
||||
if (!onInterceptor(client, files)) {
|
||||
closeClient(client);
|
||||
ALog.d(TAG, "拦截器处理完成任务");
|
||||
return;
|
||||
}
|
||||
|
||||
//为了防止编码错乱,需要使用原始字符串
|
||||
mSize = getFileSize(files, client, getRemotePath());
|
||||
int reply = client.getReplyCode();
|
||||
if (!FTPReply.isPositiveCompletion(reply)) {
|
||||
if (isUpload) {
|
||||
//服务器上没有该文件路径,表示该任务为新的上传任务
|
||||
mTaskWrapper.setNewTask(true);
|
||||
} else {
|
||||
closeClient(client);
|
||||
failDownload(client, "获取文件信息错误,url: " + mTaskOption.getUrlEntity().url, null, true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
mTaskWrapper.setCode(reply);
|
||||
if (mSize != 0 && !isUpload) {
|
||||
mEntity.setFileSize(mSize);
|
||||
}
|
||||
onPreComplete(reply);
|
||||
mEntity.update();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
failDownload(client, "FTP错误信息", e, true);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
failDownload(client, "FTP错误信息", e, true);
|
||||
} finally {
|
||||
closeClient(client);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理拦截
|
||||
*
|
||||
* @param ftpFiles remotePath路径下的所有文件
|
||||
* @return {@code false} 拦截器处理完成任务,任务将不再执行,{@code true} 拦截器处理任务完成任务,任务继续执行
|
||||
*/
|
||||
protected boolean onInterceptor(FTPClient client, FTPFile[] ftpFiles) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查文件是否存在
|
||||
*
|
||||
* @return {@code true}存在
|
||||
*/
|
||||
private boolean checkFileExist(FTPFile[] ftpFiles, String fileName) {
|
||||
for (FTPFile ff : ftpFiles) {
|
||||
if (ff.getName().equals(fileName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void onPreComplete(int code) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建FTP客户端
|
||||
*/
|
||||
private FTPClient createFtpClient() throws IOException, InterruptedException {
|
||||
FTPClient client = null;
|
||||
final FtpUrlEntity urlEntity = mTaskOption.getUrlEntity();
|
||||
if (CheckUtil.checkIp(urlEntity.hostName)) {
|
||||
client = newInstanceClient(urlEntity);
|
||||
client.setConnectTimeout(mConnectTimeOut); // 连接10s超时
|
||||
InetAddress ip = InetAddress.getByName(urlEntity.hostName);
|
||||
|
||||
client = connect(client, new InetAddress[] { ip }, 0, Integer.parseInt(urlEntity.port));
|
||||
mTaskOption.getUrlEntity().validAddr = ip;
|
||||
} else {
|
||||
DNSQueryThread dnsThread = new DNSQueryThread(urlEntity.hostName);
|
||||
dnsThread.start();
|
||||
dnsThread.join(mConnectTimeOut);
|
||||
InetAddress[] ips = dnsThread.getIps();
|
||||
client = connect(newInstanceClient(urlEntity), ips, 0, Integer.parseInt(urlEntity.port));
|
||||
}
|
||||
|
||||
if (client == null) {
|
||||
failDownload(client, String.format("链接失败, url: %s", mTaskOption.getUrlEntity().url), null,
|
||||
true);
|
||||
return null;
|
||||
}
|
||||
|
||||
boolean loginSuccess = true;
|
||||
if (urlEntity.needLogin) {
|
||||
try {
|
||||
if (TextUtils.isEmpty(urlEntity.account)) {
|
||||
loginSuccess = client.login(urlEntity.user, urlEntity.password);
|
||||
} else {
|
||||
loginSuccess = client.login(urlEntity.user, urlEntity.password, urlEntity.account);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
ALog.e(TAG,
|
||||
new TaskException(TAG, String.format("登录失败,错误码为:%s, msg:%s", client.getReplyCode(),
|
||||
client.getReplyString()), e));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (!loginSuccess) {
|
||||
failDownload(client, "登录失败", null, false);
|
||||
client.disconnect();
|
||||
return null;
|
||||
}
|
||||
|
||||
int reply = client.getReplyCode();
|
||||
if (!FTPReply.isPositiveCompletion(reply)) {
|
||||
failDownload(client, String.format("无法连接到ftp服务器,filePath: %s, url: %s", mEntity.getKey(),
|
||||
mTaskOption.getUrlEntity().url), null, true);
|
||||
client.disconnect();
|
||||
return null;
|
||||
}
|
||||
// 开启服务器对UTF-8的支持,如果服务器支持就用UTF-8编码
|
||||
charSet = "UTF-8";
|
||||
reply = client.sendCommand("OPTS UTF8", "ON");
|
||||
if (reply != FTPReply.COMMAND_IS_SUPERFLUOUS) {
|
||||
ALog.i(TAG, "D_FTP 服务器不支持开启UTF8编码,尝试使用Aria手动设置的编码");
|
||||
if (!TextUtils.isEmpty(mTaskOption.getCharSet())) {
|
||||
charSet = mTaskOption.getCharSet();
|
||||
}
|
||||
}
|
||||
client.setControlEncoding(charSet);
|
||||
client.setDataTimeout(10 * 1000);
|
||||
if (mTaskOption.getConnMode() == FtpConnectionMode.DATA_CONNECTION_MODE_ACTIVITY) {
|
||||
client.enterLocalActiveMode();
|
||||
if (mTaskOption.getMinPort() != 0 && mTaskOption.getMaxPort() != 0) {
|
||||
client.setActivePortRange(mTaskOption.getMinPort(), mTaskOption.getMaxPort());
|
||||
}
|
||||
if (!TextUtils.isEmpty(mTaskOption.getActiveExternalIPAddress())) {
|
||||
client.setActiveExternalIPAddress(mTaskOption.getActiveExternalIPAddress());
|
||||
}
|
||||
} else {
|
||||
client.enterLocalPassiveMode();
|
||||
}
|
||||
client.setFileType(FTP.BINARY_FILE_TYPE);
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
protected void closeClient(FTPClient client) {
|
||||
try {
|
||||
if (client != null && client.isConnected()) {
|
||||
client.logout();
|
||||
client.disconnect();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建FTP/FTPS客户端
|
||||
*/
|
||||
private FTPClient newInstanceClient(FtpUrlEntity urlEntity) {
|
||||
FTPClient temp;
|
||||
if (urlEntity.isFtps) {
|
||||
FTPSClient sClient;
|
||||
SSLContext sslContext = SSLContextUtil.getSSLContext(urlEntity.keyAlias, urlEntity.storePath,
|
||||
urlEntity.protocol);
|
||||
if (sslContext == null) {
|
||||
sClient = new FTPSClient(urlEntity.protocol, urlEntity.isImplicit);
|
||||
} else {
|
||||
sClient = new FTPSClient(true, sslContext);
|
||||
}
|
||||
|
||||
temp = sClient;
|
||||
} else {
|
||||
temp = new FTPClient();
|
||||
}
|
||||
|
||||
FTPClientConfig clientConfig;
|
||||
if (mTaskOption.getClientConfig() != null) {
|
||||
clientConfig = mTaskOption.getClientConfig();
|
||||
} else {
|
||||
clientConfig = new FTPClientConfig(FTPClientConfig.SYST_UNIX);
|
||||
clientConfig.setServerLanguageCode("en");
|
||||
}
|
||||
temp.configure(clientConfig);
|
||||
|
||||
return temp;
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接到ftp服务器
|
||||
*/
|
||||
private FTPClient connect(FTPClient client, InetAddress[] ips, int index, int port) {
|
||||
if (ips == null || ips.length == 0) {
|
||||
ALog.w(TAG, "无可用ip");
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
client.setConnectTimeout(mConnectTimeOut); //需要先设置超时,这样才不会出现阻塞
|
||||
client.connect(ips[index], port);
|
||||
mTaskOption.getUrlEntity().validAddr = ips[index];
|
||||
|
||||
FtpUrlEntity urlEntity = mTaskOption.getUrlEntity();
|
||||
if (urlEntity.isFtps) {
|
||||
FTPSClient sClient = (FTPSClient) client;
|
||||
sClient.execPBSZ(0);
|
||||
sClient.execPROT("P");
|
||||
}
|
||||
|
||||
return client;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
closeClient(client);
|
||||
if (index + 1 >= ips.length) {
|
||||
ALog.w(TAG, "遇到[ECONNREFUSED-连接被服务器拒绝]错误,已没有其他地址,链接失败;如果是ftps,请检查端口是否使用了ftp的端口而不是ftps的端口");
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e1) {
|
||||
e1.printStackTrace();
|
||||
}
|
||||
ALog.w(TAG, "遇到[ECONNREFUSED-连接被服务器拒绝]错误,正在尝试下一个地址");
|
||||
return connect(newInstanceClient(mTaskOption.getUrlEntity()), ips, index + 1, port);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 遍历FTP服务器上对应文件或文件夹大小
|
||||
*
|
||||
* @throws IOException 字符串编码转换错误
|
||||
*/
|
||||
private long getFileSize(FTPFile[] files, FTPClient client, String dirName) throws IOException {
|
||||
long size = 0;
|
||||
String path = dirName + "/";
|
||||
for (FTPFile file : files) {
|
||||
if (file.isFile()) {
|
||||
size += file.getSize();
|
||||
handleFile(path + file.getName(), file);
|
||||
} else {
|
||||
String remotePath = CommonUtil.convertFtpChar(charSet, path + file.getName());
|
||||
size += getFileSize(client.listFiles(remotePath), client, path + file.getName());
|
||||
}
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理FTP文件信息
|
||||
*
|
||||
* @param remotePath ftp服务器文件夹路径
|
||||
* @param ftpFile ftp服务器上对应的文件
|
||||
*/
|
||||
protected void handleFile(String remotePath, FTPFile ftpFile) {
|
||||
}
|
||||
|
||||
protected void failDownload(FTPClient client, String msg, Exception e, boolean needRetry) {
|
||||
if (callback != null) {
|
||||
if (client == null) {
|
||||
msg = "创建ftp客户端失败";
|
||||
needRetry = false;
|
||||
} else {
|
||||
msg = String.format("%s, code: %s, msg: %s", msg, client.getReplyCode(),
|
||||
client.getReplyString());
|
||||
needRetry = needRetry && !CheckUtil.ftpIsBadRequest(client.getReplyCode());
|
||||
}
|
||||
|
||||
callback.onFail(mEntity, new AriaIOException(TAG, msg), needRetry);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void accept(ILoaderVisitor visitor) {
|
||||
visitor.addComponent(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取可用IP的超时线程,InetAddress.getByName没有超时功能,需要自己处理超时
|
||||
*/
|
||||
private static class DNSQueryThread extends Thread {
|
||||
|
||||
private String hostName;
|
||||
private InetAddress[] ips;
|
||||
|
||||
DNSQueryThread(String hostName) {
|
||||
this.hostName = hostName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
ips = InetAddress.getAllByName(hostName);
|
||||
} catch (UnknownHostException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
synchronized InetAddress[] getIps() {
|
||||
return ips;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user