Files
Aria/FtpComponent/src/main/java/com/arialyy/aria/ftp/AbsFtpInfoTask.java
2020-01-01 14:43:52 +08:00

426 lines
14 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 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;
}
}
}