From d217ebcb254e9c302e6eb47c7290f3944c79bf29 Mon Sep 17 00:00:00 2001 From: laoyuyu <511455842@qq.com> Date: Sat, 18 Jan 2020 10:00:06 +0800 Subject: [PATCH] =?UTF-8?q?sftp=E4=B8=8A=E4=BC=A0=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../arialyy/aria/core/common/FtpOption.java | 51 ---- .../arialyy/aria/core/common/SFtpOption.java | 135 ++++++++++ .../download/target/FtpBuilderTarget.java | 3 +- .../core/download/target/FtpNormalTarget.java | 3 +- .../core/upload/target/FtpBuilderTarget.java | 15 ++ .../core/upload/target/FtpNormalTarget.java | 15 ++ DEV_LOG.md | 5 +- .../arialyy/aria/ftp/download/FtpDLoader.java | 95 ------- .../aria/ftp/download/FtpDRecordHandler.java | 6 +- .../arialyy/aria/ftp/upload/FtpULoader.java | 8 +- .../aria/ftp/upload/FtpULoaderUtil.java | 3 +- .../aria/ftp/upload/FtpURecordHandler.java | 2 +- .../http/download/HttpDThreadTaskAdapter.java | 4 + .../arialyy/aria/http/upload/HttpULoader.java | 2 +- .../com/arialyy/aria/m3u8/BaseM3U8Loader.java | 4 +- .../com/arialyy/aria/m3u8/M3U8InfoTask.java | 2 +- .../aria/m3u8/M3U8ThreadTaskAdapter.java | 7 +- .../aria/core/loader/AbsNormalLoader.java | 8 +- .../aria/core/loader/NormalLoader.java | 4 +- .../core/loader/NormalThreadStateManager.java | 35 ++- .../aria/core/processor/IKeyUrlConverter.java | 3 +- .../arialyy/aria/core/task/ThreadTask.java | 6 + .../aria/core/wrapper/ITaskWrapper.java | 5 + .../com/arialyy/aria/util/CommonUtil.java | 14 +- .../com/arialyy/aria/util/ComponentUtil.java | 9 + .../java/com/arialyy/aria/util/FileUtil.java | 31 +-- README.md | 32 +-- SFtpComponent/bintray-release.gradle | 11 + SFtpComponent/build.gradle | 7 +- .../arialyy/aria/sftp/AbsSFtpInfoTask.java | 7 +- .../arialyy/aria/sftp/SFtpSessionManager.java | 2 +- ...bThreadCOnfig.java => SFtpTaskOption.java} | 23 +- .../java/com/arialyy/aria/sftp/SFtpUtil.java | 6 +- .../aria/sftp/download/SFtpDInfoTask.java | 29 ++- .../aria/sftp/download/SFtpDLoader.java | 13 +- .../aria/sftp/download/SFtpDLoaderUtil.java | 10 +- .../sftp/download/SFtpDRecordHandler.java | 92 +++++++ .../sftp/download/SFtpDTTBuilderAdapter.java | 9 +- .../sftp/download/SFtpDThreadTaskAdapter.java | 150 +++++------ .../aria/sftp/upload/SFtpUInfoTask.java | 69 +++++ .../arialyy/aria/sftp/upload/SFtpULoader.java | 155 ++++++++++++ .../aria/sftp/upload/SFtpULoaderUtil.java | 55 ++++ .../aria/sftp/upload/SFtpURecordHandler.java | 115 +++++++++ .../sftp/upload/SFtpUTTBuilderAdapter.java | 73 ++++++ .../sftp/upload/SFtpUThreadTaskAdapter.java | 201 +++++++++++++++ app/src/main/AndroidManifest.xml | 55 ++-- app/src/main/assets/aria_config.xml | 2 +- .../java/com/arialyy/simple/MainActivity.java | 7 +- .../core/download/SFtpDownloadActivity.java | 7 +- .../core/download/SingleTaskActivity.java | 1 + .../download/m3u8/M3U8VodDLoadActivity.java | 21 +- .../simple/core/upload/FtpUploadActivity.java | 2 +- .../core/upload/SFtpUploadActivity.java | 239 ++++++++++++++++++ .../simple/core/upload/UploadModule.java | 25 ++ .../arialyy/simple/modlue/CommonModule.java | 1 + app/src/main/res/layout/activity_m3u8_vod.xml | 20 +- .../main/res/layout/activity_sftp_upload.xml | 83 ++++++ app/src/main/res/values/strings.xml | 2 + build.gradle | 4 +- 59 files changed, 1616 insertions(+), 387 deletions(-) create mode 100644 Aria/src/main/java/com/arialyy/aria/core/common/SFtpOption.java delete mode 100644 FtpComponent/src/main/java/com/arialyy/aria/ftp/download/FtpDLoader.java create mode 100644 SFtpComponent/bintray-release.gradle rename SFtpComponent/src/main/java/com/arialyy/aria/sftp/{SFtpSubThreadCOnfig.java => SFtpTaskOption.java} (61%) create mode 100644 SFtpComponent/src/main/java/com/arialyy/aria/sftp/download/SFtpDRecordHandler.java create mode 100644 SFtpComponent/src/main/java/com/arialyy/aria/sftp/upload/SFtpUInfoTask.java create mode 100644 SFtpComponent/src/main/java/com/arialyy/aria/sftp/upload/SFtpULoader.java create mode 100644 SFtpComponent/src/main/java/com/arialyy/aria/sftp/upload/SFtpULoaderUtil.java create mode 100644 SFtpComponent/src/main/java/com/arialyy/aria/sftp/upload/SFtpURecordHandler.java create mode 100644 SFtpComponent/src/main/java/com/arialyy/aria/sftp/upload/SFtpUTTBuilderAdapter.java create mode 100644 SFtpComponent/src/main/java/com/arialyy/aria/sftp/upload/SFtpUThreadTaskAdapter.java create mode 100644 app/src/main/java/com/arialyy/simple/core/upload/SFtpUploadActivity.java create mode 100644 app/src/main/res/layout/activity_sftp_upload.xml diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/FtpOption.java b/Aria/src/main/java/com/arialyy/aria/core/common/FtpOption.java index db9cd869..09301f2d 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/common/FtpOption.java +++ b/Aria/src/main/java/com/arialyy/aria/core/common/FtpOption.java @@ -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 * diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/SFtpOption.java b/Aria/src/main/java/com/arialyy/aria/core/common/SFtpOption.java new file mode 100644 index 00000000..9841d58e --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/common/SFtpOption.java @@ -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; + } +} \ No newline at end of file diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/target/FtpBuilderTarget.java b/Aria/src/main/java/com/arialyy/aria/core/download/target/FtpBuilderTarget.java index 4fe707bc..476c5db1 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/target/FtpBuilderTarget.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/target/FtpBuilderTarget.java @@ -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 { /** * 设置登陆、字符串编码、sftp等参数 */ - public FtpBuilderTarget sftpOption(FtpOption option) { + public FtpBuilderTarget sftpOption(SFtpOption option) { if (option == null) { throw new NullPointerException("ftp 任务配置为空"); } diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/target/FtpNormalTarget.java b/Aria/src/main/java/com/arialyy/aria/core/download/target/FtpNormalTarget.java index 5a6e87c1..68b13de0 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/target/FtpNormalTarget.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/target/FtpNormalTarget.java @@ -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 { /** * 设置登陆、字符串编码、sftp等参数 */ - public FtpNormalTarget sftpOption(FtpOption option) { + public FtpNormalTarget sftpOption(SFtpOption option) { if (option == null) { throw new NullPointerException("ftp 任务配置为空"); } diff --git a/Aria/src/main/java/com/arialyy/aria/core/upload/target/FtpBuilderTarget.java b/Aria/src/main/java/com/arialyy/aria/core/upload/target/FtpBuilderTarget.java index 846a938a..4c7ee63e 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/upload/target/FtpBuilderTarget.java +++ b/Aria/src/main/java/com/arialyy/aria/core/upload/target/FtpBuilderTarget.java @@ -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 { 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; + } } diff --git a/Aria/src/main/java/com/arialyy/aria/core/upload/target/FtpNormalTarget.java b/Aria/src/main/java/com/arialyy/aria/core/upload/target/FtpNormalTarget.java index 78111086..8597665c 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/upload/target/FtpNormalTarget.java +++ b/Aria/src/main/java/com/arialyy/aria/core/upload/target/FtpNormalTarget.java @@ -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 { 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(); } diff --git a/DEV_LOG.md b/DEV_LOG.md index b798ed95..679ff954 100644 --- a/DEV_LOG.md +++ b/DEV_LOG.md @@ -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 diff --git a/FtpComponent/src/main/java/com/arialyy/aria/ftp/download/FtpDLoader.java b/FtpComponent/src/main/java/com/arialyy/aria/ftp/download/FtpDLoader.java deleted file mode 100644 index 941797de..00000000 --- a/FtpComponent/src/main/java/com/arialyy/aria/ftp/download/FtpDLoader.java +++ /dev/null @@ -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 -// // 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; -// } -//} diff --git a/FtpComponent/src/main/java/com/arialyy/aria/ftp/download/FtpDRecordHandler.java b/FtpComponent/src/main/java/com/arialyy/aria/ftp/download/FtpDRecordHandler.java index d6aed224..53f8cf46 100644 --- a/FtpComponent/src/main/java/com/arialyy/aria/ftp/download/FtpDRecordHandler.java +++ b/FtpComponent/src/main/java/com/arialyy/aria/ftp/download/FtpDRecordHandler.java @@ -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; diff --git a/FtpComponent/src/main/java/com/arialyy/aria/ftp/upload/FtpULoader.java b/FtpComponent/src/main/java/com/arialyy/aria/ftp/upload/FtpULoader.java index 442e7e67..4d5d334f 100644 --- a/FtpComponent/src/main/java/com/arialyy/aria/ftp/upload/FtpULoader.java +++ b/FtpComponent/src/main/java/com/arialyy/aria/ftp/upload/FtpULoader.java @@ -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 { 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); diff --git a/FtpComponent/src/main/java/com/arialyy/aria/ftp/upload/FtpULoaderUtil.java b/FtpComponent/src/main/java/com/arialyy/aria/ftp/upload/FtpULoaderUtil.java index de66529f..6e6c9381 100644 --- a/FtpComponent/src/main/java/com/arialyy/aria/ftp/upload/FtpULoaderUtil.java +++ b/FtpComponent/src/main/java/com/arialyy/aria/ftp/upload/FtpULoaderUtil.java @@ -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() { diff --git a/FtpComponent/src/main/java/com/arialyy/aria/ftp/upload/FtpURecordHandler.java b/FtpComponent/src/main/java/com/arialyy/aria/ftp/upload/FtpURecordHandler.java index fdf52690..6d123c84 100644 --- a/FtpComponent/src/main/java/com/arialyy/aria/ftp/upload/FtpURecordHandler.java +++ b/FtpComponent/src/main/java/com/arialyy/aria/ftp/upload/FtpURecordHandler.java @@ -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; diff --git a/HttpComponent/src/main/java/com/arialyy/aria/http/download/HttpDThreadTaskAdapter.java b/HttpComponent/src/main/java/com/arialyy/aria/http/download/HttpDThreadTaskAdapter.java index 3557af0f..90c1560b 100644 --- a/HttpComponent/src/main/java/com/arialyy/aria/http/download/HttpDThreadTaskAdapter.java +++ b/HttpComponent/src/main/java/com/arialyy/aria/http/download/HttpDThreadTaskAdapter.java @@ -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); diff --git a/HttpComponent/src/main/java/com/arialyy/aria/http/upload/HttpULoader.java b/HttpComponent/src/main/java/com/arialyy/aria/http/upload/HttpULoader.java index 6e1bc4f4..ff2e6672 100644 --- a/HttpComponent/src/main/java/com/arialyy/aria/http/upload/HttpULoader.java +++ b/HttpComponent/src/main/java/com/arialyy/aria/http/upload/HttpULoader.java @@ -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 { HttpULoader(UTaskWrapper wrapper, IEventListener listener) { super(wrapper, listener); } diff --git a/M3U8Component/src/main/java/com/arialyy/aria/m3u8/BaseM3U8Loader.java b/M3U8Component/src/main/java/com/arialyy/aria/m3u8/BaseM3U8Loader.java index c24488dc..8d974e99 100644 --- a/M3U8Component/src/main/java/com/arialyy/aria/m3u8/BaseM3U8Loader.java +++ b/M3U8Component/src/main/java/com/arialyy/aria/m3u8/BaseM3U8Loader.java @@ -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 { 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(); } } diff --git a/M3U8Component/src/main/java/com/arialyy/aria/m3u8/M3U8InfoTask.java b/M3U8Component/src/main/java/com/arialyy/aria/m3u8/M3U8InfoTask.java index 096dc671..7e7fb816 100644 --- a/M3U8Component/src/main/java/com/arialyy/aria/m3u8/M3U8InfoTask.java +++ b/M3U8Component/src/main/java/com/arialyy/aria/m3u8/M3U8InfoTask.java @@ -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 为空"); diff --git a/M3U8Component/src/main/java/com/arialyy/aria/m3u8/M3U8ThreadTaskAdapter.java b/M3U8Component/src/main/java/com/arialyy/aria/m3u8/M3U8ThreadTaskAdapter.java index 4f8659d0..0f716273 100644 --- a/M3U8Component/src/main/java/com/arialyy/aria/m3u8/M3U8ThreadTaskAdapter.java +++ b/M3U8Component/src/main/java/com/arialyy/aria/m3u8/M3U8ThreadTaskAdapter.java @@ -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); - conn.setRequestProperty("Cookie", cookies); + if (!TextUtils.isEmpty(cookies)){ + conn.setRequestProperty("Cookie", cookies); + } if (mHttpTaskOption.isChunked()) { conn.setDoInput(true); conn.setChunkedStreamingMode(0); diff --git a/PublicComponent/src/main/java/com/arialyy/aria/core/loader/AbsNormalLoader.java b/PublicComponent/src/main/java/com/arialyy/aria/core/loader/AbsNormalLoader.java index a1918314..5015ed4c 100644 --- a/PublicComponent/src/main/java/com/arialyy/aria/core/loader/AbsNormalLoader.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/loader/AbsNormalLoader.java @@ -39,10 +39,10 @@ import java.util.concurrent.TimeUnit; * 3、创建文件信息获取器,获取文件信息,根据文件信息执行任务 * 4、创建线程任务执行下载、上传操作 */ -public abstract class AbsNormalLoader implements ILoaderVisitor, ILoader { +public abstract class AbsNormalLoader implements ILoaderVisitor, ILoader { protected final String TAG = CommonUtil.getClassName(getClass()); private IEventListener mListener; - protected AbsTaskWrapper mTaskWrapper; + protected T mTaskWrapper; protected File mTempFile; private List 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; diff --git a/PublicComponent/src/main/java/com/arialyy/aria/core/loader/NormalLoader.java b/PublicComponent/src/main/java/com/arialyy/aria/core/loader/NormalLoader.java index 79099dbf..6a008bda 100644 --- a/PublicComponent/src/main/java/com/arialyy/aria/core/loader/NormalLoader.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/loader/NormalLoader.java @@ -34,12 +34,12 @@ import java.io.File; /** * 单文件 */ -public class NormalLoader extends AbsNormalLoader { +public class NormalLoader extends AbsNormalLoader { 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); diff --git a/PublicComponent/src/main/java/com/arialyy/aria/core/loader/NormalThreadStateManager.java b/PublicComponent/src/main/java/com/arialyy/aria/core/loader/NormalThreadStateManager.java index 72b9673c..9ca449d9 100644 --- a/PublicComponent/src/main/java/com/arialyy/aria/core/loader/NormalThreadStateManager.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/loader/NormalThreadStateManager.java @@ -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 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) { diff --git a/PublicComponent/src/main/java/com/arialyy/aria/core/processor/IKeyUrlConverter.java b/PublicComponent/src/main/java/com/arialyy/aria/core/processor/IKeyUrlConverter.java index fd544746..3a95c06d 100644 --- a/PublicComponent/src/main/java/com/arialyy/aria/core/processor/IKeyUrlConverter.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/processor/IKeyUrlConverter.java @@ -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); } diff --git a/PublicComponent/src/main/java/com/arialyy/aria/core/task/ThreadTask.java b/PublicComponent/src/main/java/com/arialyy/aria/core/task/ThreadTask.java index b15a7093..d84aa596 100644 --- a/PublicComponent/src/main/java/com/arialyy/aria/core/task/ThreadTask.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/task/ThreadTask.java @@ -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); } } diff --git a/PublicComponent/src/main/java/com/arialyy/aria/core/wrapper/ITaskWrapper.java b/PublicComponent/src/main/java/com/arialyy/aria/core/wrapper/ITaskWrapper.java index e222842d..b5442513 100644 --- a/PublicComponent/src/main/java/com/arialyy/aria/core/wrapper/ITaskWrapper.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/wrapper/ITaskWrapper.java @@ -84,6 +84,11 @@ public interface ITaskWrapper { */ int D_SFTP = 12; + /** + * SFTP 上传 + */ + int U_SFTP = 13; + /** * 获取任务类型 * diff --git a/PublicComponent/src/main/java/com/arialyy/aria/util/CommonUtil.java b/PublicComponent/src/main/java/com/arialyy/aria/util/CommonUtil.java index 3699807a..a1efdab4 100644 --- a/PublicComponent/src/main/java/com/arialyy/aria/util/CommonUtil.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/util/CommonUtil.java @@ -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码 */ diff --git a/PublicComponent/src/main/java/com/arialyy/aria/util/ComponentUtil.java b/PublicComponent/src/main/java/com/arialyy/aria/util/ComponentUtil.java index 0ed85b6a..fb17e30c 100644 --- a/PublicComponent/src/main/java/com/arialyy/aria/util/ComponentUtil.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/util/ComponentUtil.java @@ -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: diff --git a/PublicComponent/src/main/java/com/arialyy/aria/util/FileUtil.java b/PublicComponent/src/main/java/com/arialyy/aria/util/FileUtil.java index f49d616f..340027c0 100644 --- a/PublicComponent/src/main/java/com/arialyy/aria/util/FileUtil.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/util/FileUtil.java @@ -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 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(); diff --git a/README.md b/README.md index 070bed9f..c96a321f 100644 --- a/README.md +++ b/README.md @@ -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:'`替换为 ``` @@ -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) diff --git a/SFtpComponent/bintray-release.gradle b/SFtpComponent/bintray-release.gradle new file mode 100644 index 00000000..85f62f0f --- /dev/null +++ b/SFtpComponent/bintray-release.gradle @@ -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 +} \ No newline at end of file diff --git a/SFtpComponent/build.gradle b/SFtpComponent/build.gradle index 3e7e3a3f..535880e7 100644 --- a/SFtpComponent/build.gradle +++ b/SFtpComponent/build.gradle @@ -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' diff --git a/SFtpComponent/src/main/java/com/arialyy/aria/sftp/AbsSFtpInfoTask.java b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/AbsSFtpInfoTask.java index 78cf4d63..ada8224a 100644 --- a/SFtpComponent/src/main/java/com/arialyy/aria/sftp/AbsSFtpInfoTask.java +++ b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/AbsSFtpInfoTask.java @@ -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 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 implements IInf } } - protected FtpTaskOption getOption() { + protected SFtpTaskOption getOption() { return option; } diff --git a/SFtpComponent/src/main/java/com/arialyy/aria/sftp/SFtpSessionManager.java b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/SFtpSessionManager.java index ab640d8d..9f43e1a8 100644 --- a/SFtpComponent/src/main/java/com/arialyy/aria/sftp/SFtpSessionManager.java +++ b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/SFtpSessionManager.java @@ -61,7 +61,7 @@ public class SFtpSessionManager { if (session == null) { ALog.w(TAG, "从缓存获取session失败,key:" + key); } - //cleanIdleSession(); + cleanIdleSession(); return session; } diff --git a/SFtpComponent/src/main/java/com/arialyy/aria/sftp/SFtpSubThreadCOnfig.java b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/SFtpTaskOption.java similarity index 61% rename from SFtpComponent/src/main/java/com/arialyy/aria/sftp/SFtpSubThreadCOnfig.java rename to SFtpComponent/src/main/java/com/arialyy/aria/sftp/SFtpTaskOption.java index 3cec638e..ce6501cc 100644 --- a/SFtpComponent/src/main/java/com/arialyy/aria/sftp/SFtpSubThreadCOnfig.java +++ b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/SFtpTaskOption.java @@ -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; + } } diff --git a/SFtpComponent/src/main/java/com/arialyy/aria/sftp/SFtpUtil.java b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/SFtpUtil.java index 90bfa6e8..17ba6445 100644 --- a/SFtpComponent/src/main/java/com/arialyy/aria/sftp/SFtpUtil.java +++ b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/SFtpUtil.java @@ -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); diff --git a/SFtpComponent/src/main/java/com/arialyy/aria/sftp/download/SFtpDInfoTask.java b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/download/SFtpDInfoTask.java index 5f5c2a7a..f01578d6 100644 --- a/SFtpComponent/src/main/java/com/arialyy/aria/sftp/download/SFtpDInfoTask.java +++ b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/download/SFtpDInfoTask.java @@ -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 { @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 { //channel.setFilenameEncoding("gbk"); String remotePath = option.getUrlEntity().remotePath; - String temp = CommonUtil.convertFtpChar(getOption().getCharSet(), remotePath); - SftpATTRS attr = channel.stat(temp); - getWrapper().getEntity().setFileSize(attr.getSize()); - CompleteInfo info = new CompleteInfo(); - info.code = 200; + 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; + callback.onSucceed(getWrapper().getKey(), info); + } else { + callback.onFail(getWrapper().getEntity(), + new AriaException(TAG, String.format("文件不存在,remotePath:%s", remotePath)), false); + } channel.disconnect(); - callback.onSucceed(getWrapper().getKey(), info); } } diff --git a/SFtpComponent/src/main/java/com/arialyy/aria/sftp/download/SFtpDLoader.java b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/download/SFtpDLoader.java index 73e75456..f6d214d3 100644 --- a/SFtpComponent/src/main/java/com/arialyy/aria/sftp/download/SFtpDLoader.java +++ b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/download/SFtpDLoader.java @@ -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 { 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() { diff --git a/SFtpComponent/src/main/java/com/arialyy/aria/sftp/download/SFtpDLoaderUtil.java b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/download/SFtpDLoaderUtil.java index 13aaa18f..44352a60 100644 --- a/SFtpComponent/src/main/java/com/arialyy/aria/sftp/download/SFtpDLoaderUtil.java +++ b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/download/SFtpDLoaderUtil.java @@ -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( diff --git a/SFtpComponent/src/main/java/com/arialyy/aria/sftp/download/SFtpDRecordHandler.java b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/download/SFtpDRecordHandler.java new file mode 100644 index 00000000..4cb46605 --- /dev/null +++ b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/download/SFtpDRecordHandler.java @@ -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; + } +} diff --git a/SFtpComponent/src/main/java/com/arialyy/aria/sftp/download/SFtpDTTBuilderAdapter.java b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/download/SFtpDTTBuilderAdapter.java index 23532d07..28e2d614 100644 --- a/SFtpComponent/src/main/java/com/arialyy/aria/sftp/download/SFtpDTTBuilderAdapter.java +++ b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/download/SFtpDTTBuilderAdapter.java @@ -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()); diff --git a/SFtpComponent/src/main/java/com/arialyy/aria/sftp/download/SFtpDThreadTaskAdapter.java b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/download/SFtpDThreadTaskAdapter.java index 0cc7d16e..a8bdcdd0 100644 --- a/SFtpComponent/src/main/java/com/arialyy/aria/sftp/download/SFtpDThreadTaskAdapter.java +++ b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/download/SFtpDThreadTaskAdapter.java @@ -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 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 class Monitor implements SftpProgressMonitor { - private boolean isResume; - - 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; } } } diff --git a/SFtpComponent/src/main/java/com/arialyy/aria/sftp/upload/SFtpUInfoTask.java b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/upload/SFtpUInfoTask.java new file mode 100644 index 00000000..138c38a2 --- /dev/null +++ b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/upload/SFtpUInfoTask.java @@ -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 { + 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); + } +} diff --git a/SFtpComponent/src/main/java/com/arialyy/aria/sftp/upload/SFtpULoader.java b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/upload/SFtpULoader.java new file mode 100644 index 00000000..ae7cdaf4 --- /dev/null +++ b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/upload/SFtpULoader.java @@ -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 { + + 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; + } +} diff --git a/SFtpComponent/src/main/java/com/arialyy/aria/sftp/upload/SFtpULoaderUtil.java b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/upload/SFtpULoaderUtil.java new file mode 100644 index 00000000..626b7b59 --- /dev/null +++ b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/upload/SFtpULoaderUtil.java @@ -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; + } +} diff --git a/SFtpComponent/src/main/java/com/arialyy/aria/sftp/upload/SFtpURecordHandler.java b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/upload/SFtpURecordHandler.java new file mode 100644 index 00000000..400c5689 --- /dev/null +++ b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/upload/SFtpURecordHandler.java @@ -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; + } +} diff --git a/SFtpComponent/src/main/java/com/arialyy/aria/sftp/upload/SFtpUTTBuilderAdapter.java b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/upload/SFtpUTTBuilderAdapter.java new file mode 100644 index 00000000..5a82b770 --- /dev/null +++ b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/upload/SFtpUTTBuilderAdapter.java @@ -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; + } +} diff --git a/SFtpComponent/src/main/java/com/arialyy/aria/sftp/upload/SFtpUThreadTaskAdapter.java b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/upload/SFtpUThreadTaskAdapter.java new file mode 100644 index 00000000..dd6ce4ed --- /dev/null +++ b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/upload/SFtpUThreadTaskAdapter.java @@ -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(); + } + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e046ebc5..f71fa4c9 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -4,11 +4,6 @@ android:sharedUserId="com.arialyy" package="com.arialyy.simple"> - - - - - - + - + - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - - - - + android:windowSoftInputMode="stateHidden|adjustResize" /> + + + android:resource="@xml/aria_fileprovider_paths" /> + + + + + + + diff --git a/app/src/main/assets/aria_config.xml b/app/src/main/assets/aria_config.xml index 8ac9205c..dca2c50b 100644 --- a/app/src/main/assets/aria_config.xml +++ b/app/src/main/assets/aria_config.xml @@ -32,7 +32,7 @@ 3、只对新的多线程下载任务有效 4、只对多线程的任务有效 --> - + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8a0c0cc9..c20b03c7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -41,6 +41,7 @@ M3U8 点播文件下载 M3U8 直播文件下载 SFTP 下载 + SFTP 上传 @@ -53,6 +54,7 @@ 下载M3U8点播文件 下载M3U8直播文件 SFTP单文件下载 + SFTP单文件上传 diff --git a/build.gradle b/build.gradle index 058d597d..90064a0d 100644 --- a/build.gradle +++ b/build.gradle @@ -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