sftp上传实现

pull/617/head 3.8.5
laoyuyu 5 years ago
parent 50b265f22a
commit d217ebcb25
  1. 51
      Aria/src/main/java/com/arialyy/aria/core/common/FtpOption.java
  2. 135
      Aria/src/main/java/com/arialyy/aria/core/common/SFtpOption.java
  3. 3
      Aria/src/main/java/com/arialyy/aria/core/download/target/FtpBuilderTarget.java
  4. 3
      Aria/src/main/java/com/arialyy/aria/core/download/target/FtpNormalTarget.java
  5. 15
      Aria/src/main/java/com/arialyy/aria/core/upload/target/FtpBuilderTarget.java
  6. 15
      Aria/src/main/java/com/arialyy/aria/core/upload/target/FtpNormalTarget.java
  7. 5
      DEV_LOG.md
  8. 95
      FtpComponent/src/main/java/com/arialyy/aria/ftp/download/FtpDLoader.java
  9. 6
      FtpComponent/src/main/java/com/arialyy/aria/ftp/download/FtpDRecordHandler.java
  10. 8
      FtpComponent/src/main/java/com/arialyy/aria/ftp/upload/FtpULoader.java
  11. 3
      FtpComponent/src/main/java/com/arialyy/aria/ftp/upload/FtpULoaderUtil.java
  12. 2
      FtpComponent/src/main/java/com/arialyy/aria/ftp/upload/FtpURecordHandler.java
  13. 4
      HttpComponent/src/main/java/com/arialyy/aria/http/download/HttpDThreadTaskAdapter.java
  14. 2
      HttpComponent/src/main/java/com/arialyy/aria/http/upload/HttpULoader.java
  15. 4
      M3U8Component/src/main/java/com/arialyy/aria/m3u8/BaseM3U8Loader.java
  16. 2
      M3U8Component/src/main/java/com/arialyy/aria/m3u8/M3U8InfoTask.java
  17. 5
      M3U8Component/src/main/java/com/arialyy/aria/m3u8/M3U8ThreadTaskAdapter.java
  18. 8
      PublicComponent/src/main/java/com/arialyy/aria/core/loader/AbsNormalLoader.java
  19. 4
      PublicComponent/src/main/java/com/arialyy/aria/core/loader/NormalLoader.java
  20. 35
      PublicComponent/src/main/java/com/arialyy/aria/core/loader/NormalThreadStateManager.java
  21. 3
      PublicComponent/src/main/java/com/arialyy/aria/core/processor/IKeyUrlConverter.java
  22. 6
      PublicComponent/src/main/java/com/arialyy/aria/core/task/ThreadTask.java
  23. 5
      PublicComponent/src/main/java/com/arialyy/aria/core/wrapper/ITaskWrapper.java
  24. 14
      PublicComponent/src/main/java/com/arialyy/aria/util/CommonUtil.java
  25. 9
      PublicComponent/src/main/java/com/arialyy/aria/util/ComponentUtil.java
  26. 31
      PublicComponent/src/main/java/com/arialyy/aria/util/FileUtil.java
  27. 32
      README.md
  28. 11
      SFtpComponent/bintray-release.gradle
  29. 7
      SFtpComponent/build.gradle
  30. 7
      SFtpComponent/src/main/java/com/arialyy/aria/sftp/AbsSFtpInfoTask.java
  31. 2
      SFtpComponent/src/main/java/com/arialyy/aria/sftp/SFtpSessionManager.java
  32. 23
      SFtpComponent/src/main/java/com/arialyy/aria/sftp/SFtpTaskOption.java
  33. 6
      SFtpComponent/src/main/java/com/arialyy/aria/sftp/SFtpUtil.java
  34. 23
      SFtpComponent/src/main/java/com/arialyy/aria/sftp/download/SFtpDInfoTask.java
  35. 13
      SFtpComponent/src/main/java/com/arialyy/aria/sftp/download/SFtpDLoader.java
  36. 10
      SFtpComponent/src/main/java/com/arialyy/aria/sftp/download/SFtpDLoaderUtil.java
  37. 92
      SFtpComponent/src/main/java/com/arialyy/aria/sftp/download/SFtpDRecordHandler.java
  38. 9
      SFtpComponent/src/main/java/com/arialyy/aria/sftp/download/SFtpDTTBuilderAdapter.java
  39. 150
      SFtpComponent/src/main/java/com/arialyy/aria/sftp/download/SFtpDThreadTaskAdapter.java
  40. 69
      SFtpComponent/src/main/java/com/arialyy/aria/sftp/upload/SFtpUInfoTask.java
  41. 155
      SFtpComponent/src/main/java/com/arialyy/aria/sftp/upload/SFtpULoader.java
  42. 55
      SFtpComponent/src/main/java/com/arialyy/aria/sftp/upload/SFtpULoaderUtil.java
  43. 115
      SFtpComponent/src/main/java/com/arialyy/aria/sftp/upload/SFtpURecordHandler.java
  44. 73
      SFtpComponent/src/main/java/com/arialyy/aria/sftp/upload/SFtpUTTBuilderAdapter.java
  45. 201
      SFtpComponent/src/main/java/com/arialyy/aria/sftp/upload/SFtpUThreadTaskAdapter.java
  46. 55
      app/src/main/AndroidManifest.xml
  47. 2
      app/src/main/assets/aria_config.xml
  48. 7
      app/src/main/java/com/arialyy/simple/MainActivity.java
  49. 7
      app/src/main/java/com/arialyy/simple/core/download/SFtpDownloadActivity.java
  50. 1
      app/src/main/java/com/arialyy/simple/core/download/SingleTaskActivity.java
  51. 21
      app/src/main/java/com/arialyy/simple/core/download/m3u8/M3U8VodDLoadActivity.java
  52. 2
      app/src/main/java/com/arialyy/simple/core/upload/FtpUploadActivity.java
  53. 239
      app/src/main/java/com/arialyy/simple/core/upload/SFtpUploadActivity.java
  54. 25
      app/src/main/java/com/arialyy/simple/core/upload/UploadModule.java
  55. 1
      app/src/main/java/com/arialyy/simple/modlue/CommonModule.java
  56. 20
      app/src/main/res/layout/activity_m3u8_vod.xml
  57. 83
      app/src/main/res/layout/activity_sftp_upload.xml
  58. 2
      app/src/main/res/values/strings.xml
  59. 4
      build.gradle

@ -141,57 +141,6 @@ public class FtpOption extends BaseOption {
return this;
}
/**
* 设置私钥证书路径
*
* @param prvKey 证书路径
*/
public FtpOption setPrvKey(String prvKey) {
if (TextUtils.isEmpty(prvKey)) {
ALog.e(TAG, "设置私钥证书失败,证书内容为空");
return this;
}
idEntity.prvKey = prvKey;
return this;
}
/**
* 设置私钥密码
*
* @param prvKeyPass 私钥密码
*/
public FtpOption setPrvKeyPass(String prvKeyPass) {
if (TextUtils.isEmpty(prvKeyPass)) {
ALog.e(TAG, "设置证书密码失败,证书密码为空");
return this;
}
idEntity.prvPass = prvKeyPass;
return this;
}
/**
* 设置公钥证书
*
* @param pubKey 公钥证书内容
*/
public FtpOption setPubKey(String pubKey) {
if (TextUtils.isEmpty(pubKey)) {
ALog.e(TAG, "设置公钥失败,证书内容为空");
return this;
}
idEntity.pubKey = pubKey;
return this;
}
public FtpOption setKnowHostPath(String knowHostPath){
if (TextUtils.isEmpty(knowHostPath)){
ALog.e(TAG, "knowhost 文件路径为空");
return this;
}
idEntity.knowHost = knowHostPath;
return this;
}
/**
* 设置安全模式默认true
*

@ -0,0 +1,135 @@
/*
* 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.text.TextUtils;
import com.arialyy.aria.core.FtpUrlEntity;
import com.arialyy.aria.core.IdEntity;
import com.arialyy.aria.core.ProtocolType;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.ComponentUtil;
/**
* Created by laoyuyu on 2018/3/9.
*/
public class SFtpOption extends BaseOption {
private String charSet, userName, password;
private boolean isNeedLogin = false;
private FtpUrlEntity urlEntity;
private String protocol;
private IdEntity idEntity = new IdEntity();
public SFtpOption() {
super();
ComponentUtil.getInstance().checkComponentExist(ComponentUtil.COMPONENT_TYPE_M3U8);
}
public SFtpOption charSet(String charSet) {
if (TextUtils.isEmpty(charSet)) {
throw new NullPointerException("字符编码为空");
}
this.charSet = charSet;
return this;
}
public SFtpOption login(String userName, String password) {
if (TextUtils.isEmpty(userName)) {
ALog.e(TAG, "用户名不能为null");
return this;
} else if (TextUtils.isEmpty(password)) {
ALog.e(TAG, "密码不能为null");
return this;
}
this.userName = userName;
this.password = password;
isNeedLogin = true;
return this;
}
/**
* 设置协议类型
*
* @param protocol {@link ProtocolType}
*/
public SFtpOption setProtocol(String protocol) {
if (TextUtils.isEmpty(protocol)) {
ALog.e(TAG, "设置协议失败,协议信息为空");
return this;
}
this.protocol = protocol;
return this;
}
/**
* 设置私钥证书路径
*
* @param prvKey 证书路径
*/
public SFtpOption setPrvKey(String prvKey) {
if (TextUtils.isEmpty(prvKey)) {
ALog.e(TAG, "设置私钥证书失败,证书内容为空");
return this;
}
idEntity.prvKey = prvKey;
return this;
}
/**
* 设置私钥密码
*
* @param prvKeyPass 私钥密码
*/
public SFtpOption setPrvKeyPass(String prvKeyPass) {
if (TextUtils.isEmpty(prvKeyPass)) {
ALog.e(TAG, "设置证书密码失败,证书密码为空");
return this;
}
idEntity.prvPass = prvKeyPass;
return this;
}
/**
* 设置公钥证书
*
* @param pubKey 公钥证书内容
*/
public SFtpOption setPubKey(String pubKey) {
if (TextUtils.isEmpty(pubKey)) {
ALog.e(TAG, "设置公钥失败,证书内容为空");
return this;
}
idEntity.pubKey = pubKey;
return this;
}
public SFtpOption setKnowHostPath(String knowHostPath) {
if (TextUtils.isEmpty(knowHostPath)) {
ALog.e(TAG, "knowhost 文件路径为空");
return this;
}
idEntity.knowHost = knowHostPath;
return this;
}
public void setUrlEntity(FtpUrlEntity urlEntity) {
this.urlEntity = urlEntity;
urlEntity.needLogin = isNeedLogin;
urlEntity.user = userName;
urlEntity.password = password;
urlEntity.idEntity = idEntity;
}
}

