sftp上传实现

This commit is contained in:
laoyuyu
2020-01-18 10:00:06 +08:00
parent 50b265f22a
commit d217ebcb25
59 changed files with 1616 additions and 387 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,92 @@
/*
* Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.arialyy.aria.sftp.download;
import com.arialyy.aria.core.TaskRecord;
import com.arialyy.aria.core.ThreadRecord;
import com.arialyy.aria.core.common.RecordHandler;
import com.arialyy.aria.core.common.RecordHelper;
import com.arialyy.aria.core.config.Configuration;
import com.arialyy.aria.core.download.DTaskWrapper;
import com.arialyy.aria.core.loader.IRecordHandler;
import com.arialyy.aria.core.wrapper.ITaskWrapper;
import com.arialyy.aria.util.RecordUtil;
import java.util.ArrayList;
/**
* @Author lyy
* @Date 2019-09-19
*/
final class SFtpDRecordHandler extends RecordHandler {
SFtpDRecordHandler(DTaskWrapper wrapper) {
super(wrapper);
}
@Override public void handlerTaskRecord(TaskRecord record) {
RecordHelper helper = new RecordHelper(getWrapper(), record);
if (record.threadNum == 1) {
helper.handleSingleThreadRecord();
} else {
if (getWrapper().isSupportBP()) {
if (record.isBlock) {
helper.handleBlockRecord();
} else {
helper.handleMultiRecord();
}
}
}
}
@Override
public ThreadRecord createThreadRecord(TaskRecord record, int threadId, long startL, long endL) {
ThreadRecord tr;
tr = new ThreadRecord();
tr.taskKey = record.filePath;
tr.threadId = threadId;
tr.startLocation = startL;
tr.isComplete = false;
tr.threadType = record.taskType;
//最后一个线程的结束位置即为文件的总长度
if (threadId == (record.threadNum - 1)) {
endL = getFileSize();
}
tr.endLocation = endL;
tr.blockLen = RecordUtil.getBlockLen(getFileSize(), threadId, record.threadNum);
return tr;
}
@Override public TaskRecord createTaskRecord(int threadNum) {
TaskRecord record = new TaskRecord();
record.fileName = getEntity().getFileName();
record.filePath = getEntity().getFilePath();
record.threadRecords = new ArrayList<>();
record.threadNum = threadNum;
record.isBlock = threadNum > 1;
record.taskType = ITaskWrapper.D_SFTP;
record.isGroupRecord = false;
return record;
}
@Override public int initTaskThreadNum() {
int threadNum = Configuration.getInstance().downloadCfg.getThreadNum();
return getFileSize() <= IRecordHandler.SUB_LEN
|| threadNum == 1
? 1
: threadNum;
}
}

View File

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

View File

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

View File

