From 50b265f22ad17716356e4e3169600349f8b6273d Mon Sep 17 00:00:00 2001 From: laoyuyu <511455842@qq.com> Date: Wed, 15 Jan 2020 20:28:10 +0800 Subject: [PATCH] =?UTF-8?q?sftp=20=E4=B8=8B=E8=BD=BD=E5=AE=9E=E7=8E=B0=20?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E9=9B=B6=E6=8B=B7=E8=B4=9D=E6=8A=80=E6=9C=AF?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=90=88=E5=B9=B6=E5=88=86=E5=9D=97=E7=9A=84?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../arialyy/aria/core/common/FtpOption.java | 37 ++- .../download/target/FtpBuilderTarget.java | 14 ++ .../core/download/target/FtpNormalTarget.java | 14 ++ .../download/target/M3U8NormalTarget.java | 2 + .../com/arialyy/aria/ftp/FtpTaskOption.java | 3 +- .../aria/ftp/download/FtpDRecordHandler.java | 5 +- .../aria/ftp/upload/FtpURecordHandler.java | 5 +- .../arialyy/aria/http/HttpRecordHandler.java | 4 +- .../aria/m3u8/live/LiveRecordHandler.java | 5 +- .../aria/m3u8/vod/VodRecordHandler.java | 4 +- .../com/arialyy/aria/core/AriaConfig.java | 8 +- .../java/com/arialyy/aria/core/IdEntity.java | 18 +- .../aria/core/group/SubRecordHandler.java | 2 +- .../aria/core/listener/BaseListener.java | 3 +- .../core/loader/NormalThreadStateManager.java | 5 +- .../arialyy/aria/core/task/ThreadTask.java | 3 + .../arialyy/aria/exception/AriaException.java | 28 +++ .../com/arialyy/aria/util/CommonUtil.java | 18 -- .../com/arialyy/aria/util/ComponentUtil.java | 4 + .../java/com/arialyy/aria/util/FileUtil.java | 110 +++++++- .../arialyy/aria/sftp/AbsSFtpInfoTask.java | 41 +-- .../arialyy/aria/sftp/SFtpSessionManager.java | 13 +- .../java/com/arialyy/aria/sftp/SFtpUtil.java | 234 ++++++++---------- .../aria/sftp/download/SFtpDInfoTask.java | 14 +- .../aria/sftp/download/SFtpDLoader.java | 6 - .../aria/sftp/download/SFtpDLoaderUtil.java | 5 +- .../sftp/download/SFtpDTTBuilderAdapter.java | 35 ++- .../sftp/download/SFtpDThreadTaskAdapter.java | 155 ++++++++++++ app/build.gradle | 1 + app/src/main/AndroidManifest.xml | 1 + app/src/main/assets/id_rsa | 30 +++ app/src/main/assets/id_rsa.pub | 1 + .../java/com/arialyy/simple/MainActivity.java | 3 + .../core/download/FtpDownloadModule.java | 25 +- .../core/download/SFtpDownloadActivity.java | 232 +++++++++++++++++ .../java/com/arialyy/simple/util/AppUtil.java | 3 +- .../res/layout/activity_sftp_download.xml | 86 +++++++ 37 files changed, 927 insertions(+), 250 deletions(-) create mode 100644 PublicComponent/src/main/java/com/arialyy/aria/exception/AriaException.java create mode 100644 app/src/main/assets/id_rsa create mode 100644 app/src/main/assets/id_rsa.pub create mode 100644 app/src/main/java/com/arialyy/simple/core/download/SFtpDownloadActivity.java create mode 100644 app/src/main/res/layout/activity_sftp_download.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 2574f8da..db9cd869 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 @@ -114,21 +114,21 @@ public class FtpOption extends BaseOption { } /** - * 设置私钥证书密码 + * 设置ca证书密码 * - * @param storePass 私钥密码 + * @param storePass ca证书密码 */ public FtpOption setStorePass(String storePass) { if (TextUtils.isEmpty(storePass)) { ALog.e(TAG, "设置证书密码失败,证书密码为空"); return this; } - idEntity.prvPass = storePass; + idEntity.storePass = storePass; return this; } /** - * 设置私钥证书路径 + * 设置cer证书路径 * * @param storePath 证书路径 */ @@ -142,9 +142,9 @@ public class FtpOption extends BaseOption { } /** - * 设置私钥证书 + * 设置私钥证书路径 * - * @param prvKey 证书内容 + * @param prvKey 证书路径 */ public FtpOption setPrvKey(String prvKey) { if (TextUtils.isEmpty(prvKey)) { @@ -155,6 +155,20 @@ public class FtpOption extends BaseOption { return this; } + /** + * 设置私钥密码 + * + * @param prvKeyPass 私钥密码 + */ + public FtpOption setPrvKeyPass(String prvKeyPass) { + if (TextUtils.isEmpty(prvKeyPass)) { + ALog.e(TAG, "设置证书密码失败,证书密码为空"); + return this; + } + idEntity.prvPass = prvKeyPass; + return this; + } + /** * 设置公钥证书 * @@ -169,6 +183,15 @@ public class FtpOption extends BaseOption { return this; } + public FtpOption setKnowHostPath(String knowHostPath){ + if (TextUtils.isEmpty(knowHostPath)){ + ALog.e(TAG, "knowhost 文件路径为空"); + return this; + } + idEntity.knowHost = knowHostPath; + return this; + } + /** * 设置安全模式,默认true * @@ -326,10 +349,10 @@ public class FtpOption extends BaseOption { urlEntity.user = userName; urlEntity.password = password; urlEntity.account = account; + urlEntity.idEntity = idEntity; if (!TextUtils.isEmpty(idEntity.storePath) || !TextUtils.isEmpty(idEntity.prvKey)) { urlEntity.isFtps = true; urlEntity.protocol = protocol; - urlEntity.idEntity = idEntity; urlEntity.isImplicit = isImplicit; } } 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 31106b59..4fe707bc 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 @@ -47,6 +47,20 @@ public class FtpBuilderTarget extends AbsBuilderTarget { return this; } + /** + * 设置登陆、字符串编码、sftp等参数 + */ + public FtpBuilderTarget sftpOption(FtpOption option) { + if (option == null) { + throw new NullPointerException("ftp 任务配置为空"); + } + option.setUrlEntity(CommonUtil.getFtpUrlInfo(mConfigHandler.getUrl())); + getTaskWrapper().getOptionParams().setParams(option); + getEntity().setTaskType(ITaskWrapper.D_SFTP); + getTaskWrapper().setRequestType(ITaskWrapper.D_SFTP); + return this; + } + /** * 设置文件保存文件夹路径 * 关于文件名: 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 21b4f2e3..5a6e87c1 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 @@ -46,6 +46,20 @@ public class FtpNormalTarget extends AbsNormalTarget { return this; } + /** + * 设置登陆、字符串编码、sftp等参数 + */ + public FtpNormalTarget sftpOption(FtpOption option) { + if (option == null) { + throw new NullPointerException("ftp 任务配置为空"); + } + option.setUrlEntity(CommonUtil.getFtpUrlInfo(mConfigHandler.getUrl())); + getTaskWrapper().getOptionParams().setParams(option); + getEntity().setTaskType(ITaskWrapper.D_SFTP); + getTaskWrapper().setRequestType(ITaskWrapper.D_SFTP); + return this; + } + /** * 设置文件保存文件夹路径 * 关于文件名: diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/target/M3U8NormalTarget.java b/Aria/src/main/java/com/arialyy/aria/core/download/target/M3U8NormalTarget.java index 63a8cfcd..3e847729 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/target/M3U8NormalTarget.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/target/M3U8NormalTarget.java @@ -21,6 +21,7 @@ import com.arialyy.aria.core.download.DTaskWrapper; import com.arialyy.aria.core.event.EventMsgUtil; import com.arialyy.aria.core.event.PeerIndexEvent; import com.arialyy.aria.core.queue.DTaskQueue; +import com.arialyy.aria.core.wrapper.ITaskWrapper; import com.arialyy.aria.util.ALog; public class M3U8NormalTarget extends AbsNormalTarget { @@ -28,6 +29,7 @@ public class M3U8NormalTarget extends AbsNormalTarget { M3U8NormalTarget(DTaskWrapper wrapper) { setTaskWrapper(wrapper); getTaskWrapper().setNewTask(false); + getTaskWrapper().setRequestType(ITaskWrapper.M3U8_VOD); } /** diff --git a/FtpComponent/src/main/java/com/arialyy/aria/ftp/FtpTaskOption.java b/FtpComponent/src/main/java/com/arialyy/aria/ftp/FtpTaskOption.java index 21e3e40b..16239c2a 100644 --- a/FtpComponent/src/main/java/com/arialyy/aria/ftp/FtpTaskOption.java +++ b/FtpComponent/src/main/java/com/arialyy/aria/ftp/FtpTaskOption.java @@ -15,6 +15,7 @@ */ package com.arialyy.aria.ftp; +import android.text.TextUtils; import aria.apache.commons.net.ftp.FTPClientConfig; import com.arialyy.aria.core.FtpUrlEntity; import com.arialyy.aria.core.common.FtpConnectionMode; @@ -172,7 +173,7 @@ public final class FtpTaskOption implements ITaskOption { } public String getCharSet() { - return charSet; + return TextUtils.isEmpty(charSet) ? "UTF-8" : charSet; } public void setCharSet(String charSet) { 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 6b63b511..d6aed224 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 @@ -22,6 +22,7 @@ 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; @@ -58,7 +59,7 @@ public final class FtpDRecordHandler extends RecordHandler { tr.threadId = threadId; tr.startLocation = startL; tr.isComplete = false; - tr.threadType = getWrapper().getEntity().getTaskType(); + tr.threadType = record.taskType; //最后一个线程的结束位置即为文件的总长度 if (threadId == (record.threadNum - 1)) { endL = getFileSize(); @@ -75,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().getEntity().getTaskType(); + record.taskType = getWrapper().getRequestType(); record.isGroupRecord = false; return record; 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 aa452338..fdf52690 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 @@ -20,6 +20,7 @@ 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.wrapper.ITaskWrapper; import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.RecordUtil; import java.util.ArrayList; @@ -87,7 +88,7 @@ final class FtpURecordHandler extends RecordHandler { tr.threadId = threadId; tr.startLocation = startL; tr.isComplete = false; - tr.threadType = getWrapper().getEntity().getTaskType(); + tr.threadType = record.taskType; tr.endLocation = getFileSize(); tr.blockLen = RecordUtil.getBlockLen(getFileSize(), threadId, record.threadNum); return tr; @@ -100,7 +101,7 @@ final class FtpURecordHandler extends RecordHandler { record.threadRecords = new ArrayList<>(); record.threadNum = threadNum; record.isBlock = false; - record.taskType = getWrapper().getEntity().getTaskType(); + record.taskType = getWrapper().getRequestType(); record.isGroupRecord = getEntity().isGroupChild(); return record; diff --git a/HttpComponent/src/main/java/com/arialyy/aria/http/HttpRecordHandler.java b/HttpComponent/src/main/java/com/arialyy/aria/http/HttpRecordHandler.java index e0472b86..c714a846 100644 --- a/HttpComponent/src/main/java/com/arialyy/aria/http/HttpRecordHandler.java +++ b/HttpComponent/src/main/java/com/arialyy/aria/http/HttpRecordHandler.java @@ -67,7 +67,7 @@ public final class HttpRecordHandler extends RecordHandler { tr.startLocation = startL; tr.isComplete = false; - tr.threadType = getEntity().getTaskType(); + tr.threadType = record.taskType; //最后一个线程的结束位置即为文件的总长度 if (threadId == (record.threadNum - 1)) { endL = getFileSize(); @@ -90,7 +90,7 @@ public final class HttpRecordHandler extends RecordHandler { } else { record.isBlock = false; } - record.taskType = getEntity().getTaskType(); + record.taskType = requestType; record.isGroupRecord = getEntity().isGroupChild(); if (record.isGroupRecord) { if (getEntity() instanceof DownloadEntity) { diff --git a/M3U8Component/src/main/java/com/arialyy/aria/m3u8/live/LiveRecordHandler.java b/M3U8Component/src/main/java/com/arialyy/aria/m3u8/live/LiveRecordHandler.java index 206c2a97..adbe657f 100644 --- a/M3U8Component/src/main/java/com/arialyy/aria/m3u8/live/LiveRecordHandler.java +++ b/M3U8Component/src/main/java/com/arialyy/aria/m3u8/live/LiveRecordHandler.java @@ -20,6 +20,7 @@ import com.arialyy.aria.core.ThreadRecord; import com.arialyy.aria.core.common.RecordHandler; import com.arialyy.aria.core.loader.IRecordHandler; import com.arialyy.aria.core.wrapper.AbsTaskWrapper; +import com.arialyy.aria.core.wrapper.ITaskWrapper; import com.arialyy.aria.m3u8.M3U8TaskOption; import com.arialyy.aria.util.RecordUtil; import java.util.ArrayList; @@ -74,7 +75,7 @@ final class LiveRecordHandler extends RecordHandler { tr.taskKey = taskRecord.filePath; tr.isComplete = false; tr.tsUrl = tsUrl; - tr.threadType = getEntity().getTaskType(); + tr.threadType = taskRecord.taskType; tr.threadId = threadId; tr.startLocation = 0; taskRecord.threadRecords.add(tr); @@ -88,7 +89,7 @@ final class LiveRecordHandler extends RecordHandler { record.threadRecords = new ArrayList<>(); record.threadNum = threadNum; record.isBlock = true; - record.taskType = getEntity().getTaskType(); + record.taskType = ITaskWrapper.M3U8_LIVE; record.bandWidth = mOption.getBandWidth(); return record; } diff --git a/M3U8Component/src/main/java/com/arialyy/aria/m3u8/vod/VodRecordHandler.java b/M3U8Component/src/main/java/com/arialyy/aria/m3u8/vod/VodRecordHandler.java index 4ba82c57..d2a74b5f 100644 --- a/M3U8Component/src/main/java/com/arialyy/aria/m3u8/vod/VodRecordHandler.java +++ b/M3U8Component/src/main/java/com/arialyy/aria/m3u8/vod/VodRecordHandler.java @@ -103,7 +103,7 @@ final class VodRecordHandler extends RecordHandler { tr.threadId = threadId; tr.isComplete = false; tr.startLocation = 0; - tr.threadType = getEntity().getTaskType(); + tr.threadType = record.taskType; tr.tsUrl = mOption.getUrls().get(threadId); return tr; } @@ -115,7 +115,7 @@ final class VodRecordHandler extends RecordHandler { record.threadRecords = new ArrayList<>(); record.threadNum = threadNum; record.isBlock = true; - record.taskType = getEntity().getTaskType(); + record.taskType = ITaskWrapper.M3U8_VOD; record.bandWidth = mOption.getBandWidth(); return record; } diff --git a/PublicComponent/src/main/java/com/arialyy/aria/core/AriaConfig.java b/PublicComponent/src/main/java/com/arialyy/aria/core/AriaConfig.java index 8698ef0d..7b4c3c04 100644 --- a/PublicComponent/src/main/java/com/arialyy/aria/core/AriaConfig.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/AriaConfig.java @@ -33,6 +33,7 @@ import com.arialyy.aria.core.config.UploadConfig; import com.arialyy.aria.core.config.XMLReader; import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.CommonUtil; +import com.arialyy.aria.util.FileUtil; import java.io.File; import java.io.IOException; import javax.xml.parsers.ParserConfigurationException; @@ -136,7 +137,7 @@ public class AriaConfig { .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) .build(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){ + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { cm.registerNetworkCallback(request, new ConnectivityManager.NetworkCallback() { @Override public void onLost(Network network) { @@ -152,7 +153,6 @@ public class AriaConfig { } }); } - } public boolean isConnectedNet() { @@ -179,7 +179,7 @@ public class AriaConfig { if (file.exists()) { file.delete(); } - CommonUtil.createFileFormInputStream(APP.getAssets().open("aria_config.xml"), + FileUtil.createFileFormInputStream(APP.getAssets().open("aria_config.xml"), file.getPath()); if (!CommonUtil.checkMD5(md5Code, file) || !Configuration.getInstance().configExists()) { loadConfig(); @@ -204,7 +204,7 @@ public class AriaConfig { SAXParserFactory factory = SAXParserFactory.newInstance(); SAXParser parser = factory.newSAXParser(); parser.parse(APP.getAssets().open("aria_config.xml"), helper); - CommonUtil.createFileFormInputStream(APP.getAssets().open("aria_config.xml"), + FileUtil.createFileFormInputStream(APP.getAssets().open("aria_config.xml"), APP.getFilesDir().getPath() + Configuration.XML_FILE); } catch (ParserConfigurationException | IOException | SAXException e) { ALog.e(TAG, e.toString()); diff --git a/PublicComponent/src/main/java/com/arialyy/aria/core/IdEntity.java b/PublicComponent/src/main/java/com/arialyy/aria/core/IdEntity.java index 7898f55b..9b8a7138 100644 --- a/PublicComponent/src/main/java/com/arialyy/aria/core/IdEntity.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/IdEntity.java @@ -21,7 +21,7 @@ package com.arialyy.aria.core; public class IdEntity { /** - * 私钥证书内容(非路径) + * 私钥证书路径 */ public String prvKey; @@ -31,17 +31,27 @@ public class IdEntity { public String prvPass; /** - * 公钥证书内容(非路径) + * 公钥证书路径 */ public String pubKey; /** - * 私钥证书路径 + * knowhost文件路径 + */ + public String knowHost; + + /** + * ca 证书密码 + */ + public String storePass; + + /** + * ca证书路径 */ public String storePath; /** - * 私钥别名 + * ca证书别名 */ public String keyAlias; } diff --git a/PublicComponent/src/main/java/com/arialyy/aria/core/group/SubRecordHandler.java b/PublicComponent/src/main/java/com/arialyy/aria/core/group/SubRecordHandler.java index b977a60b..19976503 100644 --- a/PublicComponent/src/main/java/com/arialyy/aria/core/group/SubRecordHandler.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/group/SubRecordHandler.java @@ -58,7 +58,7 @@ public class SubRecordHandler extends RecordHandler { record.threadRecords = new ArrayList<>(); record.threadNum = threadNum; record.isBlock = false; - record.taskType = getEntity().getTaskType(); + record.taskType = getWrapper().getRequestType(); record.isGroupRecord = true; if (getEntity() instanceof DownloadEntity) { record.dGroupHash = ((DownloadEntity) getEntity()).getGroupHash(); diff --git a/PublicComponent/src/main/java/com/arialyy/aria/core/listener/BaseListener.java b/PublicComponent/src/main/java/com/arialyy/aria/core/listener/BaseListener.java index 6e333d2a..8a6833e3 100644 --- a/PublicComponent/src/main/java/com/arialyy/aria/core/listener/BaseListener.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/listener/BaseListener.java @@ -136,7 +136,8 @@ public abstract class BaseListener 0) { - fos.write(buf, 0, len); - } - is.close(); - fos.flush(); - fos.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } /** * 校验文件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 d3310acf..0ed85b6a 100644 --- a/PublicComponent/src/main/java/com/arialyy/aria/util/ComponentUtil.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/util/ComponentUtil.java @@ -127,6 +127,9 @@ public class ComponentUtil { case ITaskWrapper.DG_HTTP: className = "com.arialyy.aria.http.download.HttpDGLoaderUtil"; break; + case ITaskWrapper.D_SFTP: + className = "com.arialyy.aria.sftp.download.SFtpDLoaderUtil"; + break; } if (className == null) { ALog.e(TAG, "不识别的类名:" + className); @@ -170,6 +173,7 @@ public class ComponentUtil { break; case ITaskWrapper.D_FTP: case ITaskWrapper.D_HTTP: + case ITaskWrapper.D_SFTP: className = "com.arialyy.aria.core.listener.BaseDListener"; break; case ITaskWrapper.U_FTP: 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 00861103..f49d616f 100644 --- a/PublicComponent/src/main/java/com/arialyy/aria/util/FileUtil.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/util/FileUtil.java @@ -34,6 +34,7 @@ import java.io.FileOutputStream; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; +import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.SequenceInputStream; @@ -63,6 +64,27 @@ public class FileUtil { private static final String EXTERNAL_STORAGE_PATH = Environment.getExternalStorageDirectory().getPath(); + /** + * 通过流创建文件 + * + * @param dest 输出路径 + */ + public static void createFileFormInputStream(InputStream is, String dest) { + try { + FileOutputStream fos = new FileOutputStream(dest); + byte[] buf = new byte[1024]; + int len; + while ((len = is.read(buf)) > 0) { + fos.write(buf, 0, len); + } + is.close(); + fos.flush(); + fos.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + /** * 获取m3u8 ts文件的缓存目录。 * 缓存文件夹格式:父文件夹/.文件名_码率 @@ -295,6 +317,7 @@ public class FileUtil { File file = new File(targetPath); FileOutputStream fos = null; FileChannel foc = null; + long startTime = System.currentTimeMillis(); try { if (file.exists() && file.isDirectory()) { ALog.w(TAG, String.format("路径【%s】是文件夹,将删除该文件夹", targetPath)); @@ -307,6 +330,7 @@ public class FileUtil { fos = new FileOutputStream(targetPath); foc = fos.getChannel(); List streams = new LinkedList<>(); + long fileLen = 0; for (String subPath : subPaths) { File f = new File(subPath); if (!f.exists()) { @@ -319,18 +343,92 @@ public class FileUtil { return false; } streams.add(new FileInputStream(subPath)); + fileLen += f.length(); } 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(); - } + //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) { + e.printStackTrace(); + } finally { + try { + if (foc != null) { + foc.close(); + } + if (fos != null) { + fos.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + return false; + } + + /** + * 合并sftp的分块文件,sftp的分块可能会超出规定的长度,因此需要使用本方法 + * + * @param targetPath 目标文件 + * @param subPaths 碎片文件路径 + * @param targetFileSize 文件长度 + * @return {@code true} 合并成功,{@code false}合并失败 + */ + public static boolean mergeSFtpFile(String targetPath, List subPaths, + long targetFileSize) { + File file = new File(targetPath); + FileOutputStream fos = null; + FileChannel foc = null; + long startTime = System.currentTimeMillis(); + try { + if (file.exists() && file.isDirectory()) { + ALog.w(TAG, String.format("路径【%s】是文件夹,将删除该文件夹", targetPath)); + FileUtil.deleteDir(file); + } + if (!file.exists()) { + FileUtil.createFile(file); + } + + fos = new FileOutputStream(targetPath); + foc = fos.getChannel(); + List streams = new LinkedList<>(); + 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()) { + ALog.d(TAG, String.format("合并文件失败,文件【%s】不存在", subPath)); + for (FileInputStream fis : streams) { + fis.close(); + } + streams.clear(); + + return false; + } + + 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); + fis.close(); + i ++; + } + + ALog.d(TAG, String.format("合并文件耗时:%sms", (System.currentTimeMillis() - startTime))); return true; } catch (IOException e) { e.printStackTrace(); 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 2f6237f9..78cf4d63 100644 --- a/SFtpComponent/src/main/java/com/arialyy/aria/sftp/AbsSFtpInfoTask.java +++ b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/AbsSFtpInfoTask.java @@ -15,21 +15,17 @@ */ package com.arialyy.aria.sftp; -import android.text.TextUtils; import com.arialyy.aria.core.FtpUrlEntity; -import com.arialyy.aria.core.IdEntity; 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.JSch; import com.jcraft.jsch.JSchException; import com.jcraft.jsch.Session; import com.jcraft.jsch.SftpException; import java.io.UnsupportedEncodingException; -import java.util.Properties; /** * 进行登录,获取session,获取文件信息 @@ -51,10 +47,10 @@ public abstract class AbsSFtpInfoTask implements IInf @Override public void run() { try { FtpUrlEntity entity = option.getUrlEntity(); - String key = CommonUtil.getStrMd5(entity.hostName + entity.port + entity.user); + String key = CommonUtil.getStrMd5(entity.hostName + entity.port + entity.user + 0); Session session = SFtpSessionManager.getInstance().getSession(key); if (session == null) { - session = login(entity); + session = SFtpUtil.getInstance().getSession(entity, 0); } getFileInfo(session); } catch (JSchException e) { @@ -69,39 +65,6 @@ public abstract class AbsSFtpInfoTask implements IInf } } - private Session login(FtpUrlEntity entity) throws JSchException, UnsupportedEncodingException { - JSch jSch = new JSch(); - - IdEntity idEntity = entity.idEntity; - - if (idEntity.prvKey != null) { - if (idEntity.pubKey == null) { - jSch.addIdentity(idEntity.prvKey, - entity.password == null ? null : entity.password.getBytes("UTF-8")); - } else { - jSch.addIdentity(idEntity.prvKey, idEntity.pubKey, - entity.password == null ? null : entity.password.getBytes("UTF-8")); - } - } - - Session session; - if (TextUtils.isEmpty(entity.user)) { - session = jSch.getSession(entity.url, entity.hostName, Integer.parseInt(entity.port)); - } else { - session = jSch.getSession(entity.hostName); - } - if (!TextUtils.isEmpty(entity.password)) { - session.setPassword(entity.password); - } - Properties config = new Properties(); - config.put("StrictHostKeyChecking", "no"); - session.setConfig(config);// 为Session对象设置properties - session.setTimeout(3000);// 设置超时 - session.setIdentityRepository(jSch.getIdentityRepository()); - session.connect(); - return session; - } - protected FtpTaskOption 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 9b2eb83e..ab640d8d 100644 --- a/SFtpComponent/src/main/java/com/arialyy/aria/sftp/SFtpSessionManager.java +++ b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/SFtpSessionManager.java @@ -49,32 +49,33 @@ public class SFtpSessionManager { /** * 获取session,获取完成session后,检查map中的所有session,移除所有失效的session * - * @param key md5(host + port + userName ) + * @param key md5(host + port + userName + threadId) * @return 如果session不可用,返回null */ public Session getSession(String key) { if (TextUtils.isEmpty(key)) { - ALog.e(TAG, "获取session失败,key为空"); + ALog.e(TAG, "从缓存获取session失败,key为空"); return null; } Session session = sessionDeque.get(key); if (session == null) { - ALog.w(TAG, "获取session失败,key:" + key); + ALog.w(TAG, "从缓存获取session失败,key:" + key); } - cleanIdleSession(); + //cleanIdleSession(); return session; } /** * 添加session */ - public void addSession(Session session) { + public void addSession(Session session, int threadId) { if (session == null) { ALog.e(TAG, "添加session到管理器失败,session 为空"); return; } String key = - CommonUtil.getStrMd5(session.getHost() + session.getPort() + session.getUserName()); + CommonUtil.getStrMd5( + session.getHost() + session.getPort() + session.getUserName() + threadId); sessionDeque.put(key, session); } 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 1fa1c1d8..90bfa6e8 100644 --- a/SFtpComponent/src/main/java/com/arialyy/aria/sftp/SFtpUtil.java +++ b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/SFtpUtil.java @@ -16,16 +16,19 @@ package com.arialyy.aria.sftp; import android.text.TextUtils; -import com.arialyy.aria.util.ALog; +import com.arialyy.aria.core.FtpUrlEntity; +import com.arialyy.aria.core.IdEntity; import com.arialyy.aria.util.CommonUtil; -import com.jcraft.jsch.ChannelExec; +import com.arialyy.aria.util.FileUtil; import com.jcraft.jsch.JSch; import com.jcraft.jsch.JSchException; import com.jcraft.jsch.Session; -import java.io.BufferedReader; +import com.jcraft.jsch.UserInfo; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; import java.util.Properties; /** @@ -35,166 +38,137 @@ import java.util.Properties; */ public class SFtpUtil { private final String TAG = CommonUtil.getClassName(getClass()); - /** - * 用于执行命令 - */ - public static final String CMD_TYPE_EXEC = "exec"; - /** - * 用于处理文件 - */ - public static final String CMD_TYPE_SFTP = "sftp"; - private String ip, userName, password; - private int port; - private Session session; - private boolean isLogin = false; + + private static SFtpUtil INSTANCE; private SFtpUtil() { - createClient(); + + } + + public synchronized static SFtpUtil getInstance() { + if (INSTANCE == null) { + synchronized (SFtpUtil.class) { + INSTANCE = new SFtpUtil(); + } + } + return INSTANCE; } /** - * 创建客户端 + * 创建jsch 的session + * + * @param threadId 线程id,默认0 + * @throws JSchException + * @throws UnsupportedEncodingException */ - private void createClient() { + public Session getSession(FtpUrlEntity entity, int threadId) throws JSchException, + UnsupportedEncodingException { + JSch jSch = new JSch(); - try { - if (TextUtils.isEmpty(userName)) { - session = jSch.getSession(userName, ip, port); + + IdEntity idEntity = entity.idEntity; + + if (idEntity.prvKey != null) { + if (idEntity.pubKey == null) { + jSch.addIdentity(idEntity.prvKey, + entity.password == null ? null : idEntity.prvPass.getBytes("UTF-8")); } else { - session = jSch.getSession(ip); - } - if (!TextUtils.isEmpty(password)) { - session.setPassword(password); + jSch.addIdentity(idEntity.prvKey, idEntity.pubKey, + entity.password == null ? null : idEntity.prvPass.getBytes("UTF-8")); } - Properties config = new Properties(); - config.put("StrictHostKeyChecking", "no"); - session.setConfig(config);// 为Session对象设置properties - session.setTimeout(3000);// 设置超时 - login(); - isLogin = true; - } catch (JSchException e) { - e.printStackTrace(); } - } - /** - * 执行登录 - */ - public Session login() { - try { - session.connect(); // 通过Session建立连接 - } catch (JSchException e) { - e.printStackTrace(); + setknowHost(jSch, entity); + + Session session; + if (TextUtils.isEmpty(entity.user)) { + session = jSch.getSession(null, entity.hostName, Integer.parseInt(entity.port)); + } else { + session = jSch.getSession(entity.user, entity.hostName, Integer.parseInt(entity.port)); + } + if (!TextUtils.isEmpty(entity.password)) { + session.setPassword(entity.password); } + Properties config = new Properties(); + + // 不检查公钥,需要在connect之前配置,但是不安全,no 模式会自动将配对信息写入know_host文件 + config.put("StrictHostKeyChecking", "no"); + session.setConfig(config);// 为Session对象设置properties + session.setTimeout(3000);// 设置超时 + session.setIdentityRepository(jSch.getIdentityRepository()); + session.connect(); + SFtpSessionManager.getInstance().addSession(session, threadId); return session; } - /** - * 登出 - */ - public void logout() { - if (session != null) { - session.disconnect(); + private void setknowHost(JSch jSch, FtpUrlEntity entity) throws JSchException { + IdEntity idEntity = entity.idEntity; + if (idEntity.knowHost != null) { + File knowFile = new File(idEntity.knowHost); + if (!knowFile.exists()) { + FileUtil.createFile(knowFile); + } + jSch.setKnownHosts(idEntity.knowHost); + + //HostKeyRepository hkr = jSch.getHostKeyRepository(); + //hkr.add(new HostKey(entity.hostName, HostKey.SSHRSA, getPubKey(idEntity.pubKey)), new JschUserInfo()); + // + //HostKey[] hks = hkr.getHostKey(); + //if (hks != null) { + // System.out.println("Host keys in " + hkr.getKnownHostsRepositoryID()); + // for (int i = 0; i < hks.length; i++) { + // HostKey hk = hks[i]; + // System.out.println(hk.getHost() + " " + + // hk.getType() + " " + + // hk.getFingerPrint(jSch)); + // } + //} } - isLogin = false; } - public Session getSession() { - return session; - } - - /** - * 执行命令 - * - * @param cmd sftp命令 - */ - public void execCommand(String cmd) { - if (TextUtils.isEmpty(cmd)) { - ALog.e(TAG, "命令为空"); - return; - } - if (!isLogin) { - ALog.e(TAG, "没有登录"); - return; - } - ChannelExec channel = null; + private byte[] getPubKey(String pubKeyPath) { try { - channel = (ChannelExec) session.openChannel(CMD_TYPE_EXEC); - channel.setCommand(cmd); - channel.connect(); - String rst = getResult(channel.getInputStream()); - - ALog.i(TAG, String.format("result: %s", rst)); - } catch (IOException e) { + File f = new File(pubKeyPath); + FileInputStream fis = new FileInputStream(f); + byte[] buf = new byte[(int) f.length()]; + int len = fis.read(buf); + fis.close(); + return buf; + } catch (FileNotFoundException e) { e.printStackTrace(); - } catch (JSchException e) { + } catch (IOException e) { e.printStackTrace(); - } finally { - if (channel != null) { - channel.disconnect(); - } } + return null; } - /** - * 执行命令后,获取服务器端返回的数据 - * - * @return 服务器端返回的数据 - */ - private String getResult(InputStream in) throws IOException { - if (in == null){ - ALog.e(TAG, "输入流为空"); + private static class JschUserInfo implements UserInfo { + + @Override public String getPassphrase() { return null; } - StringBuilder sb = new StringBuilder(); - BufferedReader isr = new BufferedReader(new InputStreamReader(in)); - String line; - while ((line = isr.readLine()) != null) { - sb.append(line); - } - in.close(); - isr.close(); - return sb.toString(); - } - - public static class Builder { - private String ip, userName, password; - private int port = 22; - - public Builder setIp(String ip) { - this.ip = ip; - return this; + @Override public String getPassword() { + return null; } - public Builder setUserName(String userName) { - this.userName = userName; - return this; + @Override public boolean promptPassword(String message) { + System.out.println(message); + return true; } - public Builder setPassword(String password) { - this.password = password; - return this; + @Override public boolean promptPassphrase(String message) { + System.out.println(message); + return false; } - public Builder setPort(int port) { - this.port = port; - return this; + @Override public boolean promptYesNo(String message) { + System.out.println(message); + return false; } - public SFtpUtil build() { - SFtpUtil login = new SFtpUtil(); - login.ip = ip; - login.userName = userName; - login.password = password; - login.port = port; - if (TextUtils.isEmpty(ip)) { - throw new IllegalArgumentException("ip不能为空"); - } - if (port < 0 || port > 65534) { - throw new IllegalArgumentException("端口错误"); - } - return login; + @Override public void showMessage(String message) { + System.out.println(message); } } } 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 8031f373..5f5c2a7a 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,6 +17,7 @@ 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.sftp.AbsSFtpInfoTask; import com.arialyy.aria.util.CommonUtil; import com.jcraft.jsch.ChannelSftp; @@ -37,13 +38,20 @@ final class SFtpDInfoTask extends AbsSFtpInfoTask { @Override protected void getFileInfo(Session session) throws JSchException, UnsupportedEncodingException, SftpException { + FtpTaskOption option = (FtpTaskOption) getWrapper().getTaskOption(); ChannelSftp channel = (ChannelSftp) session.openChannel("sftp"); - SftpATTRS attr = channel.stat( - CommonUtil.convertFtpChar(getOption().getCharSet(), getWrapper().getEntity().getUrl())); + channel.connect(1000); + + //channel.setFilenameEncoding(option.getCharSet()); + //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; - info.obj = channel; + 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 ec0954e7..73e75456 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 @@ -34,7 +34,6 @@ 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 com.jcraft.jsch.ChannelSftp; import java.io.File; final class SFtpDLoader extends AbsNormalLoader { @@ -42,7 +41,6 @@ final class SFtpDLoader extends AbsNormalLoader { private int startThreadNum; //启动的线程数 private boolean isComplete = false; private Looper looper; - private ChannelSftp channelSftp; SFtpDLoader(AbsTaskWrapper wrapper, IEventListener listener) { super(wrapper, listener); @@ -103,9 +101,6 @@ final class SFtpDLoader extends AbsNormalLoader { mStateManager.setLooper(mRecord, looper); // 创建线程任务 - SFtpDTTBuilderAdapter ttBuild = - (SFtpDTTBuilderAdapter) ((NormalTTBuilder) mTTBuilder).getAdapter(); - ttBuild.setChannel(channelSftp); getTaskList().addAll(mTTBuilder.buildThreadTask(mRecord, new Handler(looper, mStateManager.getHandlerCallback()))); startThreadNum = mTTBuilder.getCreatedThreadNum(); @@ -143,7 +138,6 @@ final class SFtpDLoader extends AbsNormalLoader { mInfoTask = infoTask; infoTask.setCallback(new IInfoTask.Callback() { @Override public void onSucceed(String key, CompleteInfo info) { - channelSftp = (ChannelSftp) info.obj; startThreadTask(); } 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 d101e7f9..13aaa18f 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 @@ -33,7 +33,7 @@ import com.arialyy.aria.ftp.download.FtpDRecordHandler; */ public class SFtpDLoaderUtil extends AbsNormalLoaderUtil { - protected SFtpDLoaderUtil(AbsTaskWrapper wrapper, IEventListener listener) { + public SFtpDLoaderUtil(AbsTaskWrapper wrapper, IEventListener listener) { super(wrapper, listener); wrapper.generateTaskOption(FtpTaskOption.class); } @@ -47,7 +47,8 @@ public class SFtpDLoaderUtil extends AbsNormalLoaderUtil { structure.addComponent(new FtpDRecordHandler((DTaskWrapper) getTaskWrapper())) .addComponent(new NormalThreadStateManager(getListener())) .addComponent(new SFtpDInfoTask((DTaskWrapper) getTaskWrapper())) - .addComponent(new NormalTTBuilder(getTaskWrapper(), new SFtpDTTBuilderAdapter()git)); + .addComponent(new NormalTTBuilder(getTaskWrapper(), new SFtpDTTBuilderAdapter( + (DTaskWrapper) getTaskWrapper()))); structure.accept(getLoader()); return structure; } 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 6b715aee..23532d07 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 @@ -16,25 +16,30 @@ package com.arialyy.aria.sftp.download; 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.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.SFtpUtil; 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 java.io.File; +import java.io.UnsupportedEncodingException; class SFtpDTTBuilderAdapter extends AbsNormalTTBuilderAdapter { - private ChannelSftp channel; + private FtpTaskOption option; - SFtpDTTBuilderAdapter() { - } - - void setChannel(ChannelSftp channel) { - this.channel = channel; + SFtpDTTBuilderAdapter(DTaskWrapper wrapper) { + option = (FtpTaskOption) wrapper.getTaskOption(); } @Override public IThreadTaskAdapter getAdapter(SubThreadConfig config) { @@ -46,7 +51,21 @@ class SFtpDTTBuilderAdapter extends AbsNormalTTBuilderAdapter { boolean isBlock, int startNum) { SubThreadConfig config = super.getSubThreadConfig(stateHandler, threadRecord, isBlock, startNum); - config.obj = channel; + + 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; } 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 82715ef4..0cc7d16e 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 @@ -17,6 +17,19 @@ 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.util.ALog; +import com.arialyy.aria.util.CommonUtil; +import com.arialyy.aria.util.FileUtil; +import com.jcraft.jsch.ChannelSftp; +import com.jcraft.jsch.Session; +import com.jcraft.jsch.SftpProgressMonitor; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.channels.FileChannel; /** * sftp 线程任务适配器 @@ -24,12 +37,154 @@ import com.arialyy.aria.core.task.AbsThreadTaskAdapter; * @author lyy */ final class SFtpDThreadTaskAdapter extends AbsThreadTaskAdapter { + private ChannelSftp channelSftp; + private Session session; + private FtpTaskOption option; SFtpDThreadTaskAdapter(SubThreadConfig config) { super(config); + session = (Session) config.obj; + option = (FtpTaskOption) getTaskWrapper().getTaskOption(); } @Override protected void handlerThreadTask() { + if (session == null) { + fail(new AriaException(TAG, "session 为空"), false); + return; + } + FileOutputStream fos; + try { + int timeout = getTaskConfig().getConnectTimeOut(); + if (!session.isConnected()) { + session.connect(timeout); + } + 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, + getThreadRecord().endLocation)); + // 开启服务器对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) { + e.printStackTrace(); + } finally { + channelSftp.disconnect(); + } + } + + 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) { + + 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; + } + return !getThreadTask().isBreak(); + } + + @Override public void end() { + if (getThreadTask().isBreak()) { + return; + } + + 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/app/build.gradle b/app/build.gradle index 50859d46..706b74d1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -72,6 +72,7 @@ dependencies { implementation project(':M3U8Component') implementation project(':FtpComponent') implementation project(path: ':AriaAnnotations') + implementation project(path: ':SFtpComponent') debugImplementation 'com.amitshekhar.android:debug-db:1.0.6' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 058ff739..e046ebc5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -32,6 +32,7 @@ + diff --git a/app/src/main/assets/id_rsa b/app/src/main/assets/id_rsa new file mode 100644 index 00000000..c5796584 --- /dev/null +++ b/app/src/main/assets/id_rsa @@ -0,0 +1,30 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-128-CBC,8CCEACDEBFE735A306E5E8FA84E90106 + +s9LGwTfk9PmcqF50jcFNpSB0O93ehxyD9FIahADuPwYnJCEBFQ+Hm06QOagVxB7z +44e23/SCAmo3cWes/IVXUag4+/p8UIcFZc7/CWIBlWaWKuyuP9vD4SD9EiPj6XlC +qaAsYcWxPodLoh4RQP08Chw1TET+XkOQuf1FuOil4eEeJYHBCpILF2I9lv7ujo3m +S3vS/LLpRUISOm7qVskpq0o1EXibdoSRRqvyzl7hzv+4ik7pDBl1Xun+E05So8Bz +aQP6Y8MpuowcFn0Q/OP10vcMm0tT8HwCz2qhfwN+3QQ/MdpePUw9DxCKsFS6cR6u +zILuS4a+/huQ2v2ebgTJK4gH/FYaHDP7jIVLKTB6lu4vxABHOrP19gz/Th7ZA/4i +JW3wR1VmYpSaGYVRnvzQ9sn/sYjntVOcXfOADHxy7VK2k7Ejwot88OJ96kkNCt26 +REcb3wM0AFu1sJSpDKLBqAvZEwOMhrbQW6KIorfKLYtywfEkwVdHa8JQY35Z5RrZ +qV5XvdW4LVyD0W4yjaeaeWVDShts2e7T+M9BV7LqYS+IKLrVWdQbn+7ymhi69UT8 +mLbiuLshyBDmjMqltS0Wa1Ejd3MTI2pmzgO5G8qTDL7oa2OiUO4aNCy9sy87fogH +iuE+tXrxmFeb8kSmFlblyrOi3EyRSkQEcg+JdooWZMTrxaIQUhYhlA+10JU2pc5w +cPg//c1Z2Hlgrx9tlecxDafPgH7AnLuG6J3AgZBnE1511h21iprnEKafMC9h+Lp8 +JmIeFnI3wLgfAz1olKeoRa/aHZGUiiD+GQxI21Q+XEXChAYf9vR7MuKFWqTI8jk7 +OfUMPXnKSvPBgTyNyTR8Kf3YBvm0HoWWSQr0YS7CbxsWNzdwpsnKn/qhwdiCt5kK +T1gS+yKTk6F/Z2+D0yBSaopOTumNYEDmDW3Er+r2fvM+UBTYdvYNCkaUysX1BK94 +4jmpeZZtagfppNlcQePuPv2zgckBjfeAdn4oQQqwyDS10ovWnTVEqn9yO0njZjXy +PhFZTX2R4Cn6UrW3AlLGCCdl3ogeybuoFF4dYMh6TiMgkD45XbsUrNq+iEBHSiFz +YRK4o40lMGvBxSa2bhZHtyoQlUJkptyFriCt9cH4WDbEIafLe/N29AP7ZUOQUOD8 +ZgyyUmER270uBzQig8hRXBJADZXYKqT3hUrosOt64tjw2CNIkMJPz/KVDOM66prb +WiiMV2l+xGnu0tLMCgp7TcCclBEPTlGfpsuP/iFu70j8u4vDjxNwWr3QyuqX0Krs +M6pfLHp4RSfUN9AbrjoSAu4XBVE5d8JNf7YGpx2D+HIV1hAEux9IWj+/lAA9eAEq +DnzI3JeYlWRCCF8Jg2VGt8fwQDsgTtFsHcaCuZqTPXGwsT3EIi/+9wQJshDyP/D4 +OWa8HFvkuluLeEDTW7B2u3+AFS01Gt92/fPcY06j+OJL4atW1c5XCF8inlt8Thop ++a33yUHpN8uGzkmKUz/onLxvMLYcTDDAXS2dtslij0ldSNC484GpWPHmLfW1jQCv +99FqBUbzGMy2RHP1xRHvqK2kV1N72QMde5fDLUP+vygtWFh/Nobi/guPR5OuO60R +-----END RSA PRIVATE KEY----- diff --git a/app/src/main/assets/id_rsa.pub b/app/src/main/assets/id_rsa.pub new file mode 100644 index 00000000..7606849d --- /dev/null +++ b/app/src/main/assets/id_rsa.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDBlgTuAhvU/Z0ni2PszmIXkDvANMWNYHfiCfvOxRKoK/jCGmKw/TDF1Z49P5M5CMpKwTaQKDxfolYeCgvH9eBmY1NiE26COWexgoqxrw4pWE9/h2Thx7cVWjGWZZ4iwyYfT19/5SgPNujiW9cWGV9N8VXAKe6Sl7v+6TtjLHNCNEn7B90m6sRIYHXsAQ7W7TBlUD5S6wcAsL6v6RVMVVBoIXykNW+zQK6prAWPCtPWcEw3WwfCIUQfJLS8gMC++Ox/bT9gqR+uZTk8MGxafxa9lrvIvpcZh8uVzaP428E3mlSkJnhTAzk43VoKAKxDA1Z+Qehig7VP9GsSn2HGuYE7 AriaL@DESKTOP-BBML2VA diff --git a/app/src/main/java/com/arialyy/simple/MainActivity.java b/app/src/main/java/com/arialyy/simple/MainActivity.java index 8ac51204..e68eaf49 100644 --- a/app/src/main/java/com/arialyy/simple/MainActivity.java +++ b/app/src/main/java/com/arialyy/simple/MainActivity.java @@ -38,6 +38,7 @@ import com.arialyy.simple.base.adapter.AbsRVAdapter; import com.arialyy.simple.base.adapter.RvItemClickSupport; import com.arialyy.simple.core.download.DownloadActivity; import com.arialyy.simple.core.download.FtpDownloadActivity; +import com.arialyy.simple.core.download.SFtpDownloadActivity; import com.arialyy.simple.core.download.group.DownloadGroupActivity; import com.arialyy.simple.core.download.group.FTPDirDownloadActivity; import com.arialyy.simple.core.download.m3u8.M3U8LiveDLoadActivity; @@ -117,6 +118,8 @@ public class MainActivity extends BaseActivity { M3U8LiveDLoadActivity.class); break; case 8: // sftp + module.startNextActivity(MainActivity.this, data.get(position), + SFtpDownloadActivity.class); break; } } diff --git a/app/src/main/java/com/arialyy/simple/core/download/FtpDownloadModule.java b/app/src/main/java/com/arialyy/simple/core/download/FtpDownloadModule.java index f6cb7ee5..88f80f19 100644 --- a/app/src/main/java/com/arialyy/simple/core/download/FtpDownloadModule.java +++ b/app/src/main/java/com/arialyy/simple/core/download/FtpDownloadModule.java @@ -41,7 +41,6 @@ public class FtpDownloadModule extends BaseViewModule { private DownloadEntity singDownloadInfo; /** - * xx * 单任务下载的信息 */ LiveData getFtpDownloadInfo(Context context) { @@ -65,6 +64,30 @@ public class FtpDownloadModule extends BaseViewModule { return liveData; } + /** + * 单任务下载的信息 + */ + LiveData getSftpDownloadInfo(Context context) { + //String url = AppUtil.getConfigValue(context, FTP_URL_KEY, ftpDefUrl); + //String filePath = AppUtil.getConfigValue(context, FTP_PATH_KEY, ftpDefPath); + String url = "ftp://9.9.9.72:22/Cyberduck-6.9.4.30164.zip"; + + singDownloadInfo = Aria.download(context).getFirstDownloadEntity(url); + if (singDownloadInfo == null) { + singDownloadInfo = new DownloadEntity(); + singDownloadInfo.setUrl(url); + String name = getFileName(ftpDefUrl); + singDownloadInfo.setFileName(name); + singDownloadInfo.setFilePath(ftpDefPath + name); + } else { + AppUtil.setConfigValue(context, FTP_PATH_KEY, singDownloadInfo.getFilePath()); + AppUtil.setConfigValue(context, FTP_URL_KEY, singDownloadInfo.getUrl()); + } + liveData.postValue(singDownloadInfo); + + return liveData; + } + /** * 更新文件保存路径 * diff --git a/app/src/main/java/com/arialyy/simple/core/download/SFtpDownloadActivity.java b/app/src/main/java/com/arialyy/simple/core/download/SFtpDownloadActivity.java new file mode 100644 index 00000000..67c4fbc2 --- /dev/null +++ b/app/src/main/java/com/arialyy/simple/core/download/SFtpDownloadActivity.java @@ -0,0 +1,232 @@ +/* + * 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.download; + +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import androidx.annotation.Nullable; +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.download.DownloadEntity; +import com.arialyy.aria.core.inf.IEntity; +import com.arialyy.aria.core.task.DownloadTask; +import com.arialyy.aria.util.ALog; +import com.arialyy.aria.util.CommonUtil; +import com.arialyy.aria.util.FileUtil; +import com.arialyy.frame.util.show.L; +import com.arialyy.frame.util.show.T; +import com.arialyy.simple.R; +import com.arialyy.simple.base.BaseActivity; +import com.arialyy.simple.common.DirChooseDialog; +import com.arialyy.simple.common.ModifyUrlDialog; +import com.arialyy.simple.databinding.ActivitySftpDownloadBinding; +import java.io.File; +import java.io.IOException; + +/** + * Created by lyy on 2017/7/25. + * Ftp下载 + */ +public class SFtpDownloadActivity extends BaseActivity { + private String mUrl, mFilePath; + private FtpDownloadModule mModule; + private long mTaskId; + private String user = "tester", passw = "password"; + private String prvKeyPath; + private String pubKeyPath; + + @Override protected void init(Bundle savedInstanceState) { + super.init(savedInstanceState); + setTitle("FTP文件下载"); + Aria.download(this).register(); + prvKeyPath = getFilesDir().getPath() + "/id_rsa"; + pubKeyPath = getFilesDir().getPath() + "/id_rsa.pub"; + copyKey(); + mModule = ViewModelProviders.of(this).get(FtpDownloadModule.class); + mModule.getSftpDownloadInfo(this).observe(this, new Observer() { + + @Override public void onChanged(@Nullable DownloadEntity entity) { + if (entity == null) { + return; + } + mTaskId = entity.getId(); + if (entity.getState() == IEntity.STATE_STOP) { + getBinding().setStateStr(getString(R.string.resume)); + } else if (entity.getState() == IEntity.STATE_RUNNING) { + getBinding().setStateStr(getString(R.string.stop)); + } + + 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().setViewModel(this); + //try { + // getBinding().codeView.setSource(AppUtil.getHelpCode(this, "FtpDownload.java")); + //} catch (IOException e) { + // e.printStackTrace(); + //} + } + + private void copyKey() { + try { + // 为了测试方便,每次重新覆盖证书文件 + File prvKey = new File(prvKeyPath); + //FileUtil.deleteFile(prvKey); + if (!prvKey.exists()) { + FileUtil.createFileFormInputStream(getAssets().open("id_rsa"), prvKeyPath); + } + File pubKey = new File(pubKeyPath); + //FileUtil.deleteFile(pubKey); + if (!pubKey.exists()) { + FileUtil.createFileFormInputStream(getAssets().open("id_rsa.pub"), pubKeyPath); + } + + } catch (IOException e) { + e.printStackTrace(); + } + } + + public void onClick(View view) { + + switch (view.getId()) { + case R.id.start: + + if (mTaskId == -1) { + mTaskId = Aria.download(this).loadFtp(mUrl) + .setFilePath(mFilePath, true) + .sftpOption(getFtpOption()) + .create(); + getBinding().setStateStr(getString(R.string.stop)); + break; + } + if (Aria.download(this).load(mTaskId).isRunning()) { + getBinding().setStateStr(getString(R.string.resume)); + Aria.download(this).loadFtp(mTaskId).stop(); + } else { + Aria.download(this) + .loadFtp(mTaskId) + .sftpOption(getFtpOption()) + .resume(); + getBinding().setStateStr(getString(R.string.stop)); + } + break; + + case R.id.cancel: + Aria.download(this).loadFtp(mTaskId).cancel(true); + getBinding().setStateStr(getString(R.string.start)); + mTaskId = -1; + break; + } + } + + private FtpOption getFtpOption() { + FtpOption option = new FtpOption(); + option.login(user, passw); // 账号密码登录 + // 证书登录 + option.setPrvKey(prvKeyPath); // 设置私钥 + option.setPrvKeyPass("123456"); // 设置私钥密码(如果没有密码,可以不设置) + option.setPubKey(pubKeyPath); // 设置公钥 + option.setKnowHostPath(getFilesDir().getPath() + "/know_hosts"); + + //option.setServerIdentifier(FtpOption.FTPServerIdentifier.SYST_NT); + //option.setConnectionMode(FtpConnectionMode.DATA_CONNECTION_MODE_ACTIVITY); + return option; + } + + public void chooseUrl() { + ModifyUrlDialog dialog = + new ModifyUrlDialog(this, getString(R.string.modify_url_dialog_title), mUrl); + dialog.show(getSupportFragmentManager(), "ModifyUrlDialog"); + } + + public void chooseFilePath() { + DirChooseDialog dirChooseDialog = new DirChooseDialog(this); + dirChooseDialog.show(getSupportFragmentManager(), "DirChooseDialog"); + } + + @Download.onPre() protected void onPre(DownloadTask task) { + L.d(TAG, "ftp pre"); + } + + @Download.onTaskPre() protected void onTaskPre(DownloadTask task) { + L.d(TAG, "ftp task pre, fileSize = " + task.getConvertFileSize()); + getBinding().setFileSize(task.getConvertFileSize()); + } + + @Download.onTaskStart() void taskStart(DownloadTask task) { + L.d(TAG, "ftp task create"); + } + + @Download.onTaskRunning() protected void running(DownloadTask task) { + ALog.d(TAG, "running, p = " + task.getPercent() + ", speed = " + task.getConvertSpeed()); + getBinding().setProgress(task.getPercent()); + getBinding().setSpeed(task.getConvertSpeed()); + } + + @Download.onTaskResume() void taskResume(DownloadTask task) { + L.d(TAG, "ftp task resume"); + } + + @Download.onTaskStop() void taskStop(DownloadTask task) { + L.d(TAG, "ftp task stop"); + getBinding().setSpeed(""); + getBinding().setStateStr(getString(R.string.resume)); + } + + @Download.onTaskCancel() void taskCancel(DownloadTask task) { + getBinding().setSpeed(""); + getBinding().setProgress(0); + } + + @Download.onTaskFail() void taskFail(DownloadTask task) { + L.d(TAG, "ftp task fail"); + getBinding().setSpeed(""); + getBinding().setStateStr(getString(R.string.resume)); + } + + @Download.onTaskComplete() void taskComplete(DownloadTask task) { + getBinding().setSpeed(""); + getBinding().setProgress(100); + getBinding().setStateStr(getString(R.string.re_start)); + Log.d(TAG, "md5 ==> " + CommonUtil.getFileMD5(new File(task.getFilePath()))); + T.showShort(this, "文件:" + task.getEntity().getFileName() + ",下载完成"); + } + + @Override protected int setLayoutId() { + return R.layout.activity_sftp_download; + } + + @Override protected void dataCallback(int result, Object data) { + super.dataCallback(result, data); + if (result == ModifyUrlDialog.MODIFY_URL_DIALOG_RESULT) { + mModule.uploadUrl(this, String.valueOf(data)); + } else if (result == DirChooseDialog.DIR_CHOOSE_DIALOG_RESULT) { + mModule.updateFilePath(this, String.valueOf(data)); + } + } +} diff --git a/app/src/main/java/com/arialyy/simple/util/AppUtil.java b/app/src/main/java/com/arialyy/simple/util/AppUtil.java index ecf9d0d1..96054d2b 100644 --- a/app/src/main/java/com/arialyy/simple/util/AppUtil.java +++ b/app/src/main/java/com/arialyy/simple/util/AppUtil.java @@ -26,7 +26,6 @@ import android.text.TextUtils; import androidx.core.content.FileProvider; import com.arialyy.aria.core.common.AbsEntity; import com.arialyy.aria.util.ALog; -import com.arialyy.aria.util.CommonUtil; import com.arialyy.aria.util.FileUtil; import com.arialyy.simple.BuildConfig; import java.io.File; @@ -53,7 +52,7 @@ public class AppUtil { File ftpCode = new File(path); if (!ftpCode.exists()) { FileUtil.createFile(ftpCode); - CommonUtil.createFileFormInputStream(context.getAssets() + FileUtil.createFileFormInputStream(context.getAssets() .open(String.format("help_code/%s", fileName)), path); } diff --git a/app/src/main/res/layout/activity_sftp_download.xml b/app/src/main/res/layout/activity_sftp_download.xml new file mode 100644 index 00000000..5df9badb --- /dev/null +++ b/app/src/main/res/layout/activity_sftp_download.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file