sftp 下载实现
使用零拷贝技术优化合并分块的功能
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +47,20 @@ public class FtpBuilderTarget extends AbsBuilderTarget<FtpBuilderTarget> {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置文件保存文件夹路径
|
||||
* 关于文件名:
|
||||
|
||||
@@ -46,6 +46,20 @@ public class FtpNormalTarget extends AbsNormalTarget<FtpNormalTarget> {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置文件保存文件夹路径
|
||||
* 关于文件名:
|
||||
|
||||
@@ -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<M3U8NormalTarget> {
|
||||
@@ -28,6 +29,7 @@ public class M3U8NormalTarget extends AbsNormalTarget<M3U8NormalTarget> {
|
||||
M3U8NormalTarget(DTaskWrapper wrapper) {
|
||||
setTaskWrapper(wrapper);
|
||||
getTaskWrapper().setNewTask(false);
|
||||
getTaskWrapper().setRequestType(ITaskWrapper.M3U8_VOD);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -136,7 +136,8 @@ public abstract class BaseListener<ENTITY extends AbsEntity, TASK_WRAPPER extend
|
||||
mEntity.setConvertSpeed(CommonUtil.formatFileSize(speed < 0 ? 0 : speed) + "/s");
|
||||
}
|
||||
mEntity.setSpeed(speed < 0 ? 0 : speed);
|
||||
if (mTaskWrapper.getRequestType() != ITaskWrapper.M3U8_VOD) {
|
||||
int taskType = mTaskWrapper.getRequestType();
|
||||
if (taskType != ITaskWrapper.M3U8_VOD && taskType != ITaskWrapper.M3U8_LIVE) {
|
||||
mEntity.setPercent((int) (mEntity.getFileSize() <= 0 ? 0
|
||||
: mEntity.getCurrentProgress() * 100 / mEntity.getFileSize()));
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ 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;
|
||||
@@ -229,7 +230,9 @@ 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 = FileUtil.mergeFile(mTaskRecord.filePath, partPath);
|
||||
boolean isSuccess = mTaskRecord.taskType == ITaskWrapper.D_SFTP ?
|
||||
FileUtil.mergeSFtpFile(mTaskRecord.filePath, partPath, mTaskRecord.fileLength)
|
||||
: FileUtil.mergeFile(mTaskRecord.filePath, partPath);
|
||||
if (isSuccess) {
|
||||
for (String pp : partPath) {
|
||||
File f = new File(pp);
|
||||
|
||||
@@ -327,6 +327,9 @@ public class ThreadTask implements IThreadTask, IThreadTaskObserver {
|
||||
fail(mRangeProgress, e, needRetry);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param len 新增的长度
|
||||
*/
|
||||
@Override
|
||||
public synchronized void updateProgress(long len) {
|
||||
mRangeProgress += len;
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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.exception;
|
||||
|
||||
public class AriaException extends BaseException {
|
||||
private static final String ARIA_NET_EXCEPTION = "Aria Exception:";
|
||||
|
||||
public AriaException(String tag, String message) {
|
||||
super(tag, String.format("%s%s", ARIA_NET_EXCEPTION, message));
|
||||
}
|
||||
|
||||
public AriaException(String tag, String message, Exception e) {
|
||||
super(tag, message, e);
|
||||
}
|
||||
}
|
||||
@@ -481,24 +481,6 @@ public class CommonUtil {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过流创建文件
|
||||
*/
|
||||
public static void createFileFormInputStream(InputStream is, String path) {
|
||||
try {
|
||||
FileOutputStream fos = new FileOutputStream(path);
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验文件MD5码
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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<FileInputStream> 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<FileInputStream> en = Collections.enumeration(streams);
|
||||
SequenceInputStream sis = new SequenceInputStream(en);
|
||||
ReadableByteChannel fic = Channels.newChannel(sis);
|
||||
ByteBuffer bf = ByteBuffer.allocate(8196);
|
||||
while (fic.read(bf) != -1) {
|
||||
bf.flip();
|
||||
foc.write(bf);
|
||||
bf.compact();
|
||||
}
|
||||
//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<String> 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<FileInputStream> 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();
|
||||
|
||||
@@ -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<WP extends AbsTaskWrapper> 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<WP extends AbsTaskWrapper> 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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建客户端
|
||||
*/
|
||||
private void createClient() {
|
||||
JSch jSch = new JSch();
|
||||
try {
|
||||
if (TextUtils.isEmpty(userName)) {
|
||||
session = jSch.getSession(userName, ip, port);
|
||||
} else {
|
||||
session = jSch.getSession(ip);
|
||||
public synchronized static SFtpUtil getInstance() {
|
||||
if (INSTANCE == null) {
|
||||
synchronized (SFtpUtil.class) {
|
||||
INSTANCE = new SFtpUtil();
|
||||
}
|
||||
if (!TextUtils.isEmpty(password)) {
|
||||
session.setPassword(password);
|
||||
}
|
||||
Properties config = new Properties();
|
||||
config.put("StrictHostKeyChecking", "no");
|
||||
session.setConfig(config);// 为Session对象设置properties
|
||||
session.setTimeout(3000);// 设置超时
|
||||
login();
|
||||
isLogin = true;
|
||||
} catch (JSchException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行登录
|
||||
*/
|
||||
public Session login() {
|
||||
try {
|
||||
session.connect(); // 通过Session建立连接
|
||||
} catch (JSchException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* 登出
|
||||
*/
|
||||
public void logout() {
|
||||
if (session != null) {
|
||||
session.disconnect();
|
||||
}
|
||||
isLogin = false;
|
||||
}
|
||||
|
||||
public Session getSession() {
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行命令
|
||||
* 创建jsch 的session
|
||||
*
|
||||
* @param cmd sftp命令
|
||||
* @param threadId 线程id,默认0
|
||||
* @throws JSchException
|
||||
* @throws UnsupportedEncodingException
|
||||
*/
|
||||
public void execCommand(String cmd) {
|
||||
if (TextUtils.isEmpty(cmd)) {
|
||||
ALog.e(TAG, "命令为空");
|
||||
return;
|
||||
}
|
||||
if (!isLogin) {
|
||||
ALog.e(TAG, "没有登录");
|
||||
return;
|
||||
}
|
||||
ChannelExec channel = null;
|
||||
try {
|
||||
channel = (ChannelExec) session.openChannel(CMD_TYPE_EXEC);
|
||||
channel.setCommand(cmd);
|
||||
channel.connect();
|
||||
String rst = getResult(channel.getInputStream());
|
||||
public Session getSession(FtpUrlEntity entity, int threadId) throws JSchException,
|
||||
UnsupportedEncodingException {
|
||||
|
||||
ALog.i(TAG, String.format("result: %s", rst));
|
||||
JSch jSch = new JSch();
|
||||
|
||||
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 {
|
||||
jSch.addIdentity(idEntity.prvKey, idEntity.pubKey,
|
||||
entity.password == null ? null : idEntity.prvPass.getBytes("UTF-8"));
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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));
|
||||
// }
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] getPubKey(String pubKeyPath) {
|
||||
try {
|
||||
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 (IOException e) {
|
||||
e.printStackTrace();
|
||||
} catch (JSchException 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<DTaskWrapper> {
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity android:name=".core.download.SFtpDownloadActivity"/>
|
||||
<activity android:name=".core.download.DownloadActivity"/>
|
||||
<activity android:name=".core.download.SingleTaskActivity"/>
|
||||
<activity android:name=".core.download.mutil.MultiTaskActivity"/>
|
||||
|
||||
30
app/src/main/assets/id_rsa
Normal file
30
app/src/main/assets/id_rsa
Normal file
@@ -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-----
|
||||
1
app/src/main/assets/id_rsa.pub
Normal file
1
app/src/main/assets/id_rsa.pub
Normal file
@@ -0,0 +1 @@
|
||||
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDBlgTuAhvU/Z0ni2PszmIXkDvANMWNYHfiCfvOxRKoK/jCGmKw/TDF1Z49P5M5CMpKwTaQKDxfolYeCgvH9eBmY1NiE26COWexgoqxrw4pWE9/h2Thx7cVWjGWZZ4iwyYfT19/5SgPNujiW9cWGV9N8VXAKe6Sl7v+6TtjLHNCNEn7B90m6sRIYHXsAQ7W7TBlUD5S6wcAsL6v6RVMVVBoIXykNW+zQK6prAWPCtPWcEw3WwfCIUQfJLS8gMC++Ox/bT9gqR+uZTk8MGxafxa9lrvIvpcZh8uVzaP428E3mlSkJnhTAzk43VoKAKxDA1Z+Qehig7VP9GsSn2HGuYE7 AriaL@DESKTOP-BBML2VA
|
||||
@@ -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<ActivityMainBinding> {
|
||||
M3U8LiveDLoadActivity.class);
|
||||
break;
|
||||
case 8: // sftp
|
||||
module.startNextActivity(MainActivity.this, data.get(position),
|
||||
SFtpDownloadActivity.class);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,6 @@ public class FtpDownloadModule extends BaseViewModule {
|
||||
private DownloadEntity singDownloadInfo;
|
||||
|
||||
/**
|
||||
* xx
|
||||
* 单任务下载的信息
|
||||
*/
|
||||
LiveData<DownloadEntity> getFtpDownloadInfo(Context context) {
|
||||
@@ -65,6 +64,30 @@ public class FtpDownloadModule extends BaseViewModule {
|
||||
return liveData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 单任务下载的信息
|
||||
*/
|
||||
LiveData<DownloadEntity> 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新文件保存路径
|
||||
*
|
||||
|
||||
@@ -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<ActivitySftpDownloadBinding> {
|
||||
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<DownloadEntity>() {
|
||||
|
||||
@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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
86
app/src/main/res/layout/activity_sftp_download.xml
Normal file
86
app/src/main/res/layout/activity_sftp_download.xml
Normal file
@@ -0,0 +1,86 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:bind="http://schemas.android.com/apk/res-auto"
|
||||
>
|
||||
|
||||
<data>
|
||||
<variable
|
||||
name="fileSize"
|
||||
type="String"
|
||||
/>
|
||||
<variable
|
||||
name="speed"
|
||||
type="String"
|
||||
/>
|
||||
<variable
|
||||
name="progress"
|
||||
type="int"
|
||||
/>
|
||||
<variable
|
||||
name="stateStr"
|
||||
type="String"
|
||||
/>
|
||||
|
||||
<variable
|
||||
name="url"
|
||||
type="String"
|
||||
/>
|
||||
<variable
|
||||
name="filePath"
|
||||
type="String"
|
||||
/>
|
||||
<variable
|
||||
name="viewModel"
|
||||
type="com.arialyy.simple.core.download.SFtpDownloadActivity"
|
||||
/>
|
||||
</data>
|
||||
|
||||
<LinearLayout
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true"
|
||||
android:orientation="vertical"
|
||||
tools:context=".core.download.SingleTaskActivity"
|
||||
>
|
||||
|
||||
<include layout="@layout/layout_bar"/>
|
||||
|
||||
<com.arialyy.simple.widget.SvgTextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
bind:iconClickListener="@{() -> viewModel.chooseUrl()}"
|
||||
bind:svg_text_view_icon="@drawable/ic_modify"
|
||||
bind:text="@{@string/url(url)}"
|
||||
/>
|
||||
|
||||
<com.arialyy.simple.widget.SvgTextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
bind:iconClickListener="@{() -> viewModel.chooseFilePath()}"
|
||||
bind:svg_text_view_icon="@drawable/ic_choose_file"
|
||||
bind:text="@{@string/file_path(filePath)}"
|
||||
/>
|
||||
|
||||
<include
|
||||
layout="@layout/layout_content_single"
|
||||
bind:fileSize="@{fileSize}"
|
||||
bind:progress="@{progress}"
|
||||
bind:speed="@{speed}"
|
||||
bind:stateStr="@{stateStr}"
|
||||
/>
|
||||
|
||||
<!-- <com.arialyy.simple.widget.CodeView-->
|
||||
<!-- android:id="@+id/code_view"-->
|
||||
<!-- android:layout_width="match_parent"-->
|
||||
<!-- android:layout_height="match_parent"-->
|
||||
<!-- />-->
|
||||
|
||||
</LinearLayout>
|
||||
</layout>
|
||||
Reference in New Issue
Block a user