@ -17,6 +17,7 @@ package com.arialyy.aria.core.download.target;
import com.arialyy.aria.core.common.AbsBuilderTarget;
import com.arialyy.aria.core.common.FtpOption;
import com.arialyy.aria.core.common.SFtpOption;
import com.arialyy.aria.core.download.DownloadEntity;
import com.arialyy.aria.core.wrapper.ITaskWrapper;
import com.arialyy.aria.util.CommonUtil;
@ -50,7 +51,7 @@ public class FtpBuilderTarget extends AbsBuilderTarget<FtpBuilderTarget> {
/**
* 设置登陆字符串编码sftp等参数
*/
public FtpBuilderTarget sftpOption(FtpOption option) {
public FtpBuilderTarget sftpOption(SFtpOption option) {
if (option == null) {
throw new NullPointerException("ftp 任务配置为空");
}

@ -17,6 +17,7 @@ package com.arialyy.aria.core.download.target;
import com.arialyy.aria.core.common.AbsNormalTarget;
import com.arialyy.aria.core.common.FtpOption;
import com.arialyy.aria.core.common.SFtpOption;
import com.arialyy.aria.core.download.DownloadEntity;
import com.arialyy.aria.core.wrapper.ITaskWrapper;
import com.arialyy.aria.util.CommonUtil;
@ -49,7 +50,7 @@ public class FtpNormalTarget extends AbsNormalTarget<FtpNormalTarget> {
/**
* 设置登陆字符串编码sftp等参数
*/
public FtpNormalTarget sftpOption(FtpOption option) {
public FtpNormalTarget sftpOption(SFtpOption option) {
if (option == null) {
throw new NullPointerException("ftp 任务配置为空");
}

@ -17,6 +17,7 @@ package com.arialyy.aria.core.upload.target;
import com.arialyy.aria.core.common.AbsBuilderTarget;
import com.arialyy.aria.core.common.FtpOption;
import com.arialyy.aria.core.common.SFtpOption;
import com.arialyy.aria.core.upload.UploadEntity;
import com.arialyy.aria.core.wrapper.ITaskWrapper;
import com.arialyy.aria.util.CommonUtil;
@ -70,4 +71,18 @@ public class FtpBuilderTarget extends AbsBuilderTarget<FtpBuilderTarget> {
getTaskWrapper().getOptionParams().setParams(option);
return this;
}
/**
* 设置登陆字符串编码sftp等参数
*/
public FtpBuilderTarget sftpOption(SFtpOption option) {
if (option == null) {
throw new NullPointerException("ftp 任务配置为空");
}
option.setUrlEntity(CommonUtil.getFtpUrlInfo(url));
getTaskWrapper().getOptionParams().setParams(option);
((UploadEntity)getEntity()).setTaskType(ITaskWrapper.U_SFTP);
getTaskWrapper().setRequestType(ITaskWrapper.U_SFTP);
return this;
}
}

@ -17,6 +17,7 @@ package com.arialyy.aria.core.upload.target;
import com.arialyy.aria.core.common.AbsNormalTarget;
import com.arialyy.aria.core.common.FtpOption;
import com.arialyy.aria.core.common.SFtpOption;
import com.arialyy.aria.core.upload.UploadEntity;
import com.arialyy.aria.core.wrapper.ITaskWrapper;
import com.arialyy.aria.util.CommonUtil;
@ -46,6 +47,20 @@ public class FtpNormalTarget extends AbsNormalTarget<FtpNormalTarget> {
return this;
}
/**
* 设置登陆字符串编码sftp等参数
*/
public FtpNormalTarget sftpOption(SFtpOption option) {
if (option == null) {
throw new NullPointerException("ftp 任务配置为空");
}
option.setUrlEntity(CommonUtil.getFtpUrlInfo(getEntity().getUrl()));
getTaskWrapper().getOptionParams().setParams(option);
(getEntity()).setTaskType(ITaskWrapper.U_SFTP);
getTaskWrapper().setRequestType(ITaskWrapper.U_SFTP);
return this;
}
@Override public UploadEntity getEntity() {
return (UploadEntity) super.getEntity();
}

@ -1,6 +1,9 @@
## 开发日志
+ v_3.8.4
+ v_3.8.5 (2020/1/18)
- fix bug https://github.com/AriaLyy/Aria/issues/599
- 增加密钥url转换器的参数 https://github.com/AriaLyy/Aria/issues/603
- 增加sftp,文件上传、下载功能,[sftp下载](https://aria.laoyuyu.me/aria_doc/download/sftp_normal.html),[sftp上传](https://aria.laoyuyu.me/aria_doc/upload/sftp_normal.html)
- 使用零拷贝技术,优化了合并分块的效率
+ v_3.8.3 (2020/1/9)
- fix bug https://github.com/AriaLyy/Aria/issues/573
- android P适配 https://github.com/AriaLyy/Aria/issues/581

@ -1,95 +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.ftp.download;
//
//import com.arialyy.aria.core.TaskRecord;
//import com.arialyy.aria.core.common.SubThreadConfig;
//import com.arialyy.aria.core.listener.IEventListener;
//import com.arialyy.aria.core.loader.NormalTTBuilder;
//import com.arialyy.aria.core.loader.IRecordHandler;
//import com.arialyy.aria.core.loader.IThreadTaskBuilder;
//import com.arialyy.aria.core.loader.NormalLoader;
//import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
//import com.arialyy.aria.util.ALog;
//import com.arialyy.aria.util.FileUtil;
//import java.io.File;
//import java.lang.reflect.InvocationHandler;
//import java.lang.reflect.Method;
//import java.lang.reflect.Proxy;
//
//final class FtpDLoader extends NormalLoader {
// FtpDLoader(AbsTaskWrapper wrapper, IEventListener listener) {
// super(wrapper, listener);
// }
//
// @Override public void addComponent(IThreadTaskBuilder builder) {
// mTTBuilder = (IThreadTaskBuilder) Proxy.newProxyInstance(getClass().getClassLoader(),
// NormalTTBuilder.class.getInterfaces(), new InvocationHandler() {
// NormalTTBuilder target = new NormalTTBuilder(mTaskWrapper);
// @Override public Object invoke(Object proxy, Method method, Object[] args)
// throws Exception {
// if (method.getDeclaringClass() == Object.class) {
// return method.invoke(this, args);
// }
//
// //if (method.isDefault()) {
// // Constructor<MethodHandles.Lookup>
// // constructor =
// // MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);
// // constructor.setAccessible(true);
// // return constructor.newInstance(AbsNormalTTBuilder.class, -1 /* trusted */)
// // .unreflectSpecial(method, AbsNormalTTBuilder.class)
// // .bindTo(proxy)
// // .invokeWithArguments(args);
// //}
//
// String methodName = method.getName();
// switch (methodName) {
//
// case "handleNewTask":
// return handleNewTask((TaskRecord) args[0], (int) args[1]);
// case "getAdapter":
// return new FtpDThreadTaskAdapter((SubThreadConfig) args[0]);
// }
//
// return method.invoke(target, args);
// }
// });
// }
//
// /**
// * 处理新任务
// */
// private boolean handleNewTask(TaskRecord record, int totalThreadNum) {
// File temp = new File(getEntity().getFilePath());
// if (!record.isBlock) {
// if (temp.exists()) {
// FileUtil.deleteFile(temp);
// }
// //CommonUtil.createFile(mTempFile.getPath());
// } else {
// for (int i = 0; i < totalThreadNum; i++) {
// File blockFile =
// new File(String.format(IRecordHandler.SUB_PATH, temp.getPath(), i));
// if (blockFile.exists()) {
// ALog.d(TAG, String.format("分块【%s】已经存在,将删除该分块", i));
// FileUtil.deleteFile(blockFile);
// }
// }
// }
// return true;
// }
//}

@ -30,9 +30,9 @@ import java.util.ArrayList;
* @Author lyy
* @Date 2019-09-19
*/
public final class FtpDRecordHandler extends RecordHandler {
final class FtpDRecordHandler extends RecordHandler {
public FtpDRecordHandler(DTaskWrapper wrapper) {
FtpDRecordHandler(DTaskWrapper wrapper) {
super(wrapper);
}
@ -76,7 +76,7 @@ public final class FtpDRecordHandler extends RecordHandler {
record.threadRecords = new ArrayList<>();
record.threadNum = threadNum;
record.isBlock = Configuration.getInstance().downloadCfg.isUseBlock();
record.taskType = getWrapper().getRequestType();
record.taskType = ITaskWrapper.D_FTP;
record.isGroupRecord = false;
return record;

@ -25,13 +25,13 @@ import com.arialyy.aria.core.loader.IRecordHandler;
import com.arialyy.aria.core.loader.NormalLoader;
import com.arialyy.aria.core.manager.ThreadTaskManager;
import com.arialyy.aria.core.task.IThreadTask;
import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
import com.arialyy.aria.core.upload.UTaskWrapper;
import com.arialyy.aria.exception.BaseException;
final class FtpULoader extends NormalLoader {
final class FtpULoader extends NormalLoader<UTaskWrapper> {
private FTPFile ftpFile;
FtpULoader(AbsTaskWrapper wrapper, IEventListener listener) {
FtpULoader(UTaskWrapper wrapper, IEventListener listener) {
super(wrapper, listener);
}
@ -59,6 +59,8 @@ final class FtpULoader extends NormalLoader {
getListener().onStart(mStateManager.getCurrentProgress());
}
startTimer();
// 启动线程任务
for (IThreadTask threadTask : getTaskList()) {
ThreadTaskManager.getInstance().startThread(mTaskWrapper.getKey(), threadTask);

@ -41,7 +41,8 @@ public final class FtpULoaderUtil extends AbsNormalLoaderUtil {
}
@Override public AbsNormalLoader getLoader() {
return mLoader == null ? new FtpULoader(getTaskWrapper(), getListener()) : mLoader;
return mLoader == null ? new FtpULoader((UTaskWrapper) getTaskWrapper(), getListener())
: mLoader;
}
@Override public LoaderStructure BuildLoaderStructure() {

@ -101,7 +101,7 @@ final class FtpURecordHandler extends RecordHandler {
record.threadRecords = new ArrayList<>();
record.threadNum = threadNum;
record.isBlock = false;
record.taskType = getWrapper().getRequestType();
record.taskType = ITaskWrapper.U_FTP;
record.isGroupRecord = getEntity().isGroupChild();
return record;

@ -60,6 +60,10 @@ final class HttpDThreadTaskAdapter extends BaseHttpThreadTaskAdapter {
HttpURLConnection conn = null;
BufferedInputStream is = null;
BufferedRandomAccessFile file = null;
if (getThreadRecord().threadId == 1){
fail(null, false);
return;
}
try {
URL url = ConnectionHelp.handleUrl(getThreadConfig().url, mTaskOption);
conn = ConnectionHelp.handleConnection(url, mTaskOption);

@ -30,7 +30,7 @@ import com.arialyy.aria.exception.AriaIOException;
import com.arialyy.aria.util.ALog;
import java.util.List;
final class HttpULoader extends AbsNormalLoader {
final class HttpULoader extends AbsNormalLoader<UTaskWrapper> {
HttpULoader(UTaskWrapper wrapper, IEventListener listener) {
super(wrapper, listener);
}

@ -32,7 +32,7 @@ import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
public abstract class BaseM3U8Loader extends AbsNormalLoader {
public abstract class BaseM3U8Loader extends AbsNormalLoader<DTaskWrapper> {
protected M3U8TaskOption mM3U8Option;
public BaseM3U8Loader(DTaskWrapper wrapper, IEventListener listener) {
@ -138,6 +138,6 @@ public abstract class BaseM3U8Loader extends AbsNormalLoader {
}
protected DownloadEntity getEntity() {
return (DownloadEntity) mTaskWrapper.getEntity();
return mTaskWrapper.getEntity();
}
}

@ -375,7 +375,7 @@ final public class M3U8InfoTask implements IInfoTask {
IKeyUrlConverter keyUrlConverter = mM3U8Option.getKeyUrlConverter();
String keyUrl = info.keyUrl;
if (keyUrlConverter != null) {
keyUrl = keyUrlConverter.convert(keyUrl);
keyUrl = keyUrlConverter.convert(mEntity.getUrl(), keyUrl);
}
if (TextUtils.isEmpty(keyUrl)) {
ALog.e(TAG, "m3u8密钥key url 为空");

@ -27,6 +27,7 @@ import com.arialyy.aria.http.ConnectionHelp;
import com.arialyy.aria.http.HttpTaskOption;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CheckUtil;
import com.arialyy.aria.util.CommonUtil;
import java.io.BufferedInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
@ -47,7 +48,7 @@ import java.util.Set;
* Created by lyy on 2017/1/18. 下载线程
*/
public final class M3U8ThreadTaskAdapter extends AbsThreadTaskAdapter {
private final String TAG = "M3U8ThreadTask";
private final String TAG = CommonUtil.getClassName(this);
private HttpTaskOption mHttpTaskOption;
private BufferedInputStream is = null;
@ -167,7 +168,9 @@ public final class M3U8ThreadTaskAdapter extends AbsThreadTaskAdapter {
conn.disconnect(); // 关闭上一个连接
URL url = ConnectionHelp.handleUrl(newUrl, mHttpTaskOption);
conn = ConnectionHelp.handleConnection(url, mHttpTaskOption);
if (!TextUtils.isEmpty(cookies)){
conn.setRequestProperty("Cookie", cookies);
}
if (mHttpTaskOption.isChunked()) {
conn.setDoInput(true);
conn.setChunkedStreamingMode(0);

@ -39,10 +39,10 @@ import java.util.concurrent.TimeUnit;
* 3创建文件信息获取器获取文件信息根据文件信息执行任务
* 4创建线程任务执行下载上传操作
*/
public abstract class AbsNormalLoader implements ILoaderVisitor, ILoader {
public abstract class AbsNormalLoader<T extends AbsTaskWrapper> implements ILoaderVisitor, ILoader {
protected final String TAG = CommonUtil.getClassName(getClass());
private IEventListener mListener;
protected AbsTaskWrapper mTaskWrapper;
protected T mTaskWrapper;
protected File mTempFile;
private List<IThreadTask> mTask = new ArrayList<>();
@ -61,7 +61,7 @@ public abstract class AbsNormalLoader implements ILoaderVisitor, ILoader {
protected IInfoTask mInfoTask;
protected IThreadTaskBuilder mTTBuilder;
protected AbsNormalLoader(AbsTaskWrapper wrapper, IEventListener listener) {
protected AbsNormalLoader(T wrapper, IEventListener listener) {
mListener = listener;
mTaskWrapper = wrapper;
}
@ -123,7 +123,7 @@ public abstract class AbsNormalLoader implements ILoaderVisitor, ILoader {
}
Looper.prepare();
Looper looper = Looper.myLooper();
if (looper == Looper.getMainLooper()){
if (looper == Looper.getMainLooper()) {
throw new IllegalThreadStateException("不能在主线程程序中调用Loader");
}
isRuning = true;

@ -34,12 +34,12 @@ import java.io.File;
/**
* 单文件
*/
public class NormalLoader extends AbsNormalLoader {
public class NormalLoader<T extends AbsTaskWrapper> extends AbsNormalLoader<T> {
private int startThreadNum; //启动的线程数
protected boolean isComplete = false;
private Looper looper;
public NormalLoader(AbsTaskWrapper wrapper, IEventListener listener) {
public NormalLoader(T wrapper, IEventListener listener) {
super(wrapper, listener);
mTempFile = new File(getEntity().getFilePath());
EventMsgUtil.getDefault().register(this);

@ -22,7 +22,6 @@ import android.os.Message;
import com.arialyy.aria.core.TaskRecord;
import com.arialyy.aria.core.inf.IThreadStateManager;
import com.arialyy.aria.core.listener.IEventListener;
import com.arialyy.aria.core.wrapper.ITaskWrapper;
import com.arialyy.aria.exception.BaseException;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.FileUtil;
@ -100,6 +99,11 @@ public class NormalThreadStateManager implements IThreadStateManager {
mCompleteNum++;
if (isComplete()) {
ALog.d(TAG, "isComplete, completeNum = " + mCompleteNum);
//if (mTaskRecord.taskType == ITaskWrapper.D_SFTP) {
// mergerSFtp();
// mListener.onComplete();
//} else
if (mTaskRecord.isBlock) {
if (mergeFile()) {
mListener.onComplete();
@ -215,6 +219,26 @@ public class NormalThreadStateManager implements IThreadStateManager {
return size;
}
/**
* 合并sftp的分块
*/
private boolean mergerSFtp() {
if (mTaskRecord.threadNum == 1) {
File partFile = new File(String.format(IRecordHandler.SUB_PATH, mTaskRecord.filePath, 0));
return partFile.renameTo(new File(mTaskRecord.filePath));
}
List<String> partPath = new ArrayList<>();
for (int i = 0, len = mTaskRecord.threadNum; i < len; i++) {
partPath.add(String.format(IRecordHandler.SUB_PATH, mTaskRecord.filePath, i));
}
FileUtil.mergeSFtpFile(mTaskRecord.filePath, partPath, mTaskRecord.fileLength);
for (String pp : partPath) {
FileUtil.deleteFile(pp);
}
return true;
}
/**
* 合并文件
*
@ -230,15 +254,10 @@ public class NormalThreadStateManager implements IThreadStateManager {
for (int i = 0, len = mTaskRecord.threadNum; i < len; i++) {
partPath.add(String.format(IRecordHandler.SUB_PATH, mTaskRecord.filePath, i));
}
boolean isSuccess = mTaskRecord.taskType == ITaskWrapper.D_SFTP ?
FileUtil.mergeSFtpFile(mTaskRecord.filePath, partPath, mTaskRecord.fileLength)
: FileUtil.mergeFile(mTaskRecord.filePath, partPath);
boolean isSuccess = FileUtil.mergeFile(mTaskRecord.filePath, partPath);
if (isSuccess) {
for (String pp : partPath) {
File f = new File(pp);
if (f.exists()) {
f.delete();
}
FileUtil.deleteFile(pp);
}
File targetFile = new File(mTaskRecord.filePath);
if (targetFile.exists() && targetFile.length() > mTaskRecord.fileLength) {

@ -25,8 +25,9 @@ public interface IKeyUrlConverter extends IEventHandler {
/**
* 将被加密的密钥下载地址转换为可使用的http下载地址
*
* @param m3u8Url m3u8文件的下载地址
* @param keyUrl 加密的url地址
* @return 可正常访问的http地址
*/
String convert(String keyUrl);
String convert(String m3u8Url, String keyUrl);
}

@ -367,6 +367,11 @@ public class ThreadTask implements IThreadTask, IThreadTaskObserver {
b.putLong(IThreadStateManager.DATA_ADD_LEN, mRangeProgress - mLastRangeProgress);
msg.what = IThreadStateManager.STATE_RUNNING;
msg.obj = mRangeProgress;
Thread loopThread = mStateHandler.getLooper().getThread();
if (!loopThread.isAlive() || loopThread.isInterrupted()) {
return;
}
msg.sendToTarget();
}
@ -451,6 +456,7 @@ public class ThreadTask implements IThreadTask, IThreadTaskObserver {
handleBlockRecord();
ThreadTaskManager.getInstance().retryThread(this);
} else {
ALog.e(TAG, String.format("任务【%s】执行失败", getFileName()));
sendFailMsg(null, false);
}
}

@ -84,6 +84,11 @@ public interface ITaskWrapper {
*/
int D_SFTP = 12;
/**
* SFTP 上传
*/
int U_SFTP = 13;
/**
* 获取任务类型
*

@ -30,7 +30,6 @@ import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
@ -136,6 +135,18 @@ public class CommonUtil {
return new String(str.getBytes(charSet), SERVER_CHARSET);
}
/**
* 将字符串转换为Ftp服务器默认的ISO-8859-1编码
*
* @param charSet 字符串编码
* @param str 需要转换的字符串
* @return 转换后的字符串
*/
public static String convertSFtpChar(String charSet, String str)
throws UnsupportedEncodingException {
return new String(str.getBytes(), charSet);
}
/**
* 获取某包下所有类
*
@ -481,7 +492,6 @@ public class CommonUtil {
}
}
/**
* 校验文件MD5码
*/

@ -40,6 +40,7 @@ public class ComponentUtil {
public static final int COMPONENT_TYPE_HTTP = 1;
public static final int COMPONENT_TYPE_FTP = 2;
public static final int COMPONENT_TYPE_M3U8 = 3;
public static final int COMPONENT_TYPE_SFTP = 4;
private String TAG = CommonUtil.getClassName(getClass());
private static volatile ComponentUtil INSTANCE = null;
@ -82,6 +83,10 @@ public class ComponentUtil {
className = "com.arialyy.aria.http.HttpTaskOption";
errorStr = "http插件不存在,请添加http插件";
break;
case COMPONENT_TYPE_SFTP:
className = "com.arialyy.aria.sftp.SFtpTaskOption";
errorStr = "sftp插件不存在,请添加sftp插件";
break;
}
try {
@ -130,6 +135,9 @@ public class ComponentUtil {
case ITaskWrapper.D_SFTP:
className = "com.arialyy.aria.sftp.download.SFtpDLoaderUtil";
break;
case ITaskWrapper.U_SFTP:
className = "com.arialyy.aria.sftp.upload.SFtpULoaderUtil";
break;
}
if (className == null) {
ALog.e(TAG, "不识别的类名:" + className);
@ -178,6 +186,7 @@ public class ComponentUtil {
break;
case ITaskWrapper.U_FTP:
case ITaskWrapper.U_HTTP:
case ITaskWrapper.U_SFTP:
className = "com.arialyy.aria.core.listener.BaseUListener";
break;
case ITaskWrapper.DG_HTTP:

@ -37,17 +37,13 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.SequenceInputStream;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
@ -342,21 +338,12 @@ public class FileUtil {
return false;
}
streams.add(new FileInputStream(subPath));
FileInputStream fis = new FileInputStream(subPath);
FileChannel fic = fis.getChannel();
foc.transferFrom(fic, fileLen, f.length());
fileLen += f.length();
fis.close();
}
Enumeration<FileInputStream> en = Collections.enumeration(streams);
SequenceInputStream sis = new SequenceInputStream(en);
ReadableByteChannel fic = Channels.newChannel(sis);
//ByteBuffer bf = ByteBuffer.allocate(8196);
//while (fic.read(bf) != -1) {
// bf.flip();
// foc.write(bf);
// bf.compact();
//}
foc.transferFrom(fic, 0, fileLen);
fic.close();
sis.close();
ALog.d(TAG, String.format("合并文件耗时:%sms", (System.currentTimeMillis() - startTime)));
return true;
} catch (IOException e) {
@ -405,7 +392,6 @@ public class FileUtil {
int i = 0;
int threadNum = subPaths.size();
long tempLen = targetFileSize / threadNum;
ALog.d(TAG, "fileSize = " + targetFileSize);
for (String subPath : subPaths) {
File f = new File(subPath);
if (!f.exists()) {
@ -421,14 +407,13 @@ public class FileUtil {
long blockLen = i == (threadNum - 1) ? targetFileSize - tempLen * i : tempLen;
FileInputStream fis = new FileInputStream(subPath);
FileChannel fic = fis.getChannel();
ALog.d(TAG, "blcokLen = " + blockLen);
long rLen = foc.transferFrom(fic, 0, blockLen);
ALog.d(TAG, "writeLen = " + rLen);
foc.transferFrom(fic, blockLen * i, blockLen);
fis.close();
i ++;
i++;
}
ALog.d(TAG, String.format("合并文件耗时:%sms", (System.currentTimeMillis() - startTime)));
ALog.d(TAG, String.format("合并文件耗时:%sms,合并后的文件长度:%s", (System.currentTimeMillis() - startTime),
file.length()));
return true;
} catch (IOException e) {
e.printStackTrace();

@ -44,16 +44,19 @@ Aria有以下特点:
## 引入库
[![license](http://img.shields.io/badge/license-Apache2.0-brightgreen.svg?style=flat)](https://github.com/AriaLyy/Aria/blob/master/LICENSE)
[![Core](https://img.shields.io/badge/Core-3.8.3-blue)](https://github.com/AriaLyy/Aria)
[![Compiler](https://img.shields.io/badge/Compiler-3.8.3-blue)](https://github.com/AriaLyy/Aria)
[![FtpComponent](https://img.shields.io/badge/FtpComponent-3.8.3-orange)](https://github.com/AriaLyy/Aria)
[![M3U8Component](https://img.shields.io/badge/M3U8Component-3.8.3-orange)](https://github.com/AriaLyy/Aria)
[![Core](https://img.shields.io/badge/Core-3.8.5-blue)](https://github.com/AriaLyy/Aria)
[![Compiler](https://img.shields.io/badge/Compiler-3.8.5-blue)](https://github.com/AriaLyy/Aria)
[![FtpComponent](https://img.shields.io/badge/FtpComponent-3.8.5-orange)](https://github.com/AriaLyy/Aria)
[![FtpComponent](https://img.shields.io/badge/SFtpComponent-3.8.5-orange)](https://github.com/AriaLyy/Aria)
[![M3U8Component](https://img.shields.io/badge/M3U8Component-3.8.5-orange)](https://github.com/AriaLyy/Aria)
```java
implementation 'com.arialyy.aria:core:3.8.3'
annotationProcessor 'com.arialyy.aria:compiler:3.8.3'
implementation 'com.arialyy.aria:ftpComponent:3.8.3' # 如果需要使用ftp,请增加该组件
implementation 'com.arialyy.aria:m3u8Component:3.8.3' # 如果需要使用m3u8下载功能,请增加该组件
implementation 'com.arialyy.aria:core:3.8.5'
annotationProcessor 'com.arialyy.aria:compiler:3.8.5'
implementation 'com.arialyy.aria:ftpComponent:3.8.5' # 如果需要使用ftp,请增加该组件
implementation 'com.arialyy.aria:sftpComponent:3.8.5' # 如果需要使用ftp,请增加该组件
implementation 'com.arialyy.aria:m3u8Component:3.8.5' # 如果需要使用m3u8下载功能,请增加该组件
```
如果出现android support依赖错误,请将 `compile 'com.arialyy.aria:core:<last-version>'`替换为
```
@ -139,14 +142,11 @@ protected void onCreate(Bundle savedInstanceState) {
### 版本日志
+ v_3.8.3 (2020/1/9)
- fix bug https://github.com/AriaLyy/Aria/issues/573
- android P适配 https://github.com/AriaLyy/Aria/issues/581
- 添加ftp服务器标志 https://github.com/AriaLyy/Aria/issues/580
- 重构loader模块,让loader模块的代码更加清晰,去除一些不必要的线程创建
- 修复ftp上传完成后,删除服务器端的文件,无法重新下载的问题
- 增加获取执行中的任务api,详情见:https://aria.laoyuyu.me/aria_doc/api/task_list.html
- 增加获取剩余时间的api,详情见:https://aria.laoyuyu.me/aria_doc/start/task_explain.html
+ v_3.8.5 (2020/1/18)
- fix bug https://github.com/AriaLyy/Aria/issues/599
- 增加密钥url转换器的参数 https://github.com/AriaLyy/Aria/issues/603
- 增加sftp,文件上传、下载功能,[sftp下载](https://aria.laoyuyu.me/aria_doc/download/sftp_normal.html),[sftp上传](https://aria.laoyuyu.me/aria_doc/upload/sftp_normal.html)
- 使用零拷贝技术,优化了合并分块的效率
[更多版本记录](https://github.com/AriaLyy/Aria/blob/master/DEV_LOG.md)

@ -0,0 +1,11 @@
apply plugin: 'com.novoda.bintray-release'
publish {
artifactId = 'sftpComponent'
uploadName = 'SFTPComponent'
userOrg = rootProject.ext.userOrg
groupId = rootProject.ext.groupId
publishVersion = rootProject.ext.publishVersion
desc = rootProject.ext.desc
website = rootProject.ext.website
licences = rootProject.ext.licences
}

@ -25,8 +25,9 @@ android {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "com.jcraft:jsch:0.1.55"
implementation "com.jcraft:jzlib:1.1.3"
implementation project(path: ':FtpComponent')
api "com.jcraft:jsch:0.1.55"
api "com.jcraft:jzlib:1.1.3"
implementation project(path: ':PublicComponent')
}
apply from: 'bintray-release.gradle'

@ -20,7 +20,6 @@ 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.BaseException;
import com.arialyy.aria.ftp.FtpTaskOption;
import com.arialyy.aria.util.CommonUtil;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
@ -34,11 +33,11 @@ public abstract class AbsSFtpInfoTask<WP extends AbsTaskWrapper> implements IInf
protected String TAG = CommonUtil.getClassName(this);
protected Callback callback;
private WP wrapper;
private FtpTaskOption option;
private SFtpTaskOption option;
public AbsSFtpInfoTask(WP wp) {
this.wrapper = wp;
this.option = (FtpTaskOption) wrapper.getTaskOption();
this.option = (SFtpTaskOption) wrapper.getTaskOption();
}
protected abstract void getFileInfo(Session session)
@ -65,7 +64,7 @@ public abstract class AbsSFtpInfoTask<WP extends AbsTaskWrapper> implements IInf
}
}
protected FtpTaskOption getOption() {
protected SFtpTaskOption getOption() {
return option;
}

@ -61,7 +61,7 @@ public class SFtpSessionManager {
if (session == null) {
ALog.w(TAG, "从缓存获取session失败,key:" + key);
}
//cleanIdleSession();
cleanIdleSession();
return session;
}

@ -15,7 +15,26 @@
*/
package com.arialyy.aria.sftp;
import com.arialyy.aria.core.common.SubThreadConfig;
import com.arialyy.aria.core.FtpUrlEntity;
import com.arialyy.aria.core.inf.ITaskOption;
public class SFtpSubThreadCOnfig extends SubThreadConfig {
public class SFtpTaskOption implements ITaskOption {
/**
* 账号和密码
*/
private FtpUrlEntity urlEntity;
/**
* 字符编码默认为"utf-8"
*/
private String charSet = "utf-8";
public FtpUrlEntity getUrlEntity() {
return urlEntity;
}
public String getCharSet() {
return charSet;
}
}

@ -78,7 +78,7 @@ public class SFtpUtil {
}
}
setknowHost(jSch, entity);
setKnowHost(jSch, entity);
Session session;
if (TextUtils.isEmpty(entity.user)) {
@ -94,14 +94,14 @@ public class SFtpUtil {
// 不检查公钥,需要在connect之前配置,但是不安全,no 模式会自动将配对信息写入know_host文件
config.put("StrictHostKeyChecking", "no");
session.setConfig(config);// 为Session对象设置properties
session.setTimeout(3000);// 设置超时
session.setTimeout(5000);// 设置超时
session.setIdentityRepository(jSch.getIdentityRepository());
session.connect();
SFtpSessionManager.getInstance().addSession(session, threadId);
return session;
}
private void setknowHost(JSch jSch, FtpUrlEntity entity) throws JSchException {
private void setKnowHost(JSch jSch, FtpUrlEntity entity) throws JSchException {
IdEntity idEntity = entity.idEntity;
if (idEntity.knowHost != null) {
File knowFile = new File(idEntity.knowHost);

@ -17,8 +17,10 @@ package com.arialyy.aria.sftp.download;
import com.arialyy.aria.core.common.CompleteInfo;
import com.arialyy.aria.core.download.DTaskWrapper;
import com.arialyy.aria.ftp.FtpTaskOption;
import com.arialyy.aria.exception.AriaException;
import com.arialyy.aria.sftp.AbsSFtpInfoTask;
import com.arialyy.aria.sftp.SFtpTaskOption;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSchException;
@ -38,7 +40,7 @@ final class SFtpDInfoTask extends AbsSFtpInfoTask<DTaskWrapper> {
@Override protected void getFileInfo(Session session) throws JSchException,
UnsupportedEncodingException, SftpException {
FtpTaskOption option = (FtpTaskOption) getWrapper().getTaskOption();
SFtpTaskOption option = (SFtpTaskOption) getWrapper().getTaskOption();
ChannelSftp channel = (ChannelSftp) session.openChannel("sftp");
channel.connect(1000);
@ -46,12 +48,23 @@ final class SFtpDInfoTask extends AbsSFtpInfoTask<DTaskWrapper> {
//channel.setFilenameEncoding("gbk");
String remotePath = option.getUrlEntity().remotePath;
String temp = CommonUtil.convertFtpChar(getOption().getCharSet(), remotePath);
SftpATTRS attr = channel.stat(temp);
String temp = CommonUtil.convertSFtpChar(option.getCharSet(), remotePath);
SftpATTRS attr = null;
try {
attr = channel.stat(temp);
} catch (Exception e) {
ALog.e(TAG, String.format("文件不存在,remotePath:%s", remotePath));
}
if (attr != null) {
getWrapper().getEntity().setFileSize(attr.getSize());
CompleteInfo info = new CompleteInfo();
info.code = 200;
channel.disconnect();
callback.onSucceed(getWrapper().getKey(), info);
} else {
callback.onFail(getWrapper().getEntity(),
new AriaException(TAG, String.format("文件不存在,remotePath:%s", remotePath)), false);
}
channel.disconnect();
}
}

@ -18,8 +18,9 @@ package com.arialyy.aria.sftp.download;
import android.os.Handler;
import android.os.Looper;
import com.arialyy.aria.core.common.AbsEntity;
import com.arialyy.aria.core.common.AbsNormalEntity;
import com.arialyy.aria.core.common.CompleteInfo;
import com.arialyy.aria.core.download.DTaskWrapper;
import com.arialyy.aria.core.download.DownloadEntity;
import com.arialyy.aria.core.event.EventMsgUtil;
import com.arialyy.aria.core.inf.IThreadStateManager;
import com.arialyy.aria.core.listener.IDLoadListener;
@ -28,29 +29,27 @@ import com.arialyy.aria.core.loader.AbsNormalLoader;
import com.arialyy.aria.core.loader.IInfoTask;
import com.arialyy.aria.core.loader.IRecordHandler;
import com.arialyy.aria.core.loader.IThreadTaskBuilder;
import com.arialyy.aria.core.loader.NormalTTBuilder;
import com.arialyy.aria.core.manager.ThreadTaskManager;
import com.arialyy.aria.core.task.IThreadTask;
import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
import com.arialyy.aria.exception.BaseException;
import com.arialyy.aria.util.FileUtil;
import java.io.File;
final class SFtpDLoader extends AbsNormalLoader {
final class SFtpDLoader extends AbsNormalLoader<DTaskWrapper> {
private int startThreadNum; //启动的线程数
private boolean isComplete = false;
private Looper looper;
SFtpDLoader(AbsTaskWrapper wrapper, IEventListener listener) {
SFtpDLoader(DTaskWrapper wrapper, IEventListener listener) {
super(wrapper, listener);
mTempFile = new File(getEntity().getFilePath());
EventMsgUtil.getDefault().register(this);
setUpdateInterval(wrapper.getConfig().getUpdateInterval());
}
private AbsNormalEntity getEntity() {
return (AbsNormalEntity) mTaskWrapper.getEntity();
private DownloadEntity getEntity() {
return mTaskWrapper.getEntity();
}
@Override public long getFileSize() {

@ -23,8 +23,7 @@ import com.arialyy.aria.core.loader.LoaderStructure;
import com.arialyy.aria.core.loader.NormalTTBuilder;
import com.arialyy.aria.core.loader.NormalThreadStateManager;
import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
import com.arialyy.aria.ftp.FtpTaskOption;
import com.arialyy.aria.ftp.download.FtpDRecordHandler;
import com.arialyy.aria.sftp.SFtpTaskOption;
/**
* sftp下载工具
@ -35,16 +34,17 @@ public class SFtpDLoaderUtil extends AbsNormalLoaderUtil {
public SFtpDLoaderUtil(AbsTaskWrapper wrapper, IEventListener listener) {
super(wrapper, listener);
wrapper.generateTaskOption(FtpTaskOption.class);
wrapper.generateTaskOption(SFtpTaskOption.class);
}
@Override public AbsNormalLoader getLoader() {
return mLoader == null ? new SFtpDLoader(getTaskWrapper(), getListener()) : mLoader;
return mLoader == null ? new SFtpDLoader((DTaskWrapper) getTaskWrapper(), getListener())
: mLoader;
}
@Override public LoaderStructure BuildLoaderStructure() {
LoaderStructure structure = new LoaderStructure();
structure.addComponent(new FtpDRecordHandler((DTaskWrapper) getTaskWrapper()))
structure.addComponent(new SFtpDRecordHandler((DTaskWrapper) getTaskWrapper()))
.addComponent(new NormalThreadStateManager(getListener()))
.addComponent(new SFtpDInfoTask((DTaskWrapper) getTaskWrapper()))
.addComponent(new NormalTTBuilder(getTaskWrapper(), new SFtpDTTBuilderAdapter(

@ -0,0 +1,92 @@
/*
* 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.sftp.download;
import com.arialyy.aria.core.TaskRecord;
import com.arialyy.aria.core.ThreadRecord;
import com.arialyy.aria.core.common.RecordHandler;
import com.arialyy.aria.core.common.RecordHelper;
import com.arialyy.aria.core.config.Configuration;
import com.arialyy.aria.core.download.DTaskWrapper;
import com.arialyy.aria.core.loader.IRecordHandler;
import com.arialyy.aria.core.wrapper.ITaskWrapper;
import com.arialyy.aria.util.RecordUtil;
import java.util.ArrayList;
/**
* @Author lyy
* @Date 2019-09-19
*/
final class SFtpDRecordHandler extends RecordHandler {
SFtpDRecordHandler(DTaskWrapper wrapper) {
super(wrapper);
}
@Override public void handlerTaskRecord(TaskRecord record) {
RecordHelper helper = new RecordHelper(getWrapper(), record);
if (record.threadNum == 1) {
helper.handleSingleThreadRecord();
} else {
if (getWrapper().isSupportBP()) {
if (record.isBlock) {
helper.handleBlockRecord();
} else {
helper.handleMultiRecord();
}
}
}
}
@Override
public ThreadRecord createThreadRecord(TaskRecord record, int threadId, long startL, long endL) {
ThreadRecord tr;
tr = new ThreadRecord();
tr.taskKey = record.filePath;
tr.threadId = threadId;
tr.startLocation = startL;
tr.isComplete = false;
tr.threadType = record.taskType;
//最后一个线程的结束位置即为文件的总长度
if (threadId == (record.threadNum - 1)) {
endL = getFileSize();
}
tr.endLocation = endL;
tr.blockLen = RecordUtil.getBlockLen(getFileSize(), threadId, record.threadNum);
return tr;
}
@Override public TaskRecord createTaskRecord(int threadNum) {
TaskRecord record = new TaskRecord();
record.fileName = getEntity().getFileName();
record.filePath = getEntity().getFilePath();
record.threadRecords = new ArrayList<>();
record.threadNum = threadNum;
record.isBlock = threadNum > 1;
record.taskType = ITaskWrapper.D_SFTP;
record.isGroupRecord = false;
return record;
}
@Override public int initTaskThreadNum() {
int threadNum = Configuration.getInstance().downloadCfg.getThreadNum();
return getFileSize() <= IRecordHandler.SUB_LEN
|| threadNum == 1
? 1
: threadNum;
}
}

@ -24,8 +24,8 @@ import com.arialyy.aria.core.download.DTaskWrapper;
import com.arialyy.aria.core.loader.AbsNormalTTBuilderAdapter;
import com.arialyy.aria.core.loader.IRecordHandler;
import com.arialyy.aria.core.task.IThreadTaskAdapter;
import com.arialyy.aria.ftp.FtpTaskOption;
import com.arialyy.aria.sftp.SFtpSessionManager;
import com.arialyy.aria.sftp.SFtpTaskOption;
import com.arialyy.aria.sftp.SFtpUtil;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil;
@ -35,11 +35,11 @@ import com.jcraft.jsch.Session;
import java.io.File;
import java.io.UnsupportedEncodingException;
class SFtpDTTBuilderAdapter extends AbsNormalTTBuilderAdapter {
private FtpTaskOption option;
final class SFtpDTTBuilderAdapter extends AbsNormalTTBuilderAdapter {
private SFtpTaskOption option;
SFtpDTTBuilderAdapter(DTaskWrapper wrapper) {
option = (FtpTaskOption) wrapper.getTaskOption();
option = (SFtpTaskOption) wrapper.getTaskOption();
}
@Override public IThreadTaskAdapter getAdapter(SubThreadConfig config) {
@ -71,6 +71,7 @@ class SFtpDTTBuilderAdapter extends AbsNormalTTBuilderAdapter {
}
@Override public boolean handleNewTask(TaskRecord record, int totalThreadNum) {
if (!record.isBlock) {
if (getTempFile().exists()) {
FileUtil.deleteFile(getTempFile());

@ -18,18 +18,22 @@ package com.arialyy.aria.sftp.download;
import com.arialyy.aria.core.common.SubThreadConfig;
import com.arialyy.aria.core.task.AbsThreadTaskAdapter;
import com.arialyy.aria.exception.AriaException;
import com.arialyy.aria.ftp.FtpTaskOption;
import com.arialyy.aria.sftp.SFtpTaskOption;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil;
import com.arialyy.aria.util.FileUtil;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.SftpException;
import com.jcraft.jsch.SftpProgressMonitor;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
/**
* sftp 线程任务适配器
@ -39,12 +43,12 @@ import java.nio.channels.FileChannel;
final class SFtpDThreadTaskAdapter extends AbsThreadTaskAdapter {
private ChannelSftp channelSftp;
private Session session;
private FtpTaskOption option;
private SFtpTaskOption option;
SFtpDThreadTaskAdapter(SubThreadConfig config) {
super(config);
session = (Session) config.obj;
option = (FtpTaskOption) getTaskWrapper().getTaskOption();
option = (SFtpTaskOption) getTaskWrapper().getTaskOption();
}
@Override protected void handlerThreadTask() {
@ -52,7 +56,6 @@ final class SFtpDThreadTaskAdapter extends AbsThreadTaskAdapter {
fail(new AriaException(TAG, "session 为空"), false);
return;
}
FileOutputStream fos;
try {
int timeout = getTaskConfig().getConnectTimeOut();
if (!session.isConnected()) {
@ -60,10 +63,6 @@ final class SFtpDThreadTaskAdapter extends AbsThreadTaskAdapter {
}
channelSftp = (ChannelSftp) session.openChannel("sftp");
channelSftp.connect(timeout);
fos = new FileOutputStream(getThreadConfig().tempFile, true);
if (channelSftp.isClosed() || !channelSftp.isConnected()) {
channelSftp.connect();
}
ALog.d(TAG,
String.format("任务【%s】线程__%s__开始下载【开始位置 : %s,结束位置:%s】", getTaskWrapper().getKey(),
getThreadRecord().threadId, getThreadRecord().startLocation,
@ -72,26 +71,66 @@ final class SFtpDThreadTaskAdapter extends AbsThreadTaskAdapter {
// 开启服务器对UTF-8的支持,如果服务器支持就用UTF-8编码
String charSet = option.getCharSet();
String remotePath =
CommonUtil.convertFtpChar(charSet, option.getUrlEntity().remotePath);
if (getThreadRecord().startLocation > 0) {
channelSftp.get(remotePath, fos, new Monitor(true), ChannelSftp.RESUME,
getThreadRecord().startLocation);
} else {
channelSftp.get(remotePath, fos, new Monitor(false));
}
} catch (Exception e) {
CommonUtil.convertSFtpChar(charSet, option.getUrlEntity().remotePath);
download(remotePath);
} catch (SftpException e) {
e.printStackTrace();
ALog.e(TAG, "错误类型:" + e.id);
fail(null, false);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
fail(null, false);
} catch (IOException e) {
e.printStackTrace();
fail(null, true);
} catch (JSchException e) {
e.printStackTrace();
fail(null, false);
} finally {
channelSftp.disconnect();
}
}
private class Monitor implements SftpProgressMonitor {
/**
* 下载
*/
private void download(String remotePath) throws SftpException, IOException {
InputStream is =
channelSftp.get(remotePath, new Monitor(), getThreadRecord().startLocation);
FileOutputStream fos = new FileOutputStream(getThreadConfig().tempFile, true);
FileChannel foc = fos.getChannel();
ReadableByteChannel fic = Channels.newChannel(is);
ByteBuffer bf = ByteBuffer.allocate(getTaskConfig().getBuffSize());
int len;
while (getThreadTask().isLive() && (len = fic.read(bf)) != -1) {
if (getThreadTask().isBreak()) {
break;
}
if (mSpeedBandUtil != null) {
mSpeedBandUtil.limitNextBytes(len);
}
if (getRangeProgress() + len >= getThreadRecord().endLocation) {
len = (int) (getThreadRecord().endLocation - getRangeProgress());
bf.flip();
fos.write(bf.array(), 0, len);
bf.compact();
progress(len);
break;
} else {
bf.flip();
foc.write(bf);
bf.compact();
progress(len);
}
}
fos.flush();
fos.close();
is.close();
}
private boolean isResume;
private class Monitor implements SftpProgressMonitor {
private Monitor(boolean isResume) {
this.isResume = isResume;
private Monitor() {
}
@Override public void init(int op, String src, String dest, long max) {
@ -104,19 +143,10 @@ final class SFtpDThreadTaskAdapter extends AbsThreadTaskAdapter {
*/
@Override public boolean count(long count) {
if (mSpeedBandUtil != null) {
mSpeedBandUtil.limitNextBytes((int) count);
}
/*
* jsch 如果是恢复任务第一次回调count会将已下载的长度返回后面才是新增的文件长度
* 所以恢复任务的话需要忽略一次回调
*/
if (!isResume) {
progress(count);
}
isResume = false;
//return !getThreadTask().isBreak() && getRangeProgress() < getThreadRecord().endLocation;
if (getRangeProgress() > getThreadRecord().endLocation) {
return false;
}
@ -129,62 +159,6 @@ final class SFtpDThreadTaskAdapter extends AbsThreadTaskAdapter {
}
complete();
//boolean isSuccess = true;
//// 剪裁文件
//if (getRangeProgress() > getThreadRecord().endLocation) {
// isSuccess = clipFile();
//}
//if (isSuccess) {
// complete();
//} else {
// fail(new AriaException(TAG, "剪切文件失败"), false);
//}
}
/**
* 文件超出内容剪切文件
*
* @return true 剪切文件成功
*/
private boolean clipFile() {
FileInputStream fis = null;
FileOutputStream fos = null;
long stime = System.currentTimeMillis();
try {
String destPath = getThreadConfig().tempFile.getPath();
ALog.d(TAG, "oldSize = " + getThreadConfig().tempFile.length());
String tempPath = destPath + "_temp";
fis = new FileInputStream(getThreadConfig().tempFile);
fos = new FileOutputStream(tempPath);
FileChannel inChannel = fis.getChannel();
FileChannel outChannel = fos.getChannel();
inChannel.transferTo(0, getThreadRecord().endLocation, outChannel);
FileUtil.deleteFile(getThreadConfig().tempFile);
File oldF = new File(tempPath);
File newF = new File(destPath);
boolean b = oldF.renameTo(newF);
ALog.d(TAG, String.format("剪裁文件消耗:%sms,fileSize:%s,threadId:%s",
(System.currentTimeMillis() - stime), newF.length(),
getThreadConfig().record.threadId));
return b;
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fis != null) {
fis.close();
}
if (fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return false;
}
}
}

@ -0,0 +1,69 @@
/*
* 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.sftp.upload;
import com.arialyy.aria.core.common.CompleteInfo;
import com.arialyy.aria.core.upload.UTaskWrapper;
import com.arialyy.aria.core.upload.UploadEntity;
import com.arialyy.aria.sftp.AbsSFtpInfoTask;
import com.arialyy.aria.sftp.SFtpTaskOption;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.SftpATTRS;
import com.jcraft.jsch.SftpException;
import java.io.UnsupportedEncodingException;
final class SFtpUInfoTask extends AbsSFtpInfoTask<UTaskWrapper> {
static final int ISCOMPLETE = 0xa1;
SFtpUInfoTask(UTaskWrapper uTaskWrapper) {
super(uTaskWrapper);
}
@Override protected void getFileInfo(Session session)
throws JSchException, UnsupportedEncodingException, SftpException {
SFtpTaskOption option = (SFtpTaskOption) getWrapper().getTaskOption();
ChannelSftp channel = (ChannelSftp) session.openChannel("sftp");
channel.connect(1000);
String remotePath = option.getUrlEntity().remotePath;
String temp = CommonUtil.convertSFtpChar(getOption().getCharSet(), remotePath)
+ "/"
+ getWrapper().getEntity().getFileName();
SftpATTRS attr = null;
try {
attr = channel.stat(temp);
} catch (Exception e) {
ALog.d(TAG, String.format("文件不存在,remotePath:%s", remotePath));
}
boolean isComplete = false;
UploadEntity entity = getWrapper().getEntity();
if (attr != null && attr.getSize() == entity.getFileSize()) {
isComplete = true;
}
CompleteInfo info = new CompleteInfo();
info.code = isComplete ? ISCOMPLETE : 200;
info.obj = attr;
channel.disconnect();
callback.onSucceed(getWrapper().getKey(), info);
}
}

@ -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.sftp.upload;
import android.os.Handler;
import android.os.Looper;
import com.arialyy.aria.core.common.AbsEntity;
import com.arialyy.aria.core.common.CompleteInfo;
import com.arialyy.aria.core.event.EventMsgUtil;
import com.arialyy.aria.core.inf.IThreadStateManager;
import com.arialyy.aria.core.listener.IDLoadListener;
import com.arialyy.aria.core.listener.IEventListener;
import com.arialyy.aria.core.loader.AbsNormalLoader;
import com.arialyy.aria.core.loader.IInfoTask;
import com.arialyy.aria.core.loader.IRecordHandler;
import com.arialyy.aria.core.loader.IThreadTaskBuilder;
import com.arialyy.aria.core.manager.ThreadTaskManager;
import com.arialyy.aria.core.task.IThreadTask;
import com.arialyy.aria.core.upload.UTaskWrapper;
import com.arialyy.aria.core.upload.UploadEntity;
import com.arialyy.aria.exception.BaseException;
import com.arialyy.aria.util.FileUtil;
import com.jcraft.jsch.SftpATTRS;
import java.io.File;
final class SFtpULoader extends AbsNormalLoader<UTaskWrapper> {
private Looper looper;
SFtpULoader(UTaskWrapper wrapper, IEventListener listener) {
super(wrapper, listener);
mTempFile = new File(getEntity().getFilePath());
EventMsgUtil.getDefault().register(this);
setUpdateInterval(wrapper.getConfig().getUpdateInterval());
}
private UploadEntity getEntity() {
return mTaskWrapper.getEntity();
}
@Override public long getFileSize() {
return getEntity().getFileSize();
}
/**
* 设置最大下载/上传速度AbsFtpInfoThread
*
* @param maxSpeed 单位为kb
*/
protected void setMaxSpeed(int maxSpeed) {
for (IThreadTask threadTask : getTaskList()) {
if (threadTask != null) {
threadTask.setMaxSpeed(maxSpeed);
}
}
}
@Override public void onDestroy() {
super.onDestroy();
EventMsgUtil.getDefault().unRegister(this);
}
/**
* 启动单线程任务
*/
@Override
public void handleTask(Looper looper) {
if (isBreak()) {
return;
}
this.looper = looper;
mInfoTask.run();
}
private void startThreadTask(SftpATTRS attrs) {
if (getListener() instanceof IDLoadListener) {
((IDLoadListener) getListener()).onPostPre(getEntity().getFileSize());
}
File file = new File(getEntity().getFilePath());
if (file.getParentFile() != null && !file.getParentFile().exists()) {
FileUtil.createDir(file.getPath());
}
// 处理记录、初始化状态管理器
SFtpURecordHandler recordHandler = (SFtpURecordHandler) mRecordHandler;
recordHandler.setFtpAttrs(attrs);
mRecord = recordHandler.getRecord(getFileSize());
mStateManager.setLooper(mRecord, looper);
// 创建线程任务
getTaskList().addAll(mTTBuilder.buildThreadTask(mRecord,
new Handler(looper, mStateManager.getHandlerCallback())));
mStateManager.updateCurrentProgress(getEntity().getCurrentProgress());
if (mStateManager.getCurrentProgress() > 0) {
getListener().onResume(mStateManager.getCurrentProgress());
} else {
getListener().onStart(mStateManager.getCurrentProgress());
}
// 启动线程任务
for (IThreadTask threadTask : getTaskList()) {
ThreadTaskManager.getInstance().startThread(mTaskWrapper.getKey(), threadTask);
}
// 启动定时器
startTimer();
}
@Override public long getCurrentProgress() {
return isRunning() ? mStateManager.getCurrentProgress() : getEntity().getCurrentProgress();
}
@Override public void addComponent(IRecordHandler recordHandler) {
mRecordHandler = recordHandler;
}
@Override public void addComponent(IInfoTask infoTask) {
mInfoTask = infoTask;
infoTask.setCallback(new IInfoTask.Callback() {
@Override public void onSucceed(String key, CompleteInfo info) {
if (info.code == SFtpUInfoTask.ISCOMPLETE) {
getListener().onComplete();
} else {
startThreadTask((SftpATTRS) info.obj);
}
}
@Override public void onFail(AbsEntity entity, BaseException e, boolean needRetry) {
getListener().onFail(needRetry, e);
}
});
}
@Override public void addComponent(IThreadStateManager threadState) {
mStateManager = threadState;
}
@Override public void addComponent(IThreadTaskBuilder builder) {
mTTBuilder = builder;
}
}

@ -0,0 +1,55 @@
/*
* 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.sftp.upload;
import com.arialyy.aria.core.listener.IEventListener;
import com.arialyy.aria.core.loader.AbsNormalLoader;
import com.arialyy.aria.core.loader.AbsNormalLoaderUtil;
import com.arialyy.aria.core.loader.LoaderStructure;
import com.arialyy.aria.core.loader.NormalTTBuilder;
import com.arialyy.aria.core.loader.NormalThreadStateManager;
import com.arialyy.aria.core.upload.UTaskWrapper;
import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
import com.arialyy.aria.sftp.SFtpTaskOption;
/**
* sftp下载工具
*
* @author lyy
*/
public class SFtpULoaderUtil extends AbsNormalLoaderUtil {
public SFtpULoaderUtil(AbsTaskWrapper wrapper, IEventListener listener) {
super(wrapper, listener);
wrapper.generateTaskOption(SFtpTaskOption.class);
}
@Override public AbsNormalLoader getLoader() {
return mLoader == null ? new SFtpULoader((UTaskWrapper) getTaskWrapper(), getListener())
: mLoader;
}
@Override public LoaderStructure BuildLoaderStructure() {
LoaderStructure structure = new LoaderStructure();
structure.addComponent(new SFtpURecordHandler((UTaskWrapper) getTaskWrapper()))
.addComponent(new NormalThreadStateManager(getListener()))
.addComponent(new SFtpUInfoTask((UTaskWrapper) getTaskWrapper()))
.addComponent(new NormalTTBuilder(getTaskWrapper(), new SFtpUTTBuilderAdapter(
(UTaskWrapper) getTaskWrapper())));
structure.accept(getLoader());
return structure;
}
}

@ -0,0 +1,115 @@
/*
* 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.sftp.upload;
import com.arialyy.aria.core.TaskRecord;
import com.arialyy.aria.core.ThreadRecord;
import com.arialyy.aria.core.common.RecordHandler;
import com.arialyy.aria.core.upload.UTaskWrapper;
import com.arialyy.aria.core.upload.UploadEntity;
import com.arialyy.aria.core.wrapper.ITaskWrapper;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.RecordUtil;
import com.jcraft.jsch.SftpATTRS;
import java.util.ArrayList;
/**
* 上传任务记录处理器
*/
final class SFtpURecordHandler extends RecordHandler {
private SftpATTRS ftpAttrs;
SFtpURecordHandler(UTaskWrapper wrapper) {
super(wrapper);
}
void setFtpAttrs(SftpATTRS ftpAttrs) {
this.ftpAttrs = ftpAttrs;
}
@Override public void handlerTaskRecord(TaskRecord record) {
if (record.threadRecords == null || record.threadRecords.isEmpty()) {
record.threadRecords = new ArrayList<>();
record.threadRecords.add(
createThreadRecord(record, 0, ftpAttrs == null ? 0 : ftpAttrs.getSize(), getFileSize()));
}
if (ftpAttrs != null) {
UploadEntity entity = (UploadEntity) getWrapper().getEntity();
//远程文件已完成
if (ftpAttrs.getSize() == getFileSize()) {
record.threadRecords.get(0).isComplete = true;
ALog.d(TAG, "FTP服务器上已存在该文件【" + entity.getFileName() + "】");
} else if (ftpAttrs.getSize() == 0) {
getWrapper().setNewTask(true);
ALog.d(TAG, "FTP服务器上已存在该文件【" + entity.getFileName() + "】,但文件长度为0,重新上传该文件");
} else {
ALog.w(TAG, "FTP服务器已存在未完成的文件【"
+ entity.getFileName()
+ ",size: "
+ ftpAttrs.getSize()
+ "】"
+ "尝试从位置:"
+ (ftpAttrs.getSize() - 1)
+ "开始上传");
getWrapper().setNewTask(false);
// 修改记录
ThreadRecord threadRecord = record.threadRecords.get(0);
//修改本地保存的停止地址为服务器上对应文件的大小
threadRecord.startLocation = ftpAttrs.getSize() - 1;
}
} else {
ALog.d(TAG, "SFTP服务器上不存在该文件");
getWrapper().setNewTask(true);
ThreadRecord tr = record.threadRecords.get(0);
tr.startLocation = 0;
tr.endLocation = getFileSize();
tr.isComplete = false;
}
}
@Override
public ThreadRecord createThreadRecord(TaskRecord record, int threadId, long startL, long endL) {
ThreadRecord tr;
tr = new ThreadRecord();
tr.taskKey = record.filePath;
tr.threadId = threadId;
tr.startLocation = startL;
tr.isComplete = false;
tr.threadType = record.taskType;
tr.endLocation = getFileSize();
tr.blockLen = RecordUtil.getBlockLen(getFileSize(), threadId, record.threadNum);
return tr;
}
@Override public TaskRecord createTaskRecord(int threadNum) {
TaskRecord record = new TaskRecord();
record.fileName = getEntity().getFileName();
record.filePath = getEntity().getFilePath();
record.threadRecords = new ArrayList<>();
record.threadNum = threadNum;
record.isBlock = false;
record.taskType = ITaskWrapper.U_SFTP;
record.isGroupRecord = getEntity().isGroupChild();
return record;
}
@Override public int initTaskThreadNum() {
return 1;
}
}

@ -0,0 +1,73 @@
/*
* 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.sftp.upload;
import android.os.Handler;
import com.arialyy.aria.core.FtpUrlEntity;
import com.arialyy.aria.core.TaskRecord;
import com.arialyy.aria.core.ThreadRecord;
import com.arialyy.aria.core.common.SubThreadConfig;
import com.arialyy.aria.core.loader.AbsNormalTTBuilderAdapter;
import com.arialyy.aria.core.task.IThreadTaskAdapter;
import com.arialyy.aria.core.upload.UTaskWrapper;
import com.arialyy.aria.sftp.SFtpSessionManager;
import com.arialyy.aria.sftp.SFtpTaskOption;
import com.arialyy.aria.sftp.SFtpUtil;
import com.arialyy.aria.util.CommonUtil;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import java.io.UnsupportedEncodingException;
final class SFtpUTTBuilderAdapter extends AbsNormalTTBuilderAdapter {
private SFtpTaskOption option;
SFtpUTTBuilderAdapter(UTaskWrapper wrapper) {
option = (SFtpTaskOption) wrapper.getTaskOption();
}
@Override public IThreadTaskAdapter getAdapter(SubThreadConfig config) {
return new SFtpUThreadTaskAdapter(config);
}
@Override
protected SubThreadConfig getSubThreadConfig(Handler stateHandler, ThreadRecord threadRecord,
boolean isBlock, int startNum) {
SubThreadConfig config =
super.getSubThreadConfig(stateHandler, threadRecord, isBlock, startNum);
FtpUrlEntity entity = option.getUrlEntity();
String key =
CommonUtil.getStrMd5(entity.hostName + entity.port + entity.user + threadRecord.threadId);
Session session = SFtpSessionManager.getInstance().getSession(key);
if (session == null) {
try {
session = SFtpUtil.getInstance().getSession(entity, threadRecord.threadId);
} catch (JSchException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
config.obj = session;
return config;
}
@Override public boolean handleNewTask(TaskRecord record, int totalThreadNum) {
return true;
}
}

@ -0,0 +1,201 @@
/*
* 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.sftp.upload;
import com.arialyy.aria.core.common.SubThreadConfig;
import com.arialyy.aria.core.task.AbsThreadTaskAdapter;
import com.arialyy.aria.core.upload.UploadEntity;
import com.arialyy.aria.exception.AriaException;
import com.arialyy.aria.sftp.SFtpTaskOption;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.BufferedRandomAccessFile;
import com.arialyy.aria.util.CommonUtil;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.SftpException;
import com.jcraft.jsch.SftpProgressMonitor;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
/**
* sftp 线程任务适配器
*
* @author lyy
*/
final class SFtpUThreadTaskAdapter extends AbsThreadTaskAdapter {
private ChannelSftp channelSftp;
private Session session;
private SFtpTaskOption option;
SFtpUThreadTaskAdapter(SubThreadConfig config) {
super(config);
session = (Session) config.obj;
option = (SFtpTaskOption) getTaskWrapper().getTaskOption();
}
@Override protected void handlerThreadTask() {
if (session == null) {
fail(new AriaException(TAG, "session 为空"), false);
return;
}
try {
ALog.d(TAG,
String.format("任务【%s】线程__%s__开始上传【开始位置 : %s,结束位置:%s】", getTaskWrapper().getKey(),
getThreadRecord().threadId, getThreadRecord().startLocation,
getThreadRecord().endLocation));
int timeout = getTaskConfig().getConnectTimeOut();
if (!session.isConnected()) {
session.connect(timeout);
}
// 开启服务器对UTF-8的支持,如果服务器支持就用UTF-8编码
String charSet = option.getCharSet();
String remotePath =
CommonUtil.convertSFtpChar(charSet, option.getUrlEntity().remotePath);
channelSftp = (ChannelSftp) session.openChannel("sftp");
channelSftp.connect(timeout);
if (!dirIsExist(remotePath)) {
createDir(remotePath);
}
channelSftp.cd(remotePath);
upload(remotePath);
} catch (JSchException e) {
e.printStackTrace();
fail(null, false);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
fail(null, false);
} catch (IOException e) {
e.printStackTrace();
fail(null, true);
} catch (SftpException e) {
e.printStackTrace();
fail(null, false);
ALog.d(TAG, "错误类型:" + e.id);
} finally {
channelSftp.disconnect();
}
}
/**
* 远程文件夹是否存在
*
* @return true 文件夹存在
*/
private boolean dirIsExist(String remotePath) {
try {
channelSftp.ls(remotePath);
} catch (SftpException e) {
return false;
}
return true;
}
/**
* 创建文件夹
*
* @throws SftpException
*/
private void createDir(String remotePath) throws SftpException {
String[] folders = remotePath.split("/");
for (String folder : folders) {
if (folder.length() > 0) {
try {
channelSftp.cd(folder);
} catch (SftpException e) {
channelSftp.mkdir(folder);
channelSftp.cd(folder);
}
}
}
}
/**
* 恢复上传
*
* @throws SftpException
* @throws IOException
*/
private void upload(String remotePath) throws SftpException, IOException {
UploadEntity entity = (UploadEntity) getTaskWrapper().getEntity();
remotePath = remotePath.concat("/").concat(entity.getFileName());
BufferedRandomAccessFile brf = new BufferedRandomAccessFile(getThreadConfig().tempFile, "r");
int mode = ChannelSftp.OVERWRITE;
boolean isResume = false;
if (getThreadRecord().startLocation > 0) {
brf.seek(getThreadRecord().startLocation);
mode = ChannelSftp.APPEND;
isResume = true;
}
OutputStream os = channelSftp.put(remotePath, new Monitor(isResume), mode);
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = brf.read(buffer)) != -1) {
if (getThreadTask().isBreak()) {
break;
}
os.write(buffer, 0, bytesRead);
if (mSpeedBandUtil != null) {
mSpeedBandUtil.limitNextBytes(bytesRead);
}
}
os.flush();
os.close();
brf.close();
}
private class Monitor implements SftpProgressMonitor {
private boolean isResume;
private Monitor(boolean isResume) {
this.isResume = isResume;
}
@Override public void init(int op, String src, String dest, long max) {
ALog.d(TAG, String.format("op = %s; src = %s; dest = %s; max = %s", op, src, dest, max));
}
/**
* @param count 已传输的数据
* @return false 取消任务
*/
@Override public boolean count(long count) {
/*
* jsch 如果是恢复任务第一次回调count会将已下载的长度返回后面才是新增的文件长度
* 所以恢复任务的话需要忽略一次回调
*/
if (!isResume) {
progress(count);
}
isResume = false;
return !getThreadTask().isBreak();
}
@Override public void end() {
if (getThreadTask().isBreak()) {
return;
}
complete();
}
}
}

@ -4,11 +4,6 @@
android:sharedUserId="com.arialyy"
package="com.arialyy.simple">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<application
android:name="com.arialyy.simple.base.BaseApplication"
android:allowBackup="true"
@ -26,35 +21,34 @@
android:name=".MainActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER"/>
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".core.download.SFtpDownloadActivity"/>
<activity android:name=".core.download.DownloadActivity"/>
<activity android:name=".core.download.SingleTaskActivity"/>
<activity android:name=".core.download.mutil.MultiTaskActivity"/>
<activity android:name=".core.download.fragment.FragmentActivity"/>
<activity android:name=".core.download.mutil.MultiDownloadActivity"/>
<activity android:name=".core.download.HighestPriorityActivity"/>
<activity android:name=".core.download.group.DownloadGroupActivity"/>
<activity android:name=".core.download.FtpDownloadActivity"/>
<activity android:name=".core.download.group.FTPDirDownloadActivity"/>
<activity android:name=".core.upload.HttpUploadActivity"/>
<activity android:name=".core.upload.FtpUploadActivity"/>
<activity android:name=".core.download.KotlinDownloadActivity"/>
<activity android:name=".core.download.ComponentActivity"/>
<activity android:name=".core.upload.SFtpUploadActivity" />
<activity android:name=".core.download.SFtpDownloadActivity" />
<activity android:name=".core.download.DownloadActivity" />
<activity android:name=".core.download.SingleTaskActivity" />
<activity android:name=".core.download.mutil.MultiTaskActivity" />
<activity android:name=".core.download.fragment.FragmentActivity" />
<activity android:name=".core.download.mutil.MultiDownloadActivity" />
<activity android:name=".core.download.HighestPriorityActivity" />
<activity android:name=".core.download.group.DownloadGroupActivity" />
<activity android:name=".core.download.FtpDownloadActivity" />
<activity android:name=".core.download.group.FTPDirDownloadActivity" />
<activity android:name=".core.upload.HttpUploadActivity" />
<activity android:name=".core.upload.FtpUploadActivity" />
<activity android:name=".core.download.KotlinDownloadActivity" />
<activity android:name=".core.download.ComponentActivity" />
<activity
android:name=".core.FullScreenCodeActivity"
android:screenOrientation="landscape"
android:theme="@style/FullScreen"
android:windowSoftInputMode="stateHidden|adjustResize"/>
<activity android:name=".core.download.m3u8.M3U8VodDLoadActivity"/>
<activity android:name=".core.download.m3u8.M3U8LiveDLoadActivity"/>
<service android:name=".core.download.service.DownloadService"/>
android:windowSoftInputMode="stateHidden|adjustResize" />
<activity android:name=".core.download.m3u8.M3U8VodDLoadActivity" />
<activity android:name=".core.download.m3u8.M3U8LiveDLoadActivity" />
<provider
android:name="androidx.core.content.FileProvider"
@ -63,8 +57,15 @@
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/aria_fileprovider_paths"/>
android:resource="@xml/aria_fileprovider_paths" />
</provider>
<service android:name=".core.download.service.DownloadService" />
</application>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
</manifest>

@ -32,7 +32,7 @@
3、只对新的多线程下载任务有效
4、只对多线程的任务有效
-->
<useBlock value="true"/>
<useBlock value="false"/>
<!--设置下载线程数,下载线程数不能小于1
注意:

@ -45,6 +45,7 @@ import com.arialyy.simple.core.download.m3u8.M3U8LiveDLoadActivity;
import com.arialyy.simple.core.download.m3u8.M3U8VodDLoadActivity;
import com.arialyy.simple.core.upload.FtpUploadActivity;
import com.arialyy.simple.core.upload.HttpUploadActivity;
import com.arialyy.simple.core.upload.SFtpUploadActivity;
import com.arialyy.simple.databinding.ActivityMainBinding;
import com.arialyy.simple.modlue.CommonModule;
import com.arialyy.simple.to.NormalTo;
@ -117,10 +118,14 @@ public class MainActivity extends BaseActivity<ActivityMainBinding> {
module.startNextActivity(MainActivity.this, data.get(position),
M3U8LiveDLoadActivity.class);
break;
case 8: // sftp
case 8: // d_sftp
module.startNextActivity(MainActivity.this, data.get(position),
SFtpDownloadActivity.class);
break;
case 9: // u_sftp
module.startNextActivity(MainActivity.this, data.get(position),
SFtpUploadActivity.class);
break;
}
}
});

@ -23,7 +23,7 @@ import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProviders;
import com.arialyy.annotations.Download;
import com.arialyy.aria.core.Aria;
import com.arialyy.aria.core.common.FtpOption;
import com.arialyy.aria.core.common.SFtpOption;
import com.arialyy.aria.core.download.DownloadEntity;
import com.arialyy.aria.core.inf.IEntity;
import com.arialyy.aria.core.task.DownloadTask;
@ -105,7 +105,6 @@ public class SFtpDownloadActivity extends BaseActivity<ActivitySftpDownloadBindi
if (!pubKey.exists()) {
FileUtil.createFileFormInputStream(getAssets().open("id_rsa.pub"), pubKeyPath);
}
} catch (IOException e) {
e.printStackTrace();
}
@ -144,8 +143,8 @@ public class SFtpDownloadActivity extends BaseActivity<ActivitySftpDownloadBindi
}
}
private FtpOption getFtpOption() {
FtpOption option = new FtpOption();
private SFtpOption getFtpOption() {
SFtpOption option = new SFtpOption();
option.login(user, passw); // 账号密码登录
// 证书登录
option.setPrvKey(prvKeyPath); // 设置私钥

@ -237,6 +237,7 @@ public class SingleTaskActivity extends BaseActivity<ActivitySingleBinding> {
@Download.onTaskFail
void taskFail(DownloadTask task, Exception e) {
ALog.d(TAG, "下载失败");
Toast.makeText(SingleTaskActivity.this, getString(R.string.download_fail), Toast.LENGTH_SHORT)
.show();
if (task != null && task.getKey().equals(mUrl)) {

@ -22,6 +22,7 @@ import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.SeekBar;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentTransaction;
@ -76,6 +77,9 @@ public class M3U8VodDLoadActivity extends BaseActivity<ActivityM3u8VodBinding> {
if (entity == null) {
return;
}
if (entity.getM3U8Entity() != null){
getBinding().pb.setMax(entity.getM3U8Entity().getPeerNum());
}
mTaskId = entity.getId();
if (entity.getState() == IEntity.STATE_STOP) {
getBinding().setStateStr(getString(R.string.resume));
@ -98,6 +102,19 @@ public class M3U8VodDLoadActivity extends BaseActivity<ActivityM3u8VodBinding> {
}
});
getBinding().setViewModel(this);
getBinding().pb.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
}
@Override public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override public void onStopTrackingTouch(SeekBar seekBar) {
Aria.download(this).load(mTaskId).m3u8VodOption().jumPeerIndex(seekBar.getProgress());
}
});
}
public void chooseUrl() {
@ -202,7 +219,7 @@ public class M3U8VodDLoadActivity extends BaseActivity<ActivityM3u8VodBinding> {
@Download.onTaskStart
void taskStart(DownloadTask task) {
if (task.getKey().equals(mUrl)) {
getBinding().setFileSize(task.getConvertFileSize());
getBinding().pb.setMax(task.getEntity().getM3U8Entity().getPeerNum());
ALog.d(TAG, "isComplete = " + task.isComplete() + ", state = " + task.getState());
}
}
@ -364,7 +381,7 @@ public class M3U8VodDLoadActivity extends BaseActivity<ActivityM3u8VodBinding> {
static class KeyUrlConverter implements IKeyUrlConverter{
@Override public String convert(String keyUrl) {
@Override public String convert(String m3u8Url, String keyUrl) {
ALog.d("TAG", "convertUrl....");
return null;
}

@ -139,7 +139,7 @@ public class FtpUploadActivity extends BaseActivity<ActivityFtpUploadBinding> {
//upload();
break;
case R.id.cancel:
Aria.upload(this).loadFtp(mTaskId).cancel();
Aria.upload(this).loadFtp(mTaskId).cancel(false);
mTaskId = -1;
getBinding().setStateStr(getString(R.string.start));
break;

@ -0,0 +1,239 @@
/*
* 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.simple.core.upload;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.View;
import androidx.annotation.Nullable;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProviders;
import com.arialyy.annotations.Upload;
import com.arialyy.aria.core.Aria;
import com.arialyy.aria.core.common.SFtpOption;
import com.arialyy.aria.core.inf.IEntity;
import com.arialyy.aria.core.task.UploadTask;
import com.arialyy.aria.core.upload.UploadEntity;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil;
import com.arialyy.frame.util.FileUtil;
import com.arialyy.frame.util.show.T;
import com.arialyy.simple.R;
import com.arialyy.simple.base.BaseActivity;
import com.arialyy.simple.common.ModifyUrlDialog;
import com.arialyy.simple.databinding.ActivitySftpUploadBinding;
import com.arialyy.simple.util.AppUtil;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
/**
* Created by lyy on 2017/7/28. Ftp 文件上传demo
*/
public class SFtpUploadActivity extends BaseActivity<ActivitySftpUploadBinding> {
private final int OPEN_FILE_MANAGER_CODE = 0xB1;
private String mFilePath;
private String mUrl;
private UploadModule mModule;
private long mTaskId = -1;
private String user = "tester", pwd = "password";
@Override protected void init(Bundle savedInstanceState) {
setTile("D_FTP 文件上传");
super.init(savedInstanceState);
Aria.upload(this).register();
getBinding().setViewModel(this);
setUI();
}
private void setUI() {
mModule = ViewModelProviders.of(this).get(UploadModule.class);
mModule.getSFtpInfo(this).observe(this, new Observer<UploadEntity>() {
@Override public void onChanged(@Nullable UploadEntity entity) {
if (entity != null) {
mTaskId = entity.getId();
if (entity.getFileSize() != 0) {
getBinding().setFileSize(CommonUtil.formatFileSize(entity.getFileSize()));
getBinding().setProgress(entity.isComplete() ? 100
: (int) (entity.getCurrentProgress() * 100 / entity.getFileSize()));
}
getBinding().setUrl(entity.getUrl());
getBinding().setFilePath(entity.getFilePath());
mUrl = entity.getUrl();
mFilePath = entity.getFilePath();
getBinding().setStateStr(getString(
entity.getState() == IEntity.STATE_RUNNING ? R.string.stop : R.string.start));
} else {
getBinding().setStateStr(getString(R.string.resume));
}
}
});
setHelpCode();
}
private void setHelpCode() {
//try {
// getBinding().codeView.setSource(AppUtil.getHelpCode(this, "FtpUpload.java"));
//} catch (IOException e) {
// e.printStackTrace();
//}
}
@Override protected int setLayoutId() {
return R.layout.activity_sftp_upload;
}
public void chooseUrl() {
ModifyUrlDialog dialog =
new ModifyUrlDialog(this, getString(R.string.modify_url_dialog_title), mUrl);
dialog.show(getSupportFragmentManager(), "ModifyUrlDialog");
}
public void chooseFilePath() {
AppUtil.chooseFile(this, new File(mFilePath), null, OPEN_FILE_MANAGER_CODE);
}
public void onClick(View view) {
switch (view.getId()) {
case R.id.start:
if (mTaskId == -1) {
mTaskId = Aria.upload(this)
.loadFtp(mFilePath)
.setUploadUrl(mUrl)
.sftpOption(getOption())
.ignoreFilePathOccupy()
.create();
getBinding().setStateStr(getString(R.string.stop));
break;
}
if (Aria.upload(this).loadFtp(mTaskId).isRunning()) {
Aria.upload(this).loadFtp(mTaskId).stop();
getBinding().setStateStr(getString(R.string.resume));
} else {
Aria.upload(this)
.loadFtp(mTaskId)
.sftpOption(getOption())
.resume();
getBinding().setStateStr(getString(R.string.stop));
}
//upload();
break;
case R.id.cancel:
Aria.upload(this).loadFtp(mTaskId).cancel(false);
mTaskId = -1;
getBinding().setStateStr(getString(R.string.start));
break;
}
}
private void upload() {
List<String> paths = new ArrayList<>();
paths.add(Environment.getExternalStorageDirectory().getPath() + "/Download/img/img/1.jpg");
paths.add(Environment.getExternalStorageDirectory().getPath() + "/Download/img/img/2.jpg");
paths.add(Environment.getExternalStorageDirectory().getPath() + "/Download/img/img/3.jpg");
paths.add(Environment.getExternalStorageDirectory().getPath() + "/Download/img/img/4.jpg");
paths.add(Environment.getExternalStorageDirectory().getPath() + "/Download/img/img/5.jpg");
paths.add(Environment.getExternalStorageDirectory().getPath() + "/Download/img/img/6.jpg");
paths.add(Environment.getExternalStorageDirectory().getPath() + "/Download/img/img/7.jpg");
paths.add(Environment.getExternalStorageDirectory().getPath() + "/Download/img/img/8.jpg");
paths.add(Environment.getExternalStorageDirectory().getPath() + "/Download/img/img/9.jpg");
paths.add(Environment.getExternalStorageDirectory().getPath() + "/Download/img/img/10.jpg");
for (String path : paths) {
Aria.upload(this)
.loadFtp(path)
.setUploadUrl(mUrl)
.sftpOption(getOption())
.ignoreFilePathOccupy()
.create();
}
}
private SFtpOption getOption() {
SFtpOption option = new SFtpOption();
option.login(user, pwd);
return option;
}
@Upload.onWait void onWait(UploadTask task) {
Log.d(TAG, task.getTaskName() + "_wait");
}
@Upload.onPre public void onPre(UploadTask task) {
getBinding().setFileSize(task.getConvertFileSize());
}
@Upload.onTaskStart public void taskStart(UploadTask task) {
Log.d(TAG, "开始上传,md5:" + FileUtil.getFileMD5(new File(task.getEntity().getFilePath())));
}
@Upload.onTaskResume public void taskResume(UploadTask task) {
Log.d(TAG, "恢复上传");
}
@Upload.onTaskStop public void taskStop(UploadTask task) {
getBinding().setSpeed("");
Log.d(TAG, "停止上传");
}
@Upload.onTaskCancel public void taskCancel(UploadTask task) {
getBinding().setSpeed("");
getBinding().setFileSize("");
getBinding().setProgress(0);
Log.d(TAG, "删除任务");
}
@Upload.onTaskFail public void taskFail(UploadTask task) {
Log.d(TAG, "上传失败");
getBinding().setStateStr(getString(R.string.resume));
}
@Upload.onTaskRunning public void taskRunning(UploadTask task) {
Log.d(TAG, "PP = " + task.getPercent());
getBinding().setProgress(task.getPercent());
getBinding().setSpeed(task.getConvertSpeed());
}
@Upload.onTaskComplete public void taskComplete(UploadTask task) {
getBinding().setProgress(100);
getBinding().setSpeed("");
T.showShort(this, "文件:" + task.getEntity().getFileName() + ",上传完成");
getBinding().setStateStr(getString(R.string.re_start));
}
@Override protected void dataCallback(int result, Object data) {
super.dataCallback(result, data);
if (result == ModifyUrlDialog.MODIFY_URL_DIALOG_RESULT) {
mModule.updateFtpUrl(this, String.valueOf(data));
}
}
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == OPEN_FILE_MANAGER_CODE && resultCode == RESULT_OK) {
Uri uri = data.getData();
if (uri != null) {
mModule.updateFtpFilePath(this, uri.getPath());
ALog.d(TAG, String.format("选择的文件路径:%s", uri.getPath()));
} else {
ALog.d(TAG, "没有选择文件");
}
}
}
}

@ -56,6 +56,31 @@ public class UploadModule extends BaseViewModule {
return liveData;
}
/**
* 获取Ftp上传信息
*/
LiveData<UploadEntity> getSFtpInfo(Context context) {
//String url = AppUtil.getConfigValue(context, FTP_URL_KEY, "ftp://9.9.9.72:2121/aab/你好");
//String filePath = AppUtil.getConfigValue(context, FTP_PATH_KEY,
// Environment.getExternalStorageDirectory().getPath() + "/Download/AndroidAria.db");
String url = "ftp://9.9.9.72:22/aab/你好";
String filePath = "/mnt/sdcard/QQMusic-import-1.2.1.zip";
UploadEntity entity = Aria.upload(context).getFirstUploadEntity(filePath);
if (entity != null) {
uploadInfo = entity;
AppUtil.setConfigValue(context, FTP_URL_KEY, uploadInfo.getUrl());
AppUtil.setConfigValue(context, FTP_PATH_KEY, uploadInfo.getFilePath());
} else {
uploadInfo = new UploadEntity();
uploadInfo.setUrl(url);
uploadInfo.setFilePath(filePath);
}
liveData.postValue(uploadInfo);
return liveData;
}
/**
* 更新Url
*/

@ -104,6 +104,7 @@ public class CommonModule extends BaseViewModule {
R.drawable.ic_ftp,
R.drawable.ic_ts,
R.drawable.ic_live,
R.drawable.ic_sftp,
R.drawable.ic_sftp
};
int i = 0;

@ -44,14 +44,14 @@
tools:context=".core.download.SingleTaskActivity"
>
<include layout="@layout/layout_bar"/>
<include layout="@layout/layout_bar" />
<com.arialyy.simple.widget.SvgTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginTop="16dp"
android:layout_marginRight="16dp"
bind:iconClickListener="@{() -> viewModel.chooseUrl()}"
bind:svg_text_view_icon="@drawable/ic_modify"
bind:text="@{@string/url(url)}"
@ -61,8 +61,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginTop="8dp"
android:layout_marginRight="16dp"
bind:iconClickListener="@{() -> viewModel.chooseFilePath()}"
bind:svg_text_view_icon="@drawable/ic_choose_file"
bind:text="@{@string/file_path(filePath)}"
@ -76,13 +76,23 @@
bind:stateStr="@{stateStr}"
/>
<androidx.appcompat.widget.AppCompatSeekBar
android:id="@+id/pb"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<FrameLayout
android:background="@color/white"
android:id="@+id/video_content"
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_marginTop="16dp"
/>
android:background="@color/white"
>
</FrameLayout>
</LinearLayout>
</layout>

@ -0,0 +1,83 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bind="http://schemas.android.com/apk/res-auto"
>
<data>
<variable
name="fileSize"
type="String"
/>
<variable
name="speed"
type="String"
/>
<variable
name="progress"
type="int"
/>
<variable
name="url"
type="String"
/>
<variable
name="filePath"
type="String"
/>
<variable
name="viewModel"
type="com.arialyy.simple.core.upload.SFtpUploadActivity"
/>
<variable
name="stateStr"
type="String"
/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<include layout="@layout/layout_bar"/>
<com.arialyy.simple.widget.SvgTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginTop="16dp"
bind:iconClickListener="@{() -> viewModel.chooseUrl()}"
bind:svg_text_view_icon="@drawable/ic_modify"
bind:text="@{@string/url(url)}"
/>
<com.arialyy.simple.widget.SvgTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginTop="8dp"
bind:iconClickListener="@{() -> viewModel.chooseFilePath()}"
bind:svg_text_view_icon="@drawable/ic_choose_file"
bind:text="@{@string/file_path(filePath)}"
/>
<include
layout="@layout/layout_content_single"
bind:fileSize="@{fileSize}"
bind:progress="@{progress}"
bind:speed="@{speed}"
bind:stateStr="@{stateStr}"
/>
<!-- <com.arialyy.simple.widget.CodeView-->
<!-- android:id="@+id/code_view"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="match_parent"-->
<!-- />-->
</LinearLayout>
</layout>

@ -41,6 +41,7 @@
<item>M3U8 点播文件下载</item>
<item>M3U8 直播文件下载</item>
<item>SFTP 下载</item>
<item>SFTP 上传</item>
</string-array>
<string-array name="main_items_desc">
@ -53,6 +54,7 @@
<item>下载M3U8点播文件</item>
<item>下载M3U8直播文件</item>
<item>SFTP单文件下载</item>
<item>SFTP单文件上传</item>
</string-array>
<string-array name="download_items">

@ -44,8 +44,8 @@ task clean(type: Delete) {
}
ext {
versionCode = 383
versionName = '3.8.3'
versionCode = 385
versionName = '3.8.5'
userOrg = 'arialyy'
groupId = 'com.arialyy.aria'
publishVersion = versionName

Loading…
Cancel
Save