@@ -0,0 +1,69 @@
/*
* Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.arialyy.aria.sftp.upload;
import com.arialyy.aria.core.common.CompleteInfo;
import com.arialyy.aria.core.upload.UTaskWrapper;
import com.arialyy.aria.core.upload.UploadEntity;
import com.arialyy.aria.sftp.AbsSFtpInfoTask;
import com.arialyy.aria.sftp.SFtpTaskOption;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.SftpATTRS;
import com.jcraft.jsch.SftpException;
import java.io.UnsupportedEncodingException;
final class SFtpUInfoTask extends AbsSFtpInfoTask<UTaskWrapper> {
static final int ISCOMPLETE = 0xa1;
SFtpUInfoTask(UTaskWrapper uTaskWrapper) {
super(uTaskWrapper);
}
@Override protected void getFileInfo(Session session)
throws JSchException, UnsupportedEncodingException, SftpException {
SFtpTaskOption option = (SFtpTaskOption) getWrapper().getTaskOption();
ChannelSftp channel = (ChannelSftp) session.openChannel("sftp");
channel.connect(1000);
String remotePath = option.getUrlEntity().remotePath;
String temp = CommonUtil.convertSFtpChar(getOption().getCharSet(), remotePath)
+ "/"
+ getWrapper().getEntity().getFileName();
SftpATTRS attr = null;
try {
attr = channel.stat(temp);
} catch (Exception e) {
ALog.d(TAG, String.format("文件不存在remotePath%s", remotePath));
}
boolean isComplete = false;
UploadEntity entity = getWrapper().getEntity();
if (attr != null && attr.getSize() == entity.getFileSize()) {
isComplete = true;
}
CompleteInfo info = new CompleteInfo();
info.code = isComplete ? ISCOMPLETE : 200;
info.obj = attr;
channel.disconnect();
callback.onSucceed(getWrapper().getKey(), info);
}
}

View File

@@ -0,0 +1,155 @@
/*
* Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.arialyy.aria.sftp.upload;
import android.os.Handler;
import android.os.Looper;
import com.arialyy.aria.core.common.AbsEntity;
import com.arialyy.aria.core.common.CompleteInfo;
import com.arialyy.aria.core.event.EventMsgUtil;
import com.arialyy.aria.core.inf.IThreadStateManager;
import com.arialyy.aria.core.listener.IDLoadListener;
import com.arialyy.aria.core.listener.IEventListener;
import com.arialyy.aria.core.loader.AbsNormalLoader;
import com.arialyy.aria.core.loader.IInfoTask;
import com.arialyy.aria.core.loader.IRecordHandler;
import com.arialyy.aria.core.loader.IThreadTaskBuilder;
import com.arialyy.aria.core.manager.ThreadTaskManager;
import com.arialyy.aria.core.task.IThreadTask;
import com.arialyy.aria.core.upload.UTaskWrapper;
import com.arialyy.aria.core.upload.UploadEntity;
import com.arialyy.aria.exception.BaseException;
import com.arialyy.aria.util.FileUtil;
import com.jcraft.jsch.SftpATTRS;
import java.io.File;
final class SFtpULoader extends AbsNormalLoader<UTaskWrapper> {
private Looper looper;
SFtpULoader(UTaskWrapper wrapper, IEventListener listener) {
super(wrapper, listener);
mTempFile = new File(getEntity().getFilePath());
EventMsgUtil.getDefault().register(this);
setUpdateInterval(wrapper.getConfig().getUpdateInterval());
}
private UploadEntity getEntity() {
return mTaskWrapper.getEntity();
}
@Override public long getFileSize() {
return getEntity().getFileSize();
}
/**
* 设置最大下载/上传速度AbsFtpInfoThread
*
* @param maxSpeed 单位为kb
*/
protected void setMaxSpeed(int maxSpeed) {
for (IThreadTask threadTask : getTaskList()) {
if (threadTask != null) {
threadTask.setMaxSpeed(maxSpeed);
}
}
}
@Override public void onDestroy() {
super.onDestroy();
EventMsgUtil.getDefault().unRegister(this);
}
/**
* 启动单线程任务
*/
@Override
public void handleTask(Looper looper) {
if (isBreak()) {
return;
}
this.looper = looper;
mInfoTask.run();
}
private void startThreadTask(SftpATTRS attrs) {
if (getListener() instanceof IDLoadListener) {
((IDLoadListener) getListener()).onPostPre(getEntity().getFileSize());
}
File file = new File(getEntity().getFilePath());
if (file.getParentFile() != null && !file.getParentFile().exists()) {
FileUtil.createDir(file.getPath());
}
// 处理记录、初始化状态管理器
SFtpURecordHandler recordHandler = (SFtpURecordHandler) mRecordHandler;
recordHandler.setFtpAttrs(attrs);
mRecord = recordHandler.getRecord(getFileSize());
mStateManager.setLooper(mRecord, looper);
// 创建线程任务
getTaskList().addAll(mTTBuilder.buildThreadTask(mRecord,
new Handler(looper, mStateManager.getHandlerCallback())));
mStateManager.updateCurrentProgress(getEntity().getCurrentProgress());
if (mStateManager.getCurrentProgress() > 0) {
getListener().onResume(mStateManager.getCurrentProgress());
} else {
getListener().onStart(mStateManager.getCurrentProgress());
}
// 启动线程任务
for (IThreadTask threadTask : getTaskList()) {
ThreadTaskManager.getInstance().startThread(mTaskWrapper.getKey(), threadTask);
}
// 启动定时器
startTimer();
}
@Override public long getCurrentProgress() {
return isRunning() ? mStateManager.getCurrentProgress() : getEntity().getCurrentProgress();
}
@Override public void addComponent(IRecordHandler recordHandler) {
mRecordHandler = recordHandler;
}
@Override public void addComponent(IInfoTask infoTask) {
mInfoTask = infoTask;
infoTask.setCallback(new IInfoTask.Callback() {
@Override public void onSucceed(String key, CompleteInfo info) {
if (info.code == SFtpUInfoTask.ISCOMPLETE) {
getListener().onComplete();
} else {
startThreadTask((SftpATTRS) info.obj);
}
}
@Override public void onFail(AbsEntity entity, BaseException e, boolean needRetry) {
getListener().onFail(needRetry, e);
}
});
}
@Override public void addComponent(IThreadStateManager threadState) {
mStateManager = threadState;
}
@Override public void addComponent(IThreadTaskBuilder builder) {
mTTBuilder = builder;
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.arialyy.aria.sftp.upload;
import com.arialyy.aria.core.listener.IEventListener;
import com.arialyy.aria.core.loader.AbsNormalLoader;
import com.arialyy.aria.core.loader.AbsNormalLoaderUtil;
import com.arialyy.aria.core.loader.LoaderStructure;
import com.arialyy.aria.core.loader.NormalTTBuilder;
import com.arialyy.aria.core.loader.NormalThreadStateManager;
import com.arialyy.aria.core.upload.UTaskWrapper;
import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
import com.arialyy.aria.sftp.SFtpTaskOption;
/**
* sftp下载工具
*
* @author lyy
*/
public class SFtpULoaderUtil extends AbsNormalLoaderUtil {
public SFtpULoaderUtil(AbsTaskWrapper wrapper, IEventListener listener) {
super(wrapper, listener);
wrapper.generateTaskOption(SFtpTaskOption.class);
}
@Override public AbsNormalLoader getLoader() {
return mLoader == null ? new SFtpULoader((UTaskWrapper) getTaskWrapper(), getListener())
: mLoader;
}
@Override public LoaderStructure BuildLoaderStructure() {
LoaderStructure structure = new LoaderStructure();
structure.addComponent(new SFtpURecordHandler((UTaskWrapper) getTaskWrapper()))
.addComponent(new NormalThreadStateManager(getListener()))
.addComponent(new SFtpUInfoTask((UTaskWrapper) getTaskWrapper()))
.addComponent(new NormalTTBuilder(getTaskWrapper(), new SFtpUTTBuilderAdapter(
(UTaskWrapper) getTaskWrapper())));
structure.accept(getLoader());
return structure;
}
}

View File

@@ -0,0 +1,115 @@
/*
* Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.arialyy.aria.sftp.upload;
import com.arialyy.aria.core.TaskRecord;
import com.arialyy.aria.core.ThreadRecord;
import com.arialyy.aria.core.common.RecordHandler;
import com.arialyy.aria.core.upload.UTaskWrapper;
import com.arialyy.aria.core.upload.UploadEntity;
import com.arialyy.aria.core.wrapper.ITaskWrapper;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.RecordUtil;
import com.jcraft.jsch.SftpATTRS;
import java.util.ArrayList;
/**
* 上传任务记录处理器
*/
final class SFtpURecordHandler extends RecordHandler {
private SftpATTRS ftpAttrs;
SFtpURecordHandler(UTaskWrapper wrapper) {
super(wrapper);
}
void setFtpAttrs(SftpATTRS ftpAttrs) {
this.ftpAttrs = ftpAttrs;
}
@Override public void handlerTaskRecord(TaskRecord record) {
if (record.threadRecords == null || record.threadRecords.isEmpty()) {
record.threadRecords = new ArrayList<>();
record.threadRecords.add(
createThreadRecord(record, 0, ftpAttrs == null ? 0 : ftpAttrs.getSize(), getFileSize()));
}
if (ftpAttrs != null) {
UploadEntity entity = (UploadEntity) getWrapper().getEntity();
//远程文件已完成
if (ftpAttrs.getSize() == getFileSize()) {
record.threadRecords.get(0).isComplete = true;
ALog.d(TAG, "FTP服务器上已存在该文件【" + entity.getFileName() + "");
} else if (ftpAttrs.getSize() == 0) {
getWrapper().setNewTask(true);
ALog.d(TAG, "FTP服务器上已存在该文件【" + entity.getFileName() + "但文件长度为0重新上传该文件");
} else {
ALog.w(TAG, "FTP服务器已存在未完成的文件【"
+ entity.getFileName()
+ "size: "
+ ftpAttrs.getSize()
+ ""
+ "尝试从位置:"
+ (ftpAttrs.getSize() - 1)
+ "开始上传");
getWrapper().setNewTask(false);
// 修改记录
ThreadRecord threadRecord = record.threadRecords.get(0);
//修改本地保存的停止地址为服务器上对应文件的大小
threadRecord.startLocation = ftpAttrs.getSize() - 1;
}
} else {
ALog.d(TAG, "SFTP服务器上不存在该文件");
getWrapper().setNewTask(true);
ThreadRecord tr = record.threadRecords.get(0);
tr.startLocation = 0;
tr.endLocation = getFileSize();
tr.isComplete = false;
}
}
@Override
public ThreadRecord createThreadRecord(TaskRecord record, int threadId, long startL, long endL) {
ThreadRecord tr;
tr = new ThreadRecord();
tr.taskKey = record.filePath;
tr.threadId = threadId;
tr.startLocation = startL;
tr.isComplete = false;
tr.threadType = record.taskType;
tr.endLocation = getFileSize();
tr.blockLen = RecordUtil.getBlockLen(getFileSize(), threadId, record.threadNum);
return tr;
}
@Override public TaskRecord createTaskRecord(int threadNum) {
TaskRecord record = new TaskRecord();
record.fileName = getEntity().getFileName();
record.filePath = getEntity().getFilePath();
record.threadRecords = new ArrayList<>();
record.threadNum = threadNum;
record.isBlock = false;
record.taskType = ITaskWrapper.U_SFTP;
record.isGroupRecord = getEntity().isGroupChild();
return record;
}
@Override public int initTaskThreadNum() {
return 1;
}
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.arialyy.aria.sftp.upload;
import android.os.Handler;
import com.arialyy.aria.core.FtpUrlEntity;
import com.arialyy.aria.core.TaskRecord;
import com.arialyy.aria.core.ThreadRecord;
import com.arialyy.aria.core.common.SubThreadConfig;
import com.arialyy.aria.core.loader.AbsNormalTTBuilderAdapter;
import com.arialyy.aria.core.task.IThreadTaskAdapter;
import com.arialyy.aria.core.upload.UTaskWrapper;
import com.arialyy.aria.sftp.SFtpSessionManager;
import com.arialyy.aria.sftp.SFtpTaskOption;
import com.arialyy.aria.sftp.SFtpUtil;
import com.arialyy.aria.util.CommonUtil;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import java.io.UnsupportedEncodingException;
final class SFtpUTTBuilderAdapter extends AbsNormalTTBuilderAdapter {
private SFtpTaskOption option;
SFtpUTTBuilderAdapter(UTaskWrapper wrapper) {
option = (SFtpTaskOption) wrapper.getTaskOption();
}
@Override public IThreadTaskAdapter getAdapter(SubThreadConfig config) {
return new SFtpUThreadTaskAdapter(config);
}
@Override
protected SubThreadConfig getSubThreadConfig(Handler stateHandler, ThreadRecord threadRecord,
boolean isBlock, int startNum) {
SubThreadConfig config =
super.getSubThreadConfig(stateHandler, threadRecord, isBlock, startNum);
FtpUrlEntity entity = option.getUrlEntity();
String key =
CommonUtil.getStrMd5(entity.hostName + entity.port + entity.user + threadRecord.threadId);
Session session = SFtpSessionManager.getInstance().getSession(key);
if (session == null) {
try {
session = SFtpUtil.getInstance().getSession(entity, threadRecord.threadId);
} catch (JSchException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
config.obj = session;
return config;
}
@Override public boolean handleNewTask(TaskRecord record, int totalThreadNum) {
return true;
}
}

View File

@@ -0,0 +1,201 @@
/*
* Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.arialyy.aria.sftp.upload;
import com.arialyy.aria.core.common.SubThreadConfig;
import com.arialyy.aria.core.task.AbsThreadTaskAdapter;
import com.arialyy.aria.core.upload.UploadEntity;
import com.arialyy.aria.exception.AriaException;
import com.arialyy.aria.sftp.SFtpTaskOption;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.BufferedRandomAccessFile;
import com.arialyy.aria.util.CommonUtil;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.SftpException;
import com.jcraft.jsch.SftpProgressMonitor;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
/**
* sftp 线程任务适配器
*
* @author lyy
*/
final class SFtpUThreadTaskAdapter extends AbsThreadTaskAdapter {
private ChannelSftp channelSftp;
private Session session;
private SFtpTaskOption option;
SFtpUThreadTaskAdapter(SubThreadConfig config) {
super(config);
session = (Session) config.obj;
option = (SFtpTaskOption) getTaskWrapper().getTaskOption();
}
@Override protected void handlerThreadTask() {
if (session == null) {
fail(new AriaException(TAG, "session 为空"), false);
return;
}
try {
ALog.d(TAG,
String.format("任务【%s】线程__%s__开始上传【开始位置 : %s结束位置%s】", getTaskWrapper().getKey(),
getThreadRecord().threadId, getThreadRecord().startLocation,
getThreadRecord().endLocation));
int timeout = getTaskConfig().getConnectTimeOut();
if (!session.isConnected()) {
session.connect(timeout);
}
// 开启服务器对UTF-8的支持如果服务器支持就用UTF-8编码
String charSet = option.getCharSet();
String remotePath =
CommonUtil.convertSFtpChar(charSet, option.getUrlEntity().remotePath);
channelSftp = (ChannelSftp) session.openChannel("sftp");
channelSftp.connect(timeout);
if (!dirIsExist(remotePath)) {
createDir(remotePath);
}
channelSftp.cd(remotePath);
upload(remotePath);
} catch (JSchException e) {
e.printStackTrace();
fail(null, false);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
fail(null, false);
} catch (IOException e) {
e.printStackTrace();
fail(null, true);
} catch (SftpException e) {
e.printStackTrace();
fail(null, false);
ALog.d(TAG, "错误类型:" + e.id);
} finally {
channelSftp.disconnect();
}
}
/**
* 远程文件夹是否存在
*
* @return true 文件夹存在
*/
private boolean dirIsExist(String remotePath) {
try {
channelSftp.ls(remotePath);
} catch (SftpException e) {
return false;
}
return true;
}
/**
* 创建文件夹
*
* @throws SftpException
*/
private void createDir(String remotePath) throws SftpException {
String[] folders = remotePath.split("/");
for (String folder : folders) {
if (folder.length() > 0) {
try {
channelSftp.cd(folder);
} catch (SftpException e) {
channelSftp.mkdir(folder);
channelSftp.cd(folder);
}
}
}
}
/**
* 恢复上传
*
* @throws SftpException
* @throws IOException
*/
private void upload(String remotePath) throws SftpException, IOException {
UploadEntity entity = (UploadEntity) getTaskWrapper().getEntity();
remotePath = remotePath.concat("/").concat(entity.getFileName());
BufferedRandomAccessFile brf = new BufferedRandomAccessFile(getThreadConfig().tempFile, "r");
int mode = ChannelSftp.OVERWRITE;
boolean isResume = false;
if (getThreadRecord().startLocation > 0) {
brf.seek(getThreadRecord().startLocation);
mode = ChannelSftp.APPEND;
isResume = true;
}
OutputStream os = channelSftp.put(remotePath, new Monitor(isResume), mode);
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = brf.read(buffer)) != -1) {
if (getThreadTask().isBreak()) {
break;
}
os.write(buffer, 0, bytesRead);
if (mSpeedBandUtil != null) {
mSpeedBandUtil.limitNextBytes(bytesRead);
}
}
os.flush();
os.close();
brf.close();
}
private class Monitor implements SftpProgressMonitor {
private boolean isResume;
private Monitor(boolean isResume) {
this.isResume = isResume;
}
@Override public void init(int op, String src, String dest, long max) {
ALog.d(TAG, String.format("op = %s; src = %s; dest = %s; max = %s", op, src, dest, max));
}
/**
* @param count 已传输的数据
* @return false 取消任务
*/
@Override public boolean count(long count) {
/*
* jsch 如果是恢复任务第一次回调count会将已下载的长度返回后面才是新增的文件长度。
* 所以恢复任务的话,需要忽略一次回调
*/
if (!isResume) {
progress(count);
}
isResume = false;
return !getThreadTask().isBreak();
}
@Override public void end() {
if (getThreadTask().isBreak()) {
return;
}
complete();
}
}
}