ftps 支持
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
package com.arialyy.aria.core;
|
||||
|
||||
import com.arialyy.aria.orm.annotation.Ignore;
|
||||
import java.net.InetAddress;
|
||||
|
||||
/**
|
||||
@@ -32,6 +33,12 @@ public class FtpUrlEntity implements Cloneable {
|
||||
|
||||
public String account;
|
||||
|
||||
/**
|
||||
* 是否是ftps
|
||||
* {@code true}ftps协议的地址,{@code false}不是ftps协议的地址
|
||||
*/
|
||||
public boolean isFtps = false;
|
||||
|
||||
/**
|
||||
* 原始url
|
||||
*/
|
||||
@@ -46,9 +53,11 @@ public class FtpUrlEntity implements Cloneable {
|
||||
* 登录的用户名
|
||||
*/
|
||||
public String user;
|
||||
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
@Ignore
|
||||
public String password;
|
||||
|
||||
/**
|
||||
@@ -71,6 +80,26 @@ public class FtpUrlEntity implements Cloneable {
|
||||
*/
|
||||
public InetAddress validAddr;
|
||||
|
||||
/**
|
||||
* 证书路径
|
||||
*/
|
||||
public String storePath;
|
||||
|
||||
/**
|
||||
* 证书密码
|
||||
*/
|
||||
public String storePass;
|
||||
|
||||
/**
|
||||
* SSL协议
|
||||
*/
|
||||
public String SSLProtocol;
|
||||
|
||||
/**
|
||||
* 私钥别名
|
||||
*/
|
||||
public String keyAlias;
|
||||
|
||||
@Override public FtpUrlEntity clone() {
|
||||
FtpUrlEntity entity = null;
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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.support.annotation.StringDef;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
@StringDef({
|
||||
ProtocolType.Default,
|
||||
ProtocolType.SSL,
|
||||
ProtocolType.SSLv3,
|
||||
ProtocolType.TLS,
|
||||
ProtocolType.TLSv1,
|
||||
ProtocolType.TLSv1_1,
|
||||
ProtocolType.TLSv1_2
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE) public @interface ProtocolType {
|
||||
String Default = "Default";
|
||||
String SSL = "SSL";
|
||||
String SSLv3 = "SSLv3";
|
||||
String TLS = "TLS";
|
||||
String TLSv1 = "TLSv1";
|
||||
String TLSv1_1 = "TLSv1.1";
|
||||
String TLSv1_2 = "TLSv1.2";
|
||||
}
|
||||
@@ -13,26 +13,31 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.arialyy.aria.core.common;
|
||||
package com.arialyy.aria.core.common.ftp;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import com.arialyy.aria.core.AriaManager;
|
||||
import com.arialyy.aria.core.FtpUrlEntity;
|
||||
import com.arialyy.aria.core.common.OnFileInfoCallback;
|
||||
import com.arialyy.aria.core.common.ProtocolType;
|
||||
import com.arialyy.aria.core.inf.AbsEntity;
|
||||
import com.arialyy.aria.core.inf.AbsTaskEntity;
|
||||
import com.arialyy.aria.core.upload.UploadEntity;
|
||||
import com.arialyy.aria.util.ALog;
|
||||
import com.arialyy.aria.util.Regular;
|
||||
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.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import javax.net.ssl.SSLContext;
|
||||
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;
|
||||
import org.apache.commons.net.ftp.FTPSClient;
|
||||
|
||||
/**
|
||||
* Created by Aria.Lao on 2017/7/25.
|
||||
@@ -78,9 +83,11 @@ public abstract class AbsFtpInfoThread<ENTITY extends AbsEntity, TASK_ENTITY ext
|
||||
}
|
||||
String remotePath =
|
||||
new String(setRemotePath().getBytes(charSet), AbsFtpThreadTask.SERVER_CHARSET);
|
||||
if (mTaskEntity.getUrlEntity().isFtps) {
|
||||
((FTPSClient) client).execPROT("P");
|
||||
//((FTPSClient) client).setEnabledSessionCreation(false);
|
||||
}
|
||||
FTPFile[] files = client.listFiles(remotePath);
|
||||
String s = client.getReplyString();
|
||||
ALog.i(TAG, s);
|
||||
boolean isExist = files.length != 0;
|
||||
if (!isExist && !isUpload) {
|
||||
failDownload(String.format("文件不存在,任务链接【%s】,remotePath:%s", mTaskEntity.getUrlEntity().url,
|
||||
@@ -101,8 +108,7 @@ public abstract class AbsFtpInfoThread<ENTITY extends AbsEntity, TASK_ENTITY ext
|
||||
ALog.i(TAG,
|
||||
"================================= --end-- ===================================");
|
||||
} else {
|
||||
String msg = client.getReplyString();
|
||||
ALog.w(TAG, msg);
|
||||
ALog.w(TAG, String.format("获取文件列表失败,msg:%s", client.getReplyString()));
|
||||
}
|
||||
client.disconnect();
|
||||
return;
|
||||
@@ -128,15 +134,12 @@ public abstract class AbsFtpInfoThread<ENTITY extends AbsEntity, TASK_ENTITY ext
|
||||
mTaskEntity.update();
|
||||
onPreComplete(reply);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
ALog.e(TAG,
|
||||
String.format("FTP错误信息,code:%s,msg:%s", client.getReplyCode(), client.getReplyString()));
|
||||
failDownload(e.getMessage(), true);
|
||||
} finally {
|
||||
if (client != null) {
|
||||
try {
|
||||
client.disconnect();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
closeClient(client);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,7 +175,7 @@ public abstract class AbsFtpInfoThread<ENTITY extends AbsEntity, TASK_ENTITY ext
|
||||
Pattern p = Pattern.compile(Regular.REG_IP_V4);
|
||||
Matcher m = p.matcher(urlEntity.hostName);
|
||||
if (m.find() && m.groupCount() > 0) {
|
||||
client = new FTPClient();
|
||||
client = newInstanceClient(urlEntity);
|
||||
InetAddress ip = InetAddress.getByName(urlEntity.hostName);
|
||||
client.setConnectTimeout(mConnectTimeOut); // 连接10s超时
|
||||
client.connect(ip, Integer.parseInt(urlEntity.port));
|
||||
@@ -182,7 +185,13 @@ public abstract class AbsFtpInfoThread<ENTITY extends AbsEntity, TASK_ENTITY ext
|
||||
dnsThread.start();
|
||||
dnsThread.join(mConnectTimeOut);
|
||||
InetAddress[] ips = dnsThread.getIps();
|
||||
client = connect(new FTPClient(), ips, 0, Integer.parseInt(urlEntity.port));
|
||||
client = connect(newInstanceClient(urlEntity), ips, 0, Integer.parseInt(urlEntity.port));
|
||||
}
|
||||
|
||||
if (urlEntity.isFtps) {
|
||||
int code = ((FTPSClient) client).execAUTH(
|
||||
TextUtils.isEmpty(urlEntity.SSLProtocol) ? ProtocolType.TLS : urlEntity.SSLProtocol);
|
||||
ALog.d(TAG, String.format("cod:%s,msg:%s", code, client.getReplyString()));
|
||||
}
|
||||
|
||||
if (client == null) {
|
||||
@@ -199,13 +208,17 @@ public abstract class AbsFtpInfoThread<ENTITY extends AbsEntity, TASK_ENTITY ext
|
||||
loginSuccess = client.login(urlEntity.user, urlEntity.password, urlEntity.account);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
ALog.e(TAG, client.getReplyString());
|
||||
ALog.e(TAG, String.format("登录失败,错误码为:%s, msg:%s", client.getReplyCode(),
|
||||
client.getReplyString()));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (!loginSuccess) {
|
||||
failDownload("登录失败", false);
|
||||
failDownload(
|
||||
String.format("登录失败,错误码为:%s, msg:%s", client.getReplyCode(), client.getReplyString()),
|
||||
false);
|
||||
client.disconnect();
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -219,23 +232,59 @@ public abstract class AbsFtpInfoThread<ENTITY extends AbsEntity, TASK_ENTITY ext
|
||||
// 开启服务器对UTF-8的支持,如果服务器支持就用UTF-8编码
|
||||
charSet = "UTF-8";
|
||||
reply = client.sendCommand("OPTS UTF8", "ON");
|
||||
if (!TextUtils.isEmpty(mTaskEntity.getCharSet()) || (!FTPReply.isPositiveCompletion(reply)
|
||||
&& reply != FTPReply.COMMAND_OK)) {
|
||||
if (reply != FTPReply.COMMAND_IS_SUPERFLUOUS) {
|
||||
ALog.i(TAG, "FTP 服务器不支持开启UTF8编码,尝试使用Aria手动设置的编码");
|
||||
charSet = mTaskEntity.getCharSet();
|
||||
if (!TextUtils.isEmpty(mTaskEntity.getCharSet())) {
|
||||
charSet = mTaskEntity.getCharSet();
|
||||
}
|
||||
}
|
||||
client.setControlEncoding(charSet);
|
||||
client.setDataTimeout(10 * 1000);
|
||||
client.enterLocalPassiveMode();
|
||||
client.setFileType(FTP.BINARY_FILE_TYPE);
|
||||
} catch (IOException e) {
|
||||
closeClient(client);
|
||||
e.printStackTrace();
|
||||
} catch (InterruptedException e) {
|
||||
closeClient(client);
|
||||
e.printStackTrace();
|
||||
}
|
||||
return client;
|
||||
}
|
||||
|
||||
private void closeClient(FTPClient client) {
|
||||
try {
|
||||
if (client != null && client.isConnected()) {
|
||||
client.disconnect();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建FTP/FTPS客户端
|
||||
*/
|
||||
private FTPClient newInstanceClient(FtpUrlEntity urlEntity) {
|
||||
FTPClient temp = null;
|
||||
if (urlEntity.isFtps) {
|
||||
SSLContext sslContext =
|
||||
SSLContextUtil.getSSLContext(urlEntity.keyAlias, urlEntity.storePath);
|
||||
if (sslContext == null) {
|
||||
sslContext = SSLContextUtil.getDefaultSLLContext();
|
||||
}
|
||||
//System.setProperty("jdk.tls.useExtendedMasterSecret", "false");
|
||||
//temp = new SSLSessionReuseFTPSClient(true, sslContext);
|
||||
|
||||
FTPSClient client = new FTPSClient(true, sslContext);
|
||||
temp = client;
|
||||
} else {
|
||||
temp = new FTPClient();
|
||||
}
|
||||
|
||||
return temp;
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接到ftp服务器
|
||||
*/
|
||||
@@ -268,7 +317,7 @@ public abstract class AbsFtpInfoThread<ENTITY extends AbsEntity, TASK_ENTITY ext
|
||||
e1.printStackTrace();
|
||||
}
|
||||
ALog.w(TAG, "遇到[ECONNREFUSED-连接被服务器拒绝]错误,正在尝试下一个地址");
|
||||
return connect(new FTPClient(), ips, index + 1, port);
|
||||
return connect(newInstanceClient(mTaskEntity.getUrlEntity()), ips, index + 1, port);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,20 +13,27 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.arialyy.aria.core.common;
|
||||
package com.arialyy.aria.core.common.ftp;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import com.arialyy.aria.core.FtpUrlEntity;
|
||||
import com.arialyy.aria.core.common.AbsThreadTask;
|
||||
import com.arialyy.aria.core.common.ProtocolType;
|
||||
import com.arialyy.aria.core.common.StateConstance;
|
||||
import com.arialyy.aria.core.common.SubThreadConfig;
|
||||
import com.arialyy.aria.core.inf.AbsNormalEntity;
|
||||
import com.arialyy.aria.core.inf.AbsTaskEntity;
|
||||
import com.arialyy.aria.core.inf.IEventListener;
|
||||
import com.arialyy.aria.util.ALog;
|
||||
import com.arialyy.aria.util.SSLContextUtil;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import org.apache.commons.net.ftp.FTP;
|
||||
import org.apache.commons.net.ftp.FTPClient;
|
||||
import org.apache.commons.net.ftp.FTPReply;
|
||||
import org.apache.commons.net.ftp.FTPSClient;
|
||||
|
||||
/**
|
||||
* Created by lyy on 2017/9/26.
|
||||
@@ -35,7 +42,7 @@ import org.apache.commons.net.ftp.FTPReply;
|
||||
public abstract class AbsFtpThreadTask<ENTITY extends AbsNormalEntity, TASK_ENTITY extends AbsTaskEntity<ENTITY>>
|
||||
extends AbsThreadTask<ENTITY, TASK_ENTITY> {
|
||||
private final String TAG = "AbsFtpThreadTask";
|
||||
protected String charSet, port;
|
||||
protected String charSet;
|
||||
/**
|
||||
* D_FTP 服务器编码
|
||||
*/
|
||||
@@ -55,7 +62,7 @@ public abstract class AbsFtpThreadTask<ENTITY extends AbsNormalEntity, TASK_ENTI
|
||||
if (urlEntity.validAddr == null) {
|
||||
try {
|
||||
InetAddress[] ips = InetAddress.getAllByName(urlEntity.hostName);
|
||||
client = connect(new FTPClient(), ips, 0, Integer.parseInt(urlEntity.port));
|
||||
client = connect(newInstanceClient(urlEntity), ips, 0, Integer.parseInt(urlEntity.port));
|
||||
if (client == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -63,7 +70,7 @@ public abstract class AbsFtpThreadTask<ENTITY extends AbsNormalEntity, TASK_ENTI
|
||||
e.printStackTrace();
|
||||
}
|
||||
} else {
|
||||
client = new FTPClient();
|
||||
client = newInstanceClient(urlEntity);
|
||||
try {
|
||||
client.connect(urlEntity.validAddr, Integer.parseInt(urlEntity.port));
|
||||
} catch (IOException e) {
|
||||
@@ -77,6 +84,12 @@ public abstract class AbsFtpThreadTask<ENTITY extends AbsNormalEntity, TASK_ENTI
|
||||
}
|
||||
|
||||
try {
|
||||
if (urlEntity.isFtps) {
|
||||
int code = ((FTPSClient) client).execAUTH(
|
||||
TextUtils.isEmpty(urlEntity.SSLProtocol) ? ProtocolType.TLS : urlEntity.SSLProtocol);
|
||||
ALog.d(TAG, String.format("cod:%s,msg:%s", code, client.getReplyString()));
|
||||
}
|
||||
|
||||
if (urlEntity.needLogin) {
|
||||
if (TextUtils.isEmpty(urlEntity.account)) {
|
||||
client.login(urlEntity.user, urlEntity.password);
|
||||
@@ -93,9 +106,10 @@ public abstract class AbsFtpThreadTask<ENTITY extends AbsNormalEntity, TASK_ENTI
|
||||
}
|
||||
// 开启服务器对UTF-8的支持,如果服务器支持就用UTF-8编码
|
||||
charSet = "UTF-8";
|
||||
if (!TextUtils.isEmpty(mTaskEntity.getCharSet()) || !FTPReply.isPositiveCompletion(
|
||||
client.sendCommand("OPTS UTF8", "ON"))) {
|
||||
charSet = mTaskEntity.getCharSet();
|
||||
if (reply != FTPReply.COMMAND_IS_SUPERFLUOUS) {
|
||||
if (!TextUtils.isEmpty(mTaskEntity.getCharSet())) {
|
||||
charSet = mTaskEntity.getCharSet();
|
||||
}
|
||||
}
|
||||
client.setControlEncoding(charSet);
|
||||
client.setDataTimeout(mReadTimeOut);
|
||||
@@ -103,12 +117,34 @@ public abstract class AbsFtpThreadTask<ENTITY extends AbsNormalEntity, TASK_ENTI
|
||||
client.enterLocalPassiveMode();
|
||||
client.setFileType(FTP.BINARY_FILE_TYPE);
|
||||
client.setControlKeepAliveTimeout(5000);
|
||||
if (mTaskEntity.getUrlEntity().isFtps) {
|
||||
((FTPSClient) client).execPROT("P");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return client;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建FTP/FTPS客户端
|
||||
*/
|
||||
private FTPClient newInstanceClient(FtpUrlEntity urlEntity) {
|
||||
FTPClient temp;
|
||||
if (urlEntity.isFtps) {
|
||||
SSLContext sslContext =
|
||||
SSLContextUtil.getSSLContext(urlEntity.keyAlias, urlEntity.storePath);
|
||||
if (sslContext == null) {
|
||||
sslContext = SSLContextUtil.getDefaultSLLContext();
|
||||
}
|
||||
temp = new FTPSClient(true, sslContext);
|
||||
} else {
|
||||
temp = new FTPClient();
|
||||
}
|
||||
|
||||
return temp;
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接到ftp服务器
|
||||
*/
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* 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.ftp;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import com.arialyy.aria.core.FtpUrlEntity;
|
||||
import com.arialyy.aria.core.common.ProtocolType;
|
||||
import com.arialyy.aria.core.inf.AbsTarget;
|
||||
import com.arialyy.aria.core.inf.ITarget;
|
||||
|
||||
/**
|
||||
* FTP SSL/TSL配置
|
||||
*/
|
||||
public class FTPSSLConfig<TARGET extends AbsTarget> implements ITarget {
|
||||
private final String TAG = "FTPSSLConfig";
|
||||
private TARGET mTarget;
|
||||
private FtpUrlEntity mUrlEntity;
|
||||
|
||||
public FTPSSLConfig(TARGET target) {
|
||||
mTarget = target;
|
||||
mUrlEntity = mTarget.getTaskEntity().getUrlEntity();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置协议类型
|
||||
*
|
||||
* @param protocol {@link ProtocolType}
|
||||
*/
|
||||
public FTPSSLConfig setProtocol(@ProtocolType String protocol) {
|
||||
if (TextUtils.isEmpty(protocol)) {
|
||||
throw new NullPointerException("协议为空");
|
||||
}
|
||||
mUrlEntity.SSLProtocol = protocol;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置证书别名
|
||||
*
|
||||
* @param keyAlias 别名
|
||||
*/
|
||||
public FTPSSLConfig setAlias(String keyAlias) {
|
||||
if (TextUtils.isEmpty(keyAlias)) {
|
||||
throw new NullPointerException("别名为空");
|
||||
}
|
||||
mUrlEntity.keyAlias = keyAlias;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置证书密码
|
||||
*
|
||||
* @param storePass 私钥密码
|
||||
*/
|
||||
public FTPSSLConfig setStorePass(String storePass) {
|
||||
if (TextUtils.isEmpty(storePass)) {
|
||||
throw new NullPointerException("证书密码为空");
|
||||
}
|
||||
mUrlEntity.storePass = storePass;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置证书路径
|
||||
*
|
||||
* @param storePath 证书路径
|
||||
*/
|
||||
public FTPSSLConfig setStorePath(String storePath) {
|
||||
if (TextUtils.isEmpty(storePath)) {
|
||||
throw new NullPointerException("证书路径为空");
|
||||
}
|
||||
mUrlEntity.storePath = storePath;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override public void start() {
|
||||
mTarget.start();
|
||||
}
|
||||
|
||||
@Override public void stop() {
|
||||
mTarget.stop();
|
||||
}
|
||||
|
||||
@Override public void resume() {
|
||||
mTarget.resume();
|
||||
}
|
||||
|
||||
@Override public void cancel() {
|
||||
mTarget.cancel();
|
||||
}
|
||||
}
|
||||
@@ -13,35 +13,33 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.arialyy.aria.core.delegate;
|
||||
package com.arialyy.aria.core.common.ftp;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import com.arialyy.aria.core.inf.AbsEntity;
|
||||
import com.arialyy.aria.core.inf.AbsTaskEntity;
|
||||
import com.arialyy.aria.core.FtpUrlEntity;
|
||||
import com.arialyy.aria.core.inf.AbsTarget;
|
||||
import com.arialyy.aria.core.inf.IFtpTarget;
|
||||
import com.arialyy.aria.core.inf.ITarget;
|
||||
import com.arialyy.aria.util.ALog;
|
||||
|
||||
/**
|
||||
* Created by laoyuyu on 2018/3/9.
|
||||
* ftp 委托
|
||||
*/
|
||||
public class FtpDelegate<TARGET extends ITarget, ENTITY extends AbsEntity, TASK_ENTITY extends AbsTaskEntity<ENTITY>>
|
||||
implements IFtpTarget<TARGET> {
|
||||
public class FtpDelegate<TARGET extends AbsTarget> implements IFtpTarget<TARGET> {
|
||||
private static final String TAG = "FtpDelegate";
|
||||
private ENTITY mEntity;
|
||||
private TASK_ENTITY mTaskEntity;
|
||||
private FtpUrlEntity mUrlEntity;
|
||||
private TARGET mTarget;
|
||||
|
||||
public FtpDelegate(TARGET target, TASK_ENTITY taskEntity) {
|
||||
public FtpDelegate(TARGET target) {
|
||||
mTarget = target;
|
||||
mTaskEntity = taskEntity;
|
||||
mEntity = mTaskEntity.getEntity();
|
||||
mUrlEntity = target.getTaskEntity().getUrlEntity();
|
||||
}
|
||||
|
||||
@Override public TARGET charSet(String charSet) {
|
||||
if (TextUtils.isEmpty(charSet)) return mTarget;
|
||||
mTaskEntity.setCharSet(charSet);
|
||||
if (TextUtils.isEmpty(charSet)) {
|
||||
throw new NullPointerException("字符编码为空");
|
||||
}
|
||||
mTarget.getTaskEntity().setCharSet(charSet);
|
||||
return mTarget;
|
||||
}
|
||||
|
||||
@@ -57,10 +55,10 @@ public class FtpDelegate<TARGET extends ITarget, ENTITY extends AbsEntity, TASK_
|
||||
ALog.e(TAG, "密码不能为null");
|
||||
return mTarget;
|
||||
}
|
||||
mTaskEntity.getUrlEntity().needLogin = true;
|
||||
mTaskEntity.getUrlEntity().user = userName;
|
||||
mTaskEntity.getUrlEntity().password = password;
|
||||
mTaskEntity.getUrlEntity().account = account;
|
||||
mUrlEntity.needLogin = true;
|
||||
mUrlEntity.user = userName;
|
||||
mUrlEntity.password = password;
|
||||
mUrlEntity.account = account;
|
||||
return mTarget;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.arialyy.aria.core.common.ftp;
|
||||
|
||||
import com.arialyy.aria.util.ALog;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.Socket;
|
||||
import java.util.Locale;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSession;
|
||||
import javax.net.ssl.SSLSessionContext;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import org.apache.commons.net.ftp.FTPSClient;
|
||||
|
||||
public class SSLSessionReuseFTPSClient extends FTPSClient {
|
||||
|
||||
SSLSessionReuseFTPSClient(boolean b, SSLContext context) {
|
||||
super(b, context);
|
||||
}
|
||||
|
||||
// adapted from:
|
||||
// https://trac.cyberduck.io/browser/trunk/ftp/src/main/java/ch/cyberduck/core/ftp/FTPClient.java
|
||||
@Override
|
||||
protected void _prepareDataSocket_(final Socket socket) throws IOException {
|
||||
if (socket instanceof SSLSocket) {
|
||||
// Control socket is SSL
|
||||
final SSLSession session = ((SSLSocket) _socket_).getSession();
|
||||
if (session.isValid()) {
|
||||
final SSLSessionContext context = session.getSessionContext();
|
||||
try {
|
||||
//final Field sessionHostPortCache = context.getClass().getDeclaredField("sessionHostPortCache");
|
||||
final Field sessionHostPortCache =
|
||||
context.getClass().getDeclaredField("sessionsByHostAndPort");
|
||||
sessionHostPortCache.setAccessible(true);
|
||||
final Object cache = sessionHostPortCache.get(context);
|
||||
final Method method =
|
||||
cache.getClass().getDeclaredMethod("put", Object.class, Object.class);
|
||||
method.setAccessible(true);
|
||||
method.invoke(cache, String.format("%s:%s", socket.getInetAddress().getHostName(),
|
||||
String.valueOf(socket.getPort())).toLowerCase(Locale.ROOT), session);
|
||||
method.invoke(cache, String.format("%s:%s", socket.getInetAddress().getHostAddress(),
|
||||
String.valueOf(socket.getPort())).toLowerCase(Locale.ROOT), session);
|
||||
ALog.d("tag", "GGGG");
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new IOException(e);
|
||||
} catch (Exception e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
} else {
|
||||
throw new IOException("Invalid SSL Session");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,16 +13,16 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.arialyy.aria.core.delegate;
|
||||
package com.arialyy.aria.core.common.http;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.text.TextUtils;
|
||||
import com.arialyy.aria.core.common.RequestEnum;
|
||||
import com.arialyy.aria.core.inf.AbsEntity;
|
||||
import com.arialyy.aria.core.inf.AbsTarget;
|
||||
import com.arialyy.aria.core.inf.AbsTaskEntity;
|
||||
import com.arialyy.aria.core.inf.IHttpHeaderTarget;
|
||||
import com.arialyy.aria.core.inf.ITarget;
|
||||
import com.arialyy.aria.util.ALog;
|
||||
import java.net.Proxy;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@@ -31,18 +31,13 @@ import java.util.Set;
|
||||
* Created by laoyuyu on 2018/3/9.
|
||||
* HTTP header参数设置委托类
|
||||
*/
|
||||
public class HttpHeaderDelegate<TARGET extends ITarget, ENTITY extends AbsEntity, TASK_ENTITY extends AbsTaskEntity<ENTITY>>
|
||||
public class HttpHeaderDelegate<TARGET extends AbsTarget>
|
||||
implements IHttpHeaderTarget<TARGET> {
|
||||
private static final String TAG = "HttpHeaderDelegate";
|
||||
private ENTITY mEntity;
|
||||
private TASK_ENTITY mTaskEntity;
|
||||
private TARGET mTarget;
|
||||
|
||||
public HttpHeaderDelegate(TARGET target, TASK_ENTITY taskEntity) {
|
||||
public HttpHeaderDelegate(TARGET target) {
|
||||
mTarget = target;
|
||||
mTaskEntity = taskEntity;
|
||||
|
||||
mEntity = mTaskEntity.getEntity();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -61,7 +56,7 @@ public class HttpHeaderDelegate<TARGET extends ITarget, ENTITY extends AbsEntity
|
||||
ALog.w(TAG, "设置header失败,header对应的value不能为null");
|
||||
return mTarget;
|
||||
}
|
||||
addHeader(mTaskEntity, key, value);
|
||||
addHeader(mTarget.getTaskEntity(), key, value);
|
||||
return mTarget;
|
||||
}
|
||||
|
||||
@@ -77,7 +72,7 @@ public class HttpHeaderDelegate<TARGET extends ITarget, ENTITY extends AbsEntity
|
||||
ALog.w(TAG, "设置header失败,map没有header数据");
|
||||
return mTarget;
|
||||
}
|
||||
addHeaders(mTaskEntity, headers);
|
||||
addHeaders(mTarget.getTaskEntity(), headers);
|
||||
return mTarget;
|
||||
}
|
||||
|
||||
@@ -89,7 +84,12 @@ public class HttpHeaderDelegate<TARGET extends ITarget, ENTITY extends AbsEntity
|
||||
*/
|
||||
@Override
|
||||
public TARGET setRequestMode(RequestEnum requestEnum) {
|
||||
setRequestMode(mTaskEntity, requestEnum);
|
||||
setRequestMode(mTarget.getTaskEntity(), requestEnum);
|
||||
return mTarget;
|
||||
}
|
||||
|
||||
@Override public TARGET setUrlProxy(Proxy proxy) {
|
||||
mTarget.getTaskEntity().setProxy(proxy);
|
||||
return mTarget;
|
||||
}
|
||||
|
||||
@@ -19,13 +19,14 @@ import android.support.annotation.CheckResult;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.text.TextUtils;
|
||||
import com.arialyy.aria.core.common.RequestEnum;
|
||||
import com.arialyy.aria.core.delegate.HttpHeaderDelegate;
|
||||
import com.arialyy.aria.core.common.http.HttpHeaderDelegate;
|
||||
import com.arialyy.aria.core.inf.IHttpHeaderTarget;
|
||||
import com.arialyy.aria.core.manager.TEManager;
|
||||
import com.arialyy.aria.orm.DbEntity;
|
||||
import com.arialyy.aria.util.ALog;
|
||||
import com.arialyy.aria.util.CommonUtil;
|
||||
import java.io.File;
|
||||
import java.net.Proxy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@@ -38,8 +39,7 @@ import java.util.Set;
|
||||
*/
|
||||
public class DownloadGroupTarget extends BaseGroupTarget<DownloadGroupTarget> implements
|
||||
IHttpHeaderTarget<DownloadGroupTarget> {
|
||||
private HttpHeaderDelegate<DownloadGroupTarget, DownloadGroupEntity, DownloadGroupTaskEntity>
|
||||
mDelegate;
|
||||
private HttpHeaderDelegate<DownloadGroupTarget> mDelegate;
|
||||
/**
|
||||
* 子任务下载地址,
|
||||
*/
|
||||
@@ -71,7 +71,35 @@ public class DownloadGroupTarget extends BaseGroupTarget<DownloadGroupTarget> im
|
||||
if (mEntity != null) {
|
||||
mDirPathTemp = mEntity.getDirPath();
|
||||
}
|
||||
mDelegate = new HttpHeaderDelegate<>(this, mTaskEntity);
|
||||
mDelegate = new HttpHeaderDelegate<>(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新组合任务下载地址
|
||||
*
|
||||
* @param urls 新的组合任务下载地址列表
|
||||
*/
|
||||
@CheckResult
|
||||
public DownloadGroupTarget updateUrls(List<String> urls) {
|
||||
if (urls == null || urls.isEmpty()) {
|
||||
throw new NullPointerException("下载地址列表为空");
|
||||
}
|
||||
if (urls.size() != mUrls.size()) {
|
||||
throw new IllegalArgumentException("新下载地址数量和旧下载地址数量不一致");
|
||||
}
|
||||
mUrls.clear();
|
||||
mUrls.addAll(urls);
|
||||
mGroupName = CommonUtil.getMd5Code(urls);
|
||||
mEntity.setGroupName(mGroupName);
|
||||
mTaskEntity.setKey(mGroupName);
|
||||
mEntity.update();
|
||||
if (mEntity.getSubEntities() != null && !mEntity.getSubEntities().isEmpty()) {
|
||||
for (DownloadEntity de : mEntity.getSubEntities()) {
|
||||
de.setGroupName(mGroupName);
|
||||
de.update();
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -268,6 +296,7 @@ public class DownloadGroupTarget extends BaseGroupTarget<DownloadGroupTarget> im
|
||||
return true;
|
||||
}
|
||||
|
||||
@CheckResult
|
||||
@Override public DownloadGroupTarget addHeader(@NonNull String key, @NonNull String value) {
|
||||
for (DownloadTaskEntity subTask : mTaskEntity.getSubTaskEntities()) {
|
||||
mDelegate.addHeader(subTask, key, value);
|
||||
@@ -275,6 +304,7 @@ public class DownloadGroupTarget extends BaseGroupTarget<DownloadGroupTarget> im
|
||||
return mDelegate.addHeader(key, value);
|
||||
}
|
||||
|
||||
@CheckResult
|
||||
@Override public DownloadGroupTarget addHeaders(Map<String, String> headers) {
|
||||
for (DownloadTaskEntity subTask : mTaskEntity.getSubTaskEntities()) {
|
||||
mDelegate.addHeaders(subTask, headers);
|
||||
@@ -282,10 +312,16 @@ public class DownloadGroupTarget extends BaseGroupTarget<DownloadGroupTarget> im
|
||||
return mDelegate.addHeaders(headers);
|
||||
}
|
||||
|
||||
@CheckResult
|
||||
@Override public DownloadGroupTarget setRequestMode(RequestEnum requestEnum) {
|
||||
for (DownloadTaskEntity subTask : mTaskEntity.getSubTaskEntities()) {
|
||||
subTask.setRequestEnum(requestEnum);
|
||||
}
|
||||
return mDelegate.setRequestMode(requestEnum);
|
||||
}
|
||||
|
||||
@CheckResult
|
||||
@Override public DownloadGroupTarget setUrlProxy(Proxy proxy) {
|
||||
return mDelegate.setUrlProxy(proxy);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ package com.arialyy.aria.core.download;
|
||||
import android.support.annotation.CheckResult;
|
||||
import android.support.annotation.NonNull;
|
||||
import com.arialyy.aria.core.common.RequestEnum;
|
||||
import com.arialyy.aria.core.delegate.HttpHeaderDelegate;
|
||||
import com.arialyy.aria.core.common.http.HttpHeaderDelegate;
|
||||
import com.arialyy.aria.core.inf.IHttpHeaderTarget;
|
||||
import java.net.Proxy;
|
||||
import java.util.Map;
|
||||
@@ -29,7 +29,7 @@ import java.util.Map;
|
||||
*/
|
||||
public class DownloadTarget extends BaseNormalTarget<DownloadTarget>
|
||||
implements IHttpHeaderTarget<DownloadTarget> {
|
||||
private HttpHeaderDelegate<DownloadTarget, DownloadEntity, DownloadTaskEntity> mDelegate;
|
||||
private HttpHeaderDelegate<DownloadTarget> mDelegate;
|
||||
|
||||
|
||||
DownloadTarget(DownloadEntity entity, String targetName) {
|
||||
@@ -38,7 +38,7 @@ public class DownloadTarget extends BaseNormalTarget<DownloadTarget>
|
||||
|
||||
DownloadTarget(String url, String targetName) {
|
||||
initTarget(url, targetName);
|
||||
mDelegate = new HttpHeaderDelegate<>(this, mTaskEntity);
|
||||
mDelegate = new HttpHeaderDelegate<>(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -53,17 +53,6 @@ public class DownloadTarget extends BaseNormalTarget<DownloadTarget>
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置URL的代理
|
||||
*
|
||||
* @param proxy {@link Proxy}
|
||||
*/
|
||||
@CheckResult
|
||||
public DownloadTarget setUrlProxy(Proxy proxy) {
|
||||
mTaskEntity.setProxy(proxy);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置文件存储路径
|
||||
* 该api后续版本会删除
|
||||
@@ -100,6 +89,17 @@ public class DownloadTarget extends BaseNormalTarget<DownloadTarget>
|
||||
return HTTP;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置URL的代理
|
||||
*
|
||||
* @param proxy {@link Proxy}
|
||||
*/
|
||||
@CheckResult
|
||||
@Override public DownloadTarget setUrlProxy(Proxy proxy) {
|
||||
return mDelegate.setUrlProxy(proxy);
|
||||
}
|
||||
|
||||
@CheckResult
|
||||
@Override public DownloadTarget addHeader(@NonNull String key, @NonNull String value) {
|
||||
return mDelegate.addHeader(key, value);
|
||||
|
||||
@@ -17,7 +17,7 @@ package com.arialyy.aria.core.download;
|
||||
|
||||
import android.support.annotation.CheckResult;
|
||||
import android.text.TextUtils;
|
||||
import com.arialyy.aria.core.delegate.FtpDelegate;
|
||||
import com.arialyy.aria.core.common.ftp.FtpDelegate;
|
||||
import com.arialyy.aria.core.inf.AbsTaskEntity;
|
||||
import com.arialyy.aria.core.inf.IFtpTarget;
|
||||
import com.arialyy.aria.core.manager.TEManager;
|
||||
@@ -29,7 +29,7 @@ import com.arialyy.aria.util.ALog;
|
||||
*/
|
||||
public class FtpDirDownloadTarget extends BaseGroupTarget<FtpDirDownloadTarget>
|
||||
implements IFtpTarget<FtpDirDownloadTarget> {
|
||||
private FtpDelegate<FtpDirDownloadTarget, DownloadGroupEntity, DownloadGroupTaskEntity> mDelegate;
|
||||
private FtpDelegate<FtpDirDownloadTarget> mDelegate;
|
||||
|
||||
FtpDirDownloadTarget(String url, String targetName) {
|
||||
mTargetName = targetName;
|
||||
@@ -44,7 +44,7 @@ public class FtpDirDownloadTarget extends BaseGroupTarget<FtpDirDownloadTarget>
|
||||
if (mEntity != null) {
|
||||
mDirPathTemp = mEntity.getDirPath();
|
||||
}
|
||||
mDelegate = new FtpDelegate<>(this, mTaskEntity);
|
||||
mDelegate = new FtpDelegate<>(this);
|
||||
}
|
||||
|
||||
@Override protected int getTargetType() {
|
||||
@@ -90,14 +90,17 @@ public class FtpDirDownloadTarget extends BaseGroupTarget<FtpDirDownloadTarget>
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@CheckResult
|
||||
@Override public FtpDirDownloadTarget charSet(String charSet) {
|
||||
return mDelegate.charSet(charSet);
|
||||
}
|
||||
|
||||
@CheckResult
|
||||
@Override public FtpDirDownloadTarget login(String userName, String password) {
|
||||
return mDelegate.login(userName, password);
|
||||
}
|
||||
|
||||
@CheckResult
|
||||
@Override public FtpDirDownloadTarget login(String userName, String password, String account) {
|
||||
return mDelegate.login(userName, password, account);
|
||||
|
||||
@@ -17,7 +17,8 @@ package com.arialyy.aria.core.download;
|
||||
|
||||
import android.support.annotation.CheckResult;
|
||||
import android.support.annotation.NonNull;
|
||||
import com.arialyy.aria.core.delegate.FtpDelegate;
|
||||
import com.arialyy.aria.core.common.ftp.FTPSSLConfig;
|
||||
import com.arialyy.aria.core.common.ftp.FtpDelegate;
|
||||
import com.arialyy.aria.core.inf.AbsTaskEntity;
|
||||
import com.arialyy.aria.core.inf.IFtpTarget;
|
||||
import com.arialyy.aria.util.CommonUtil;
|
||||
@@ -28,7 +29,7 @@ import com.arialyy.aria.util.CommonUtil;
|
||||
*/
|
||||
public class FtpDownloadTarget extends BaseNormalTarget<FtpDownloadTarget>
|
||||
implements IFtpTarget<FtpDownloadTarget> {
|
||||
private FtpDelegate<FtpDownloadTarget, DownloadEntity, DownloadTaskEntity> mDelegate;
|
||||
private FtpDelegate<FtpDownloadTarget> mDelegate;
|
||||
|
||||
public FtpDownloadTarget(DownloadEntity entity, String targetName) {
|
||||
this(entity.getUrl(), targetName);
|
||||
@@ -45,7 +46,18 @@ public class FtpDownloadTarget extends BaseNormalTarget<FtpDownloadTarget>
|
||||
mTaskEntity.setUrlEntity(CommonUtil.getFtpUrlInfo(url));
|
||||
mTaskEntity.setRequestType(AbsTaskEntity.D_FTP);
|
||||
|
||||
mDelegate = new FtpDelegate<>(this, mTaskEntity);
|
||||
mDelegate = new FtpDelegate<>(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否是FTPS协议
|
||||
* 如果是FTPS协议,需要使用{@link FTPSSLConfig#setPrivateKeyPath(String)}、{@link FTPSSLConfig#setCertPath(String)}
|
||||
* 设置证书信息
|
||||
*/
|
||||
@CheckResult
|
||||
public FTPSSLConfig<FtpDownloadTarget> asFtps() {
|
||||
mTaskEntity.getUrlEntity().isFtps = true;
|
||||
return new FTPSSLConfig<>(this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package com.arialyy.aria.core.download.downloader;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import com.arialyy.aria.core.AriaManager;
|
||||
import com.arialyy.aria.core.common.RequestEnum;
|
||||
import com.arialyy.aria.core.download.DownloadTaskEntity;
|
||||
import com.arialyy.aria.core.inf.AbsTaskEntity;
|
||||
@@ -26,8 +27,6 @@ import java.net.HttpURLConnection;
|
||||
import java.net.ProtocolException;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import java.util.zip.InflaterInputStream;
|
||||
@@ -75,9 +74,11 @@ class ConnectionHelp {
|
||||
urlConn = url.openConnection();
|
||||
}
|
||||
if (urlConn instanceof HttpsURLConnection) {
|
||||
AriaManager manager = AriaManager.getInstance(AriaManager.APP);
|
||||
conn = (HttpsURLConnection) urlConn;
|
||||
SSLContext sslContext =
|
||||
SSLContextUtil.getSSLContext(SSLContextUtil.CA_ALIAS, SSLContextUtil.CA_PATH);
|
||||
SSLContextUtil.getSSLContextFromAssets(manager.getDownloadConfig().getCaName(),
|
||||
manager.getDownloadConfig().getCaPath());
|
||||
if (sslContext == null) {
|
||||
sslContext = SSLContextUtil.getDefaultSLLContext();
|
||||
}
|
||||
@@ -126,24 +127,24 @@ class ConnectionHelp {
|
||||
if (conn.getRequestProperty("Accept") == null) {
|
||||
StringBuilder accept = new StringBuilder();
|
||||
//accept
|
||||
//.append("image/gif, ")
|
||||
//.append("image/jpeg, ")
|
||||
//.append("image/pjpeg, ")
|
||||
//.append("image/webp, ")
|
||||
//.append("image/apng, ")
|
||||
//.append("application/xml, ")
|
||||
//.append("application/xaml+xml, ")
|
||||
//.append("application/xhtml+xml, ")
|
||||
//.append("application/x-shockwave-flash, ")
|
||||
//.append("application/x-ms-xbap, ")
|
||||
//.append("application/x-ms-application, ")
|
||||
//.append("application/msword, ")
|
||||
//.append("application/vnd.ms-excel, ")
|
||||
//.append("application/vnd.ms-xpsdocument, ")
|
||||
//.append("application/vnd.ms-powerpoint, ")
|
||||
//.append("text/plain, ")
|
||||
//.append("text/html, ")
|
||||
//.append("*/*");
|
||||
//.append("image/gif, ")
|
||||
//.append("image/jpeg, ")
|
||||
//.append("image/pjpeg, ")
|
||||
//.append("image/webp, ")
|
||||
//.append("image/apng, ")
|
||||
//.append("application/xml, ")
|
||||
//.append("application/xaml+xml, ")
|
||||
//.append("application/xhtml+xml, ")
|
||||
//.append("application/x-shockwave-flash, ")
|
||||
//.append("application/x-ms-xbap, ")
|
||||
//.append("application/x-ms-application, ")
|
||||
//.append("application/msword, ")
|
||||
//.append("application/vnd.ms-excel, ")
|
||||
//.append("application/vnd.ms-xpsdocument, ")
|
||||
//.append("application/vnd.ms-powerpoint, ")
|
||||
//.append("text/plain, ")
|
||||
//.append("text/html, ")
|
||||
//.append("*/*");
|
||||
conn.setRequestProperty("Accept", "*/*");
|
||||
}
|
||||
//302获取重定向地址
|
||||
|
||||
@@ -16,9 +16,9 @@
|
||||
package com.arialyy.aria.core.download.downloader;
|
||||
|
||||
import com.arialyy.aria.core.FtpUrlEntity;
|
||||
import com.arialyy.aria.core.common.AbsFtpInfoThread;
|
||||
import com.arialyy.aria.core.common.CompleteInfo;
|
||||
import com.arialyy.aria.core.common.OnFileInfoCallback;
|
||||
import com.arialyy.aria.core.common.ftp.AbsFtpInfoThread;
|
||||
import com.arialyy.aria.core.download.DownloadEntity;
|
||||
import com.arialyy.aria.core.download.DownloadGroupEntity;
|
||||
import com.arialyy.aria.core.download.DownloadGroupTaskEntity;
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
package com.arialyy.aria.core.download.downloader;
|
||||
|
||||
import com.arialyy.aria.core.common.AbsFtpInfoThread;
|
||||
import com.arialyy.aria.core.common.ftp.AbsFtpInfoThread;
|
||||
import com.arialyy.aria.core.common.CompleteInfo;
|
||||
import com.arialyy.aria.core.common.OnFileInfoCallback;
|
||||
import com.arialyy.aria.core.download.DownloadEntity;
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
package com.arialyy.aria.core.download.downloader;
|
||||
|
||||
import com.arialyy.aria.core.AriaManager;
|
||||
import com.arialyy.aria.core.common.AbsFtpThreadTask;
|
||||
import com.arialyy.aria.core.common.ftp.AbsFtpThreadTask;
|
||||
import com.arialyy.aria.core.common.StateConstance;
|
||||
import com.arialyy.aria.core.common.SubThreadConfig;
|
||||
import com.arialyy.aria.core.download.DownloadEntity;
|
||||
|
||||
@@ -37,7 +37,7 @@ import java.util.List;
|
||||
* Created by AriaL on 2017/7/3.
|
||||
*/
|
||||
public abstract class AbsTarget<TARGET extends AbsTarget, ENTITY extends AbsEntity, TASK_ENTITY extends AbsTaskEntity>
|
||||
implements ITarget<TARGET> {
|
||||
implements ITarget {
|
||||
protected String TAG;
|
||||
protected ENTITY mEntity;
|
||||
protected TASK_ENTITY mTaskEntity;
|
||||
@@ -82,12 +82,18 @@ public abstract class AbsTarget<TARGET extends AbsTarget, ENTITY extends AbsEnti
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取任务实体
|
||||
*/
|
||||
public TASK_ENTITY getTaskEntity() {
|
||||
return mTaskEntity;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取任务进度,如果任务存在,则返回当前进度
|
||||
*
|
||||
* @return 该任务进度
|
||||
*/
|
||||
@Override
|
||||
public long getCurrentProgress() {
|
||||
return mEntity == null ? -1 : mEntity.getCurrentProgress();
|
||||
}
|
||||
@@ -97,7 +103,7 @@ public abstract class AbsTarget<TARGET extends AbsTarget, ENTITY extends AbsEnti
|
||||
*
|
||||
* @return 文件大小
|
||||
*/
|
||||
@Override public long getSize() {
|
||||
public long getSize() {
|
||||
return mEntity == null ? 0 : mEntity.getFileSize();
|
||||
}
|
||||
|
||||
@@ -106,7 +112,7 @@ public abstract class AbsTarget<TARGET extends AbsTarget, ENTITY extends AbsEnti
|
||||
*
|
||||
* @return 文件大小{@code xxx mb}
|
||||
*/
|
||||
@Override public String getConvertSize() {
|
||||
public String getConvertSize() {
|
||||
return mEntity == null ? "0b" : CommonUtil.formatFileSize(mEntity.getFileSize());
|
||||
}
|
||||
|
||||
@@ -142,7 +148,6 @@ public abstract class AbsTarget<TARGET extends AbsTarget, ENTITY extends AbsEnti
|
||||
*
|
||||
* @return {@link IEntity}
|
||||
*/
|
||||
@Override
|
||||
public int getTaskState() {
|
||||
return mEntity.getState();
|
||||
}
|
||||
@@ -152,7 +157,7 @@ public abstract class AbsTarget<TARGET extends AbsTarget, ENTITY extends AbsEnti
|
||||
*
|
||||
* @return 返回任务进度
|
||||
*/
|
||||
@Override public int getPercent() {
|
||||
public int getPercent() {
|
||||
if (mEntity == null) {
|
||||
ALog.e("AbsTarget", "下载管理器中没有该任务");
|
||||
return 0;
|
||||
@@ -191,6 +196,20 @@ public abstract class AbsTarget<TARGET extends AbsTarget, ENTITY extends AbsEnti
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务是否在执行
|
||||
*
|
||||
* @return {@code true} 任务正在执行
|
||||
*/
|
||||
public abstract boolean isRunning();
|
||||
|
||||
/**
|
||||
* 任务是否存在
|
||||
*
|
||||
* @return {@code true} 任务存在
|
||||
*/
|
||||
public abstract boolean taskExists();
|
||||
|
||||
/**
|
||||
* 开始任务
|
||||
*/
|
||||
|
||||
@@ -19,6 +19,7 @@ import android.support.annotation.CheckResult;
|
||||
import android.support.annotation.NonNull;
|
||||
import com.arialyy.aria.core.common.RequestEnum;
|
||||
import com.arialyy.aria.core.download.DownloadEntity;
|
||||
import java.net.Proxy;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@@ -53,4 +54,7 @@ public interface IHttpHeaderTarget<TARGET extends ITarget> {
|
||||
*/
|
||||
@CheckResult
|
||||
TARGET setRequestMode(RequestEnum requestEnum);
|
||||
|
||||
@CheckResult
|
||||
TARGET setUrlProxy(Proxy proxy);
|
||||
}
|
||||
|
||||
@@ -18,48 +18,7 @@ package com.arialyy.aria.core.inf;
|
||||
/**
|
||||
* Created by AriaL on 2017/6/29.
|
||||
*/
|
||||
public interface ITarget<TARGET extends ITarget> {
|
||||
/**
|
||||
* 获取任务状态
|
||||
*
|
||||
* @return {@link IEntity}
|
||||
*/
|
||||
int getTaskState();
|
||||
|
||||
/**
|
||||
* 任务是否在执行
|
||||
*
|
||||
* @return {@code true} 任务正在执行
|
||||
*/
|
||||
boolean isRunning();
|
||||
|
||||
/**
|
||||
* 任务是否存在
|
||||
*
|
||||
* @return {@code true} 任务存在
|
||||
*/
|
||||
boolean taskExists();
|
||||
|
||||
/**
|
||||
* 任务文件大小
|
||||
*/
|
||||
long getSize();
|
||||
|
||||
/**
|
||||
* 转换后的大小
|
||||
*/
|
||||
String getConvertSize();
|
||||
|
||||
/**
|
||||
* 获取任务进度百分比
|
||||
*/
|
||||
int getPercent();
|
||||
|
||||
/**
|
||||
* 获取任务进度,如果任务存在,则返回当前进度
|
||||
*/
|
||||
long getCurrentProgress();
|
||||
|
||||
public interface ITarget {
|
||||
/**
|
||||
* 开始下载
|
||||
*/
|
||||
|
||||
@@ -18,7 +18,7 @@ package com.arialyy.aria.core.upload;
|
||||
import android.support.annotation.CheckResult;
|
||||
import com.arialyy.aria.core.AriaManager;
|
||||
import com.arialyy.aria.core.command.normal.NormalCmdFactory;
|
||||
import com.arialyy.aria.core.delegate.FtpDelegate;
|
||||
import com.arialyy.aria.core.common.ftp.FtpDelegate;
|
||||
import com.arialyy.aria.core.inf.AbsTaskEntity;
|
||||
import com.arialyy.aria.core.inf.IFtpTarget;
|
||||
import com.arialyy.aria.util.CommonUtil;
|
||||
@@ -29,7 +29,7 @@ import com.arialyy.aria.util.CommonUtil;
|
||||
*/
|
||||
public class FtpUploadTarget extends BaseNormalTarget<FtpUploadTarget>
|
||||
implements IFtpTarget<FtpUploadTarget> {
|
||||
private FtpDelegate<FtpUploadTarget, UploadEntity, UploadTaskEntity> mDelegate;
|
||||
private FtpDelegate<FtpUploadTarget> mDelegate;
|
||||
|
||||
private String mAccount, mUser, mPw;
|
||||
private boolean needLogin = false;
|
||||
@@ -42,7 +42,7 @@ public class FtpUploadTarget extends BaseNormalTarget<FtpUploadTarget>
|
||||
private void initTask(String filePath) {
|
||||
initTarget(filePath);
|
||||
mTaskEntity.setRequestType(AbsTaskEntity.U_FTP);
|
||||
mDelegate = new FtpDelegate<>(this, mTaskEntity);
|
||||
mDelegate = new FtpDelegate<>(this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,9 +18,10 @@ package com.arialyy.aria.core.upload;
|
||||
import android.support.annotation.CheckResult;
|
||||
import android.support.annotation.NonNull;
|
||||
import com.arialyy.aria.core.common.RequestEnum;
|
||||
import com.arialyy.aria.core.delegate.HttpHeaderDelegate;
|
||||
import com.arialyy.aria.core.common.http.HttpHeaderDelegate;
|
||||
import com.arialyy.aria.core.inf.AbsTaskEntity;
|
||||
import com.arialyy.aria.core.inf.IHttpHeaderTarget;
|
||||
import java.net.Proxy;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@@ -29,7 +30,7 @@ import java.util.Map;
|
||||
*/
|
||||
public class UploadTarget extends BaseNormalTarget<UploadTarget>
|
||||
implements IHttpHeaderTarget<UploadTarget> {
|
||||
private HttpHeaderDelegate<UploadTarget, UploadEntity, UploadTaskEntity> mDelegate;
|
||||
private HttpHeaderDelegate<UploadTarget> mDelegate;
|
||||
|
||||
UploadTarget(String filePath, String targetName) {
|
||||
this.mTargetName = targetName;
|
||||
@@ -42,7 +43,7 @@ public class UploadTarget extends BaseNormalTarget<UploadTarget>
|
||||
//http暂时不支持断点上传
|
||||
mTaskEntity.setSupportBP(false);
|
||||
mTaskEntity.setRequestType(AbsTaskEntity.U_HTTP);
|
||||
mDelegate = new HttpHeaderDelegate<>(this, mTaskEntity);
|
||||
mDelegate = new HttpHeaderDelegate<>(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -90,4 +91,8 @@ public class UploadTarget extends BaseNormalTarget<UploadTarget>
|
||||
@Override public UploadTarget setRequestMode(RequestEnum requestEnum) {
|
||||
return mDelegate.setRequestMode(requestEnum);
|
||||
}
|
||||
|
||||
@Override public UploadTarget setUrlProxy(Proxy proxy) {
|
||||
return mDelegate.setUrlProxy(proxy);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
package com.arialyy.aria.core.upload.uploader;
|
||||
|
||||
import com.arialyy.aria.core.common.AbsFtpInfoThread;
|
||||
import com.arialyy.aria.core.common.ftp.AbsFtpInfoThread;
|
||||
import com.arialyy.aria.core.common.CompleteInfo;
|
||||
import com.arialyy.aria.core.common.OnFileInfoCallback;
|
||||
import com.arialyy.aria.core.common.TaskRecord;
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
package com.arialyy.aria.core.upload.uploader;
|
||||
|
||||
import com.arialyy.aria.core.AriaManager;
|
||||
import com.arialyy.aria.core.common.AbsFtpThreadTask;
|
||||
import com.arialyy.aria.core.common.ftp.AbsFtpThreadTask;
|
||||
import com.arialyy.aria.core.common.StateConstance;
|
||||
import com.arialyy.aria.core.common.SubThreadConfig;
|
||||
import com.arialyy.aria.core.inf.IEventListener;
|
||||
|
||||
@@ -35,7 +35,7 @@ class DBConfig {
|
||||
/*adb pull /mnt/sdcard/Android/data/com.arialyy.simple/files/DB/AriaLyyDb d:/db*/
|
||||
static Map<String, Class> mapping = new HashMap<>();
|
||||
static String DB_NAME;
|
||||
static int VERSION = 40;
|
||||
static int VERSION = 41;
|
||||
|
||||
/**
|
||||
* 是否将数据库保存在Sd卡,{@code true} 是
|
||||
|
||||
@@ -17,6 +17,7 @@ package com.arialyy.aria.util;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import com.arialyy.aria.core.AriaManager;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.KeyManagementException;
|
||||
@@ -42,30 +43,46 @@ import javax.net.ssl.X509TrustManager;
|
||||
* SSL证书工具
|
||||
*/
|
||||
public class SSLContextUtil {
|
||||
private static final String TAG = "SSLContextUtil";
|
||||
|
||||
public static String CA_PATH, CA_ALIAS;
|
||||
/**
|
||||
* 从assets目录下加载证书
|
||||
*
|
||||
* @param caAlias CA证书别名
|
||||
* @param caPath 保存在assets目录下的CA证书完整路径
|
||||
*/
|
||||
public static SSLContext getSSLContextFromAssets(String caAlias, String caPath) {
|
||||
try {
|
||||
InputStream caInput = AriaManager.APP.getAssets().open(caPath);
|
||||
Certificate ca = loadCert(caInput);
|
||||
return createContext(caAlias, ca);
|
||||
} catch (IOException | CertificateException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 颁发服务器证书的 CA 未知
|
||||
*
|
||||
* @param caAlias CA证书别名
|
||||
* @param caPath 保存在assets目录下的CA证书完整路径
|
||||
* @param caPath CA证书路径
|
||||
*/
|
||||
public static SSLContext getSSLContext(String caAlias, String caPath) {
|
||||
if (TextUtils.isEmpty(caAlias) || TextUtils.isEmpty(caPath)) {
|
||||
return null;
|
||||
}
|
||||
// Load CAs from an InputStream
|
||||
// (could be from a resource or ByteArrayInputStream or ...)
|
||||
CertificateFactory cf = null;
|
||||
try {
|
||||
cf = CertificateFactory.getInstance("X.509");
|
||||
InputStream caInput = AriaManager.APP.getAssets().open(caPath);
|
||||
Certificate ca;
|
||||
ca = cf.generateCertificate(caInput);
|
||||
System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN());
|
||||
Certificate ca = loadCert(new FileInputStream(caPath));
|
||||
return createContext(caAlias, ca);
|
||||
} catch (CertificateException | IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Create a KeyStore containing our trusted CAs
|
||||
private static SSLContext createContext(String caAlias, Certificate ca) {
|
||||
try {
|
||||
String keyStoreType = KeyStore.getDefaultType();
|
||||
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
|
||||
keyStore.load(null, null);
|
||||
@@ -89,6 +106,20 @@ public class SSLContextUtil {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载CA证书
|
||||
*
|
||||
* @param is CA证书文件流
|
||||
* @throws CertificateException
|
||||
*/
|
||||
private static Certificate loadCert(InputStream is) throws CertificateException, IOException {
|
||||
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
||||
Certificate ca = cf.generateCertificate(is);
|
||||
ALog.d(TAG, String.format("ca【%s】", ((X509Certificate) ca).getSubjectDN()));
|
||||
is.close();
|
||||
return ca;
|
||||
}
|
||||
|
||||
/**
|
||||
* 服务器证书不是由 CA 签署的,而是自签署时,获取默认的SSL
|
||||
*/
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
## 开发日志
|
||||
+ v_3.4.8
|
||||
- 组合任务新增`updateUrls(List<String>)`用于修改组合子任务的url,[see](https://aria.laoyuyu.me/aria_doc/api/task_handle.html#%E6%9B%B4%E6%96%B0%E4%BB%BB%E5%8A%A1url)
|
||||
- 出于安全考虑,FTP数据库去掉密码的保存
|
||||
- 增加FTPS支持 [see]()
|
||||
+ v_3.4.7
|
||||
- 修复分块任务异常操作导致的问题
|
||||
+ v_3.4.6
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
<!--android:name=".test.TestGroupActivity"-->
|
||||
<!--android:name=".MainActivity"-->
|
||||
<!--android:name="com.arialyy.simple.test.AnyRunActivity"-->
|
||||
<!--android:name=".test.AnyRunActivity"-->
|
||||
<!--android:name=".download.group.DownloadGroupActivity"-->
|
||||
<activity
|
||||
android:name=".download.group.DownloadGroupActivity"
|
||||
android:name=".test.AnyRunActivity"
|
||||
android:label="@string/app_name">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
|
||||
@@ -32,6 +32,7 @@ import com.arialyy.simple.R;
|
||||
import com.arialyy.simple.base.BaseActivity;
|
||||
import com.arialyy.simple.databinding.ActivityDownloadGroupBinding;
|
||||
import com.arialyy.simple.widget.SubStateLinearLayout;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -80,6 +81,8 @@ public class DownloadGroupActivity extends BaseActivity<ActivityDownloadGroupBin
|
||||
public void onClick(View view) {
|
||||
switch (view.getId()) {
|
||||
case R.id.start:
|
||||
List<String> temp = new ArrayList<>();
|
||||
temp.add("https://d.pcs.baidu.com/file/130335545f3f4d9cc38afe709c19af5a?fid=1411168371-250528-1010657263806840&dstime=1531134714&rt=sh&sign=FDtAERVY-DCb740ccc5511e5e8fedcff06b081203-h8KgJ6gl4oY9UR6NqvwJsT4nVSM%3D&expires=8h&chkv=1&chkbd=0&chkpc=et&dp-logid=4401996296756616039&dp-callid=0&r=279987343");
|
||||
Aria.download(this)
|
||||
.loadGroup(mUrls)
|
||||
.setDirPath(
|
||||
@@ -88,7 +91,7 @@ public class DownloadGroupActivity extends BaseActivity<ActivityDownloadGroupBin
|
||||
.setGroupAlias("任务组测试")
|
||||
//.setSubFileName(getModule(GroupModule.class).getSubName2())
|
||||
.setSubFileName(getModule(GroupModule.class).getSubName())
|
||||
//.setFileSize(32895492)
|
||||
.updateUrls(temp)
|
||||
.start();
|
||||
break;
|
||||
case R.id.stop:
|
||||
|
||||
@@ -39,8 +39,9 @@ public class GroupModule extends BaseModule {
|
||||
|
||||
List<String> getUrls1() {
|
||||
List<String> urls = new ArrayList<>();
|
||||
String[] str = getContext().getResources().getStringArray(R.array.group_urls);
|
||||
Collections.addAll(urls, str);
|
||||
//String[] str = getContext().getResources().getStringArray(R.array.group_urls);
|
||||
//Collections.addAll(urls, str);
|
||||
urls.add("https://d.pcs.baidu.com/file/130335545f3f4d9cc38afe709c19af5a?fid=1411168371-250528-1010657263806840&dstime=1531134607&rt=sh&sign=FDtAERVY-DCb740ccc5511e5e8fedcff06b081203-sNCujT7lC42aMcfiHcgqAzYHuw4%3D&expires=8h&chkv=1&chkbd=0&chkpc=et&dp-logid=4401967667009194668&dp-callid=0&r=540192514");
|
||||
return urls;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,8 @@ public class AnyRunActivity extends BaseActivity<ActivityTestBinding> {
|
||||
//private final String URL = "ftp://192.168.29.140:21/download/AriaPrj.rar";
|
||||
//String URL = "https://dl.genymotion.com/releases/genymotion-2.12.1/genymotion-2.12.1-vbox.exe";
|
||||
//String URL = "ftp://192.168.29.140:21/download/SDK_Demo-release.apk";
|
||||
String URL = "http://d.quanscreen.com/k/down/resourceDownLoad?resourceId=1994&clientId=A000011106034058176";
|
||||
String URL = "ftps://192.168.29.140:990/download/SDK_Demo-release.apk";
|
||||
//String URL = "http://d.quanscreen.com/k/down/resourceDownLoad?resourceId=1994&clientId=A000011106034058176";
|
||||
//String URL = "ftp://z:z@dygod18.com:21211/[电影天堂www.dy2018.com]猩球崛起3:终极之战BD国英双语中英双字.mkv";
|
||||
//private String URL = "https://www.bilibili.com/bangumi/play/ep77693";
|
||||
//private String URL = "http://cn-hbsjz-cmcc-v-03.acgvideo.com/upgcxcode/63/82/5108263/5108263-1-80.flv?expires=1530178500&platform=pc&ssig=vr7gLl0duyqWqSMnIpzaDA&oi=3746029570&nfa=BpfiWF+i4mNW8KzjZFHzBQ==&dynamic=1&hfa=2030547937&hfb=Yjk5ZmZjM2M1YzY4ZjAwYTMzMTIzYmIyNWY4ODJkNWI=&trid=3476be01a9254115b15f8cc7198600fe&nfc=1";
|
||||
@@ -50,8 +51,8 @@ public class AnyRunActivity extends BaseActivity<ActivityTestBinding> {
|
||||
public void onClick(View view) {
|
||||
switch (view.getId()) {
|
||||
case R.id.start:
|
||||
module.start(URL);
|
||||
//module.startFtp(URL);
|
||||
//module.start(URL);
|
||||
module.startFtp(URL);
|
||||
break;
|
||||
case R.id.stop:
|
||||
module.stop(URL);
|
||||
|
||||
@@ -10,7 +10,6 @@ import com.arialyy.aria.core.download.DownloadTask;
|
||||
import com.arialyy.aria.util.CommonUtil;
|
||||
import com.arialyy.frame.util.show.L;
|
||||
import java.io.File;
|
||||
import java.net.Proxy;
|
||||
|
||||
/**
|
||||
* Created by laoyuyu on 2018/4/13.
|
||||
@@ -79,7 +78,11 @@ public class AnyRunnModule {
|
||||
Aria.download(this)
|
||||
.loadFtp(url)
|
||||
.login("lao", "123456")
|
||||
.setFilePath(Environment.getExternalStorageDirectory().getPath() + "/")
|
||||
.setFilePath(Environment.getExternalStorageDirectory().getPath() + "/Download/")
|
||||
.asFtps()
|
||||
.setStorePath("/mnt/sdcard/Download/server.crt")
|
||||
.setAlias("www.laoyuyu.me")
|
||||
.setStorePass("123456")
|
||||
.start();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user