源代码备份
This commit is contained in:
1
SFtpComponent/.gitignore
vendored
Normal file
1
SFtpComponent/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/build
|
11
SFtpComponent/bintray-release.gradle
Normal file
11
SFtpComponent/bintray-release.gradle
Normal file
@ -0,0 +1,11 @@
|
||||
apply plugin: 'com.novoda.bintray-release'
|
||||
publish {
|
||||
artifactId = 'sftpComponent'
|
||||
uploadName = 'SFTPComponent'
|
||||
userOrg = rootProject.ext.userOrg
|
||||
groupId = rootProject.ext.groupId
|
||||
publishVersion = rootProject.ext.publishVersion
|
||||
desc = rootProject.ext.desc
|
||||
website = rootProject.ext.website
|
||||
licences = rootProject.ext.licences
|
||||
}
|
44
SFtpComponent/build.gradle
Normal file
44
SFtpComponent/build.gradle
Normal file
@ -0,0 +1,44 @@
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
buildToolsVersion rootProject.ext.buildToolsVersion
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode rootProject.ext.versionCode
|
||||
versionName rootProject.ext.versionName
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
consumerProguardFiles 'consumer-rules.pro'
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug{
|
||||
debuggable true
|
||||
}
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
|
||||
api "com.jcraft:jsch:0.1.55"
|
||||
api "com.jcraft:jzlib:1.1.3"
|
||||
implementation project(path: ':PublicComponent')
|
||||
}
|
||||
|
||||
//apply from: 'bintray-release.gradle'
|
||||
ext{
|
||||
PUBLISH_ARTIFACT_ID = 'sftp'
|
||||
}
|
||||
apply from: '../gradle/mavenCentral-release.gradle'
|
0
SFtpComponent/consumer-rules.pro
Normal file
0
SFtpComponent/consumer-rules.pro
Normal file
21
SFtpComponent/proguard-rules.pro
vendored
Normal file
21
SFtpComponent/proguard-rules.pro
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
2
SFtpComponent/src/main/AndroidManifest.xml
Normal file
2
SFtpComponent/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,2 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.arialyy.aria.sftp" />
|
@ -0,0 +1,108 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import com.arialyy.aria.core.FtpUrlEntity;
|
||||
import com.arialyy.aria.core.common.CompleteInfo;
|
||||
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.AriaException;
|
||||
import com.arialyy.aria.exception.AriaSFTPException;
|
||||
import com.arialyy.aria.util.CommonUtil;
|
||||
import com.jcraft.jsch.JSchException;
|
||||
import com.jcraft.jsch.Session;
|
||||
import com.jcraft.jsch.SftpException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
/**
|
||||
* 进行登录,获取session,获取文件信息
|
||||
*/
|
||||
public abstract class AbsSFtpInfoTask<WP extends AbsTaskWrapper> implements IInfoTask {
|
||||
protected String TAG = CommonUtil.getClassName(this);
|
||||
private Callback callback;
|
||||
private WP wrapper;
|
||||
private SFtpTaskOption option;
|
||||
private boolean isStop = false, isCancel = false;
|
||||
|
||||
public AbsSFtpInfoTask(WP wp) {
|
||||
this.wrapper = wp;
|
||||
this.option = (SFtpTaskOption) wrapper.getTaskOption();
|
||||
}
|
||||
|
||||
protected abstract void getFileInfo(Session session)
|
||||
throws JSchException, UnsupportedEncodingException, SftpException;
|
||||
|
||||
@Override public void stop() {
|
||||
isStop = true;
|
||||
}
|
||||
|
||||
@Override public void cancel() {
|
||||
isCancel = true;
|
||||
}
|
||||
|
||||
protected void handleFail(AriaException e, boolean needRetry) {
|
||||
if (isStop || isCancel){
|
||||
return;
|
||||
}
|
||||
callback.onFail(getWrapper().getEntity(), e, needRetry);
|
||||
}
|
||||
|
||||
protected void onSucceed(CompleteInfo info){
|
||||
if (isStop || isCancel){
|
||||
return;
|
||||
}
|
||||
callback.onSucceed(getWrapper().getKey(), info);
|
||||
}
|
||||
|
||||
@Override public void run() {
|
||||
try {
|
||||
FtpUrlEntity entity = option.getUrlEntity();
|
||||
String key = CommonUtil.getStrMd5(entity.hostName + entity.port + entity.user + 0);
|
||||
Session session = SFtpSessionManager.getInstance().getSession(key);
|
||||
if (session == null) {
|
||||
session = SFtpUtil.getInstance().getSession(entity, 0);
|
||||
}
|
||||
getFileInfo(session);
|
||||
} catch (JSchException e) {
|
||||
fail(new AriaSFTPException("jsch错误", e), false);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
fail(new AriaSFTPException("字符编码错误", e), false);
|
||||
} catch (SftpException e) {
|
||||
fail(new AriaSFTPException("sftp错误,错误类型:" + e.id, e), false);
|
||||
}
|
||||
}
|
||||
|
||||
protected SFtpTaskOption getOption() {
|
||||
return option;
|
||||
}
|
||||
|
||||
protected WP getWrapper() {
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
protected void fail(AriaException e, boolean needRetry) {
|
||||
callback.onFail(getWrapper().getEntity(), e, needRetry);
|
||||
}
|
||||
|
||||
@Override public void setCallback(Callback callback) {
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
@Override public void accept(ILoaderVisitor visitor) {
|
||||
visitor.addComponent(this);
|
||||
}
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import com.arialyy.aria.util.ALog;
|
||||
import com.arialyy.aria.util.CommonUtil;
|
||||
import com.jcraft.jsch.Session;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* SFTP session 管理器
|
||||
* 1、管理session
|
||||
* 2、定时清除无效的session
|
||||
*/
|
||||
public class SFtpSessionManager {
|
||||
private String TAG = CommonUtil.getClassName(this);
|
||||
private static volatile SFtpSessionManager INSTANCE = null;
|
||||
private Map<String, Session> sessionDeque = new HashMap<>();
|
||||
|
||||
public synchronized static SFtpSessionManager getInstance() {
|
||||
if (INSTANCE == null) {
|
||||
synchronized (SFtpSessionManager.class) {
|
||||
INSTANCE = new SFtpSessionManager();
|
||||
}
|
||||
}
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
private SFtpSessionManager() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取session,获取完成session后,检查map中的所有session,移除所有失效的session
|
||||
*
|
||||
* @param key md5(host + port + userName + threadId)
|
||||
* @return 如果session不可用,返回null
|
||||
*/
|
||||
public Session getSession(String key) {
|
||||
if (TextUtils.isEmpty(key)) {
|
||||
ALog.e(TAG, "从缓存获取session失败,key为空");
|
||||
return null;
|
||||
}
|
||||
Session session = sessionDeque.get(key);
|
||||
if (session == null) {
|
||||
ALog.w(TAG, "从缓存获取session失败,key:" + key);
|
||||
}
|
||||
cleanIdleSession();
|
||||
return 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() + threadId);
|
||||
sessionDeque.put(key, session);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除所有闲置的session,闲置条件:
|
||||
* 1、session 为空
|
||||
* 2、session未连接
|
||||
*/
|
||||
private void cleanIdleSession() {
|
||||
if (sessionDeque.size() > 0) {
|
||||
Iterator<Map.Entry<String, Session>> i = sessionDeque.entrySet().iterator();
|
||||
while (i.hasNext()) {
|
||||
Map.Entry<String, Session> entry = i.next();
|
||||
Session session = entry.getValue();
|
||||
if (session == null || !session.isConnected()) {
|
||||
i.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import com.arialyy.aria.core.FtpUrlEntity;
|
||||
import com.arialyy.aria.core.inf.ITaskOption;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
174
SFtpComponent/src/main/java/com/arialyy/aria/sftp/SFtpUtil.java
Normal file
174
SFtpComponent/src/main/java/com/arialyy/aria/sftp/SFtpUtil.java
Normal file
@ -0,0 +1,174 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import com.arialyy.aria.core.FtpUrlEntity;
|
||||
import com.arialyy.aria.core.IdEntity;
|
||||
import com.arialyy.aria.util.CommonUtil;
|
||||
import com.arialyy.aria.util.FileUtil;
|
||||
import com.jcraft.jsch.JSch;
|
||||
import com.jcraft.jsch.JSchException;
|
||||
import com.jcraft.jsch.Session;
|
||||
import com.jcraft.jsch.UserInfo;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* sftp工具类
|
||||
*
|
||||
* @author lyy
|
||||
*/
|
||||
public class SFtpUtil {
|
||||
private final String TAG = CommonUtil.getClassName(getClass());
|
||||
|
||||
private static SFtpUtil INSTANCE;
|
||||
|
||||
private SFtpUtil() {
|
||||
|
||||
}
|
||||
|
||||
public synchronized static SFtpUtil getInstance() {
|
||||
if (INSTANCE == null) {
|
||||
synchronized (SFtpUtil.class) {
|
||||
INSTANCE = new SFtpUtil();
|
||||
}
|
||||
}
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建jsch 的session
|
||||
*
|
||||
* @param threadId 线程id,默认0
|
||||
* @throws JSchException
|
||||
* @throws UnsupportedEncodingException
|
||||
*/
|
||||
public Session getSession(FtpUrlEntity entity, int threadId) 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 : 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(5000);// 设置超时
|
||||
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();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static class JschUserInfo implements UserInfo {
|
||||
|
||||
@Override public String getPassphrase() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override public String getPassword() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override public boolean promptPassword(String message) {
|
||||
System.out.println(message);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override public boolean promptPassphrase(String message) {
|
||||
System.out.println(message);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override public boolean promptYesNo(String message) {
|
||||
System.out.println(message);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override public void showMessage(String message) {
|
||||
System.out.println(message);
|
||||
}
|
||||
}
|
||||
}
|
@ -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.download;
|
||||
|
||||
import com.arialyy.aria.core.common.CompleteInfo;
|
||||
import com.arialyy.aria.core.download.DTaskWrapper;
|
||||
import com.arialyy.aria.exception.AriaSFTPException;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 进行登录,获取session,获取文件信息
|
||||
*/
|
||||
final class SFtpDInfoTask extends AbsSFtpInfoTask<DTaskWrapper> {
|
||||
|
||||
SFtpDInfoTask(DTaskWrapper wrapper) {
|
||||
super(wrapper);
|
||||
}
|
||||
|
||||
@Override protected void getFileInfo(Session session) throws JSchException,
|
||||
UnsupportedEncodingException {
|
||||
SFtpTaskOption option = (SFtpTaskOption) getWrapper().getTaskOption();
|
||||
ChannelSftp channel = (ChannelSftp) session.openChannel("sftp");
|
||||
channel.connect(1000);
|
||||
|
||||
//channel.setFilenameEncoding(option.getCharSet());
|
||||
//channel.setFilenameEncoding("gbk");
|
||||
|
||||
String remotePath = option.getUrlEntity().remotePath;
|
||||
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;
|
||||
onSucceed(info);
|
||||
} else {
|
||||
handleFail(new AriaSFTPException(String.format("文件不存在,remotePath:%s", remotePath)), false);
|
||||
}
|
||||
channel.disconnect();
|
||||
}
|
||||
}
|
@ -0,0 +1,159 @@
|
||||
/*
|
||||
* 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 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.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;
|
||||
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.exception.AriaException;
|
||||
import com.arialyy.aria.util.FileUtil;
|
||||
import java.io.File;
|
||||
|
||||
final class SFtpDLoader extends AbsNormalLoader<DTaskWrapper> {
|
||||
|
||||
private int startThreadNum; //启动的线程数
|
||||
private boolean isComplete = false;
|
||||
private Looper looper;
|
||||
|
||||
SFtpDLoader(DTaskWrapper wrapper, IEventListener listener) {
|
||||
super(wrapper, listener);
|
||||
mTempFile = new File(getEntity().getFilePath());
|
||||
EventMsgUtil.getDefault().register(this);
|
||||
setUpdateInterval(wrapper.getConfig().getUpdateInterval());
|
||||
}
|
||||
|
||||
private DownloadEntity 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 && startThreadNum > 0) {
|
||||
threadTask.setMaxSpeed(maxSpeed / startThreadNum);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void onDestroy() {
|
||||
super.onDestroy();
|
||||
EventMsgUtil.getDefault().unRegister(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动单线程任务
|
||||
*/
|
||||
@Override
|
||||
public void handleTask(Looper looper) {
|
||||
if (isBreak() || isComplete) {
|
||||
return;
|
||||
}
|
||||
this.looper = looper;
|
||||
mInfoTask.run();
|
||||
}
|
||||
|
||||
private void startThreadTask() {
|
||||
if (isBreak()) {
|
||||
return;
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
// 处理记录、初始化状态管理器
|
||||
mRecord = mRecordHandler.getRecord(getFileSize());
|
||||
mStateManager.setLooper(mRecord, looper);
|
||||
|
||||
// 创建线程任务
|
||||
getTaskList().addAll(mTTBuilder.buildThreadTask(mRecord,
|
||||
new Handler(looper, mStateManager.getHandlerCallback())));
|
||||
startThreadNum = mTTBuilder.getCreatedThreadNum();
|
||||
|
||||
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;
|
||||
if (recordHandler.checkTaskCompleted()) {
|
||||
mRecord.deleteData();
|
||||
isComplete = true;
|
||||
getListener().onComplete();
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void addComponent(IInfoTask infoTask) {
|
||||
mInfoTask = infoTask;
|
||||
infoTask.setCallback(new IInfoTask.Callback() {
|
||||
@Override public void onSucceed(String key, CompleteInfo info) {
|
||||
startThreadTask();
|
||||
}
|
||||
|
||||
@Override public void onFail(AbsEntity entity, AriaException e, boolean needRetry) {
|
||||
getListener().onFail(needRetry, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override public void addComponent(IThreadStateManager threadState) {
|
||||
mStateManager = threadState;
|
||||
}
|
||||
|
||||
@Override public void addComponent(IThreadTaskBuilder builder) {
|
||||
mTTBuilder = builder;
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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.download.DTaskWrapper;
|
||||
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.sftp.SFtpTaskOption;
|
||||
|
||||
/**
|
||||
* sftp下载工具
|
||||
*
|
||||
* @author lyy
|
||||
*/
|
||||
public class SFtpDLoaderUtil extends AbsNormalLoaderUtil {
|
||||
|
||||
@Override public AbsNormalLoader getLoader() {
|
||||
if (mLoader == null) {
|
||||
getTaskWrapper().generateTaskOption(SFtpTaskOption.class);
|
||||
mLoader = new SFtpDLoader((DTaskWrapper) getTaskWrapper(), getListener());
|
||||
}
|
||||
return mLoader;
|
||||
}
|
||||
|
||||
@Override public LoaderStructure BuildLoaderStructure() {
|
||||
LoaderStructure structure = new LoaderStructure();
|
||||
structure.addComponent(new SFtpDRecordHandler((DTaskWrapper) getTaskWrapper()))
|
||||
.addComponent(new NormalThreadStateManager(getListener()))
|
||||
.addComponent(new SFtpDInfoTask((DTaskWrapper) getTaskWrapper()))
|
||||
.addComponent(new NormalTTBuilder(getTaskWrapper(), new SFtpDTTBuilderAdapter(
|
||||
(DTaskWrapper) getTaskWrapper())));
|
||||
structure.accept(getLoader());
|
||||
return structure;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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 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.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;
|
||||
import com.arialyy.aria.util.FileUtil;
|
||||
import com.jcraft.jsch.JSchException;
|
||||
import com.jcraft.jsch.Session;
|
||||
import java.io.File;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
final class SFtpDTTBuilderAdapter extends AbsNormalTTBuilderAdapter {
|
||||
private SFtpTaskOption option;
|
||||
|
||||
SFtpDTTBuilderAdapter(DTaskWrapper wrapper) {
|
||||
option = (SFtpTaskOption) wrapper.getTaskOption();
|
||||
}
|
||||
|
||||
@Override public IThreadTaskAdapter getAdapter(SubThreadConfig config) {
|
||||
return new SFtpDThreadTaskAdapter(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) {
|
||||
|
||||
if (!record.isBlock) {
|
||||
if (getTempFile().exists()) {
|
||||
FileUtil.deleteFile(getTempFile());
|
||||
}
|
||||
//CommonUtil.createFile(mTempFile.getPath());
|
||||
} else {
|
||||
for (int i = 0; i < totalThreadNum; i++) {
|
||||
File blockFile =
|
||||
new File(String.format(IRecordHandler.SUB_PATH, getTempFile().getPath(), i));
|
||||
if (blockFile.exists()) {
|
||||
ALog.d(TAG, String.format("分块【%s】已经存在,将删除该分块", i));
|
||||
FileUtil.deleteFile(blockFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,159 @@
|
||||
/*
|
||||
* 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.common.SubThreadConfig;
|
||||
import com.arialyy.aria.core.task.AbsThreadTaskAdapter;
|
||||
import com.arialyy.aria.exception.AriaSFTPException;
|
||||
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.SftpException;
|
||||
import com.jcraft.jsch.SftpProgressMonitor;
|
||||
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 线程任务适配器
|
||||
*
|
||||
* @author lyy
|
||||
*/
|
||||
final class SFtpDThreadTaskAdapter extends AbsThreadTaskAdapter {
|
||||
private ChannelSftp channelSftp;
|
||||
private Session session;
|
||||
private SFtpTaskOption option;
|
||||
|
||||
SFtpDThreadTaskAdapter(SubThreadConfig config) {
|
||||
super(config);
|
||||
session = (Session) config.obj;
|
||||
option = (SFtpTaskOption) getTaskWrapper().getTaskOption();
|
||||
}
|
||||
|
||||
@Override protected void handlerThreadTask() {
|
||||
if (session == null) {
|
||||
fail(new AriaSFTPException("session 为空"), false);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
int timeout = getTaskConfig().getConnectTimeOut();
|
||||
if (!session.isConnected()) {
|
||||
session.connect(timeout);
|
||||
}
|
||||
channelSftp = (ChannelSftp) session.openChannel("sftp");
|
||||
channelSftp.connect(timeout);
|
||||
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.convertSFtpChar(charSet, option.getUrlEntity().remotePath);
|
||||
download(remotePath);
|
||||
} catch (SftpException e) {
|
||||
fail(new AriaSFTPException("sftp错误,错误类型:" + e.id, e), false);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
fail(new AriaSFTPException("字符编码错误", e), false);
|
||||
} catch (IOException e) {
|
||||
fail(new AriaSFTPException("", e), true);
|
||||
} catch (JSchException e) {
|
||||
fail(new AriaSFTPException("jsch 错误", e), 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 Monitor() {
|
||||
}
|
||||
|
||||
@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 (getRangeProgress() > getThreadRecord().endLocation) {
|
||||
return false;
|
||||
}
|
||||
return !getThreadTask().isBreak();
|
||||
}
|
||||
|
||||
@Override public void end() {
|
||||
if (getThreadTask().isBreak()) {
|
||||
return;
|
||||
}
|
||||
|
||||
complete();
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
onSucceed(info);
|
||||
}
|
||||
}
|
@ -0,0 +1,158 @@
|
||||
/*
|
||||
* 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.AriaException;
|
||||
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 (isBreak()) {
|
||||
return;
|
||||
}
|
||||
|
||||
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, AriaException e, boolean needRetry) {
|
||||
getListener().onFail(needRetry, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override public void addComponent(IThreadStateManager threadState) {
|
||||
mStateManager = threadState;
|
||||
}
|
||||
|
||||
@Override public void addComponent(IThreadTaskBuilder builder) {
|
||||
mTTBuilder = builder;
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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.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.loader.UploadThreadStateManager;
|
||||
import com.arialyy.aria.core.upload.UTaskWrapper;
|
||||
import com.arialyy.aria.sftp.SFtpTaskOption;
|
||||
|
||||
/**
|
||||
* sftp下载工具
|
||||
*
|
||||
* @author lyy
|
||||
*/
|
||||
public class SFtpULoaderUtil extends AbsNormalLoaderUtil {
|
||||
|
||||
@Override public AbsNormalLoader getLoader() {
|
||||
if (mLoader == null) {
|
||||
getTaskWrapper().generateTaskOption(SFtpTaskOption.class);
|
||||
mLoader = new SFtpULoader((UTaskWrapper) getTaskWrapper(), getListener());
|
||||
}
|
||||
return mLoader;
|
||||
}
|
||||
|
||||
@Override public LoaderStructure BuildLoaderStructure() {
|
||||
LoaderStructure structure = new LoaderStructure();
|
||||
structure.addComponent(new SFtpURecordHandler((UTaskWrapper) getTaskWrapper()))
|
||||
// .addComponent(new NormalThreadStateManager(getListener()))
|
||||
.addComponent(new UploadThreadStateManager(getListener()))
|
||||
.addComponent(new SFtpUInfoTask((UTaskWrapper) getTaskWrapper()))
|
||||
.addComponent(new NormalTTBuilder(getTaskWrapper(), new SFtpUTTBuilderAdapter(
|
||||
(UTaskWrapper) getTaskWrapper())));
|
||||
structure.accept(getLoader());
|
||||
return structure;
|
||||
}
|
||||
}
|
@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.arialyy.aria.sftp.upload;
|
||||
|
||||
import com.arialyy.aria.core.TaskRecord;
|
||||
import com.arialyy.aria.core.ThreadRecord;
|
||||
import com.arialyy.aria.core.common.RecordHandler;
|
||||
import com.arialyy.aria.core.upload.UTaskWrapper;
|
||||
import com.arialyy.aria.core.upload.UploadEntity;
|
||||
import com.arialyy.aria.core.wrapper.ITaskWrapper;
|
||||
import com.arialyy.aria.util.ALog;
|
||||
import com.arialyy.aria.util.RecordUtil;
|
||||
import com.jcraft.jsch.SftpATTRS;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* 上传任务记录处理器
|
||||
*/
|
||||
final class SFtpURecordHandler extends RecordHandler {
|
||||
private SftpATTRS ftpAttrs;
|
||||
|
||||
SFtpURecordHandler(UTaskWrapper wrapper) {
|
||||
super(wrapper);
|
||||
}
|
||||
|
||||
void setFtpAttrs(SftpATTRS ftpAttrs) {
|
||||
this.ftpAttrs = ftpAttrs;
|
||||
}
|
||||
|
||||
@Override public void handlerTaskRecord(TaskRecord record) {
|
||||
if (record.threadRecords == null || record.threadRecords.isEmpty()) {
|
||||
record.threadRecords = new ArrayList<>();
|
||||
record.threadRecords.add(
|
||||
createThreadRecord(record, 0, ftpAttrs == null ? 0 : ftpAttrs.getSize(), getFileSize()));
|
||||
}
|
||||
|
||||
if (ftpAttrs != null) {
|
||||
UploadEntity entity = (UploadEntity) getWrapper().getEntity();
|
||||
//远程文件已完成
|
||||
if (ftpAttrs.getSize() == getFileSize()) {
|
||||
record.threadRecords.get(0).isComplete = true;
|
||||
ALog.d(TAG, "FTP服务器上已存在该文件【" + entity.getFileName() + "】");
|
||||
} else if (ftpAttrs.getSize() == 0) {
|
||||
getWrapper().setNewTask(true);
|
||||
ALog.d(TAG, "FTP服务器上已存在该文件【" + entity.getFileName() + "】,但文件长度为0,重新上传该文件");
|
||||
} else {
|
||||
ALog.w(TAG, "FTP服务器已存在未完成的文件【"
|
||||
+ entity.getFileName()
|
||||
+ ",size: "
|
||||
+ ftpAttrs.getSize()
|
||||
+ "】"
|
||||
+ "尝试从位置:"
|
||||
+ ftpAttrs.getSize()
|
||||
+ "开始上传");
|
||||
getWrapper().setNewTask(false);
|
||||
|
||||
// 修改记录
|
||||
ThreadRecord threadRecord = record.threadRecords.get(0);
|
||||
//修改本地保存的停止地址为服务器上对应文件的大小
|
||||
threadRecord.startLocation = ftpAttrs.getSize();
|
||||
}
|
||||
} else {
|
||||
ALog.d(TAG, "SFTP服务器上不存在该文件");
|
||||
getWrapper().setNewTask(true);
|
||||
ThreadRecord tr = record.threadRecords.get(0);
|
||||
tr.startLocation = 0;
|
||||
tr.endLocation = getFileSize();
|
||||
tr.isComplete = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThreadRecord createThreadRecord(TaskRecord record, int threadId, long startL, long endL) {
|
||||
ThreadRecord tr;
|
||||
tr = new ThreadRecord();
|
||||
tr.taskKey = record.filePath;
|
||||
tr.threadId = threadId;
|
||||
tr.startLocation = startL;
|
||||
tr.isComplete = false;
|
||||
tr.threadType = record.taskType;
|
||||
tr.endLocation = getFileSize();
|
||||
tr.blockLen = RecordUtil.getBlockLen(getFileSize(), threadId, record.threadNum);
|
||||
return tr;
|
||||
}
|
||||
|
||||
@Override public TaskRecord createTaskRecord(int threadNum) {
|
||||
TaskRecord record = new TaskRecord();
|
||||
record.fileName = getEntity().getFileName();
|
||||
record.filePath = getEntity().getFilePath();
|
||||
record.threadRecords = new ArrayList<>();
|
||||
record.threadNum = threadNum;
|
||||
record.isBlock = false;
|
||||
record.taskType = ITaskWrapper.U_SFTP;
|
||||
record.isGroupRecord = getEntity().isGroupChild();
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
@Override public int initTaskThreadNum() {
|
||||
return 1;
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.arialyy.aria.sftp.upload;
|
||||
|
||||
import android.os.Handler;
|
||||
import com.arialyy.aria.core.FtpUrlEntity;
|
||||
import com.arialyy.aria.core.TaskRecord;
|
||||
import com.arialyy.aria.core.ThreadRecord;
|
||||
import com.arialyy.aria.core.common.SubThreadConfig;
|
||||
import com.arialyy.aria.core.loader.AbsNormalTTBuilderAdapter;
|
||||
import com.arialyy.aria.core.task.IThreadTaskAdapter;
|
||||
import com.arialyy.aria.core.upload.UTaskWrapper;
|
||||
import com.arialyy.aria.sftp.SFtpSessionManager;
|
||||
import com.arialyy.aria.sftp.SFtpTaskOption;
|
||||
import com.arialyy.aria.sftp.SFtpUtil;
|
||||
import com.arialyy.aria.util.CommonUtil;
|
||||
import com.jcraft.jsch.JSchException;
|
||||
import com.jcraft.jsch.Session;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
final class SFtpUTTBuilderAdapter extends AbsNormalTTBuilderAdapter {
|
||||
private SFtpTaskOption option;
|
||||
|
||||
SFtpUTTBuilderAdapter(UTaskWrapper wrapper) {
|
||||
option = (SFtpTaskOption) wrapper.getTaskOption();
|
||||
}
|
||||
|
||||
@Override public IThreadTaskAdapter getAdapter(SubThreadConfig config) {
|
||||
return new SFtpUThreadTaskAdapter(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SubThreadConfig getSubThreadConfig(Handler stateHandler, ThreadRecord threadRecord,
|
||||
boolean isBlock, int startNum) {
|
||||
SubThreadConfig config =
|
||||
super.getSubThreadConfig(stateHandler, threadRecord, isBlock, startNum);
|
||||
|
||||
FtpUrlEntity entity = option.getUrlEntity();
|
||||
String key =
|
||||
CommonUtil.getStrMd5(entity.hostName + entity.port + entity.user + threadRecord.threadId);
|
||||
Session session = SFtpSessionManager.getInstance().getSession(key);
|
||||
if (session == null) {
|
||||
try {
|
||||
session = SFtpUtil.getInstance().getSession(entity, threadRecord.threadId);
|
||||
} catch (JSchException e) {
|
||||
e.printStackTrace();
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
config.obj = session;
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
@Override public boolean handleNewTask(TaskRecord record, int totalThreadNum) {
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,196 @@
|
||||
/*
|
||||
* 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.AriaSFTPException;
|
||||
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 AriaSFTPException("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 (SftpException e) {
|
||||
fail(new AriaSFTPException("sftp错误,错误类型:" + e.id, e), false);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
fail(new AriaSFTPException("字符编码错误", e), false);
|
||||
} catch (IOException e) {
|
||||
fail(new AriaSFTPException("", e), true);
|
||||
} catch (JSchException e) {
|
||||
fail(new AriaSFTPException("jsch 错误", e), false);
|
||||
} 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();
|
||||
}
|
||||
}
|
||||
}
|
2
SFtpComponent/src/main/resources/META-INF/MANIFEST.MF
Normal file
2
SFtpComponent/src/main/resources/META-INF/MANIFEST.MF
Normal file
@ -0,0 +1,2 @@
|
||||
Manifest-Version: 1.0
|
||||
|
@ -0,0 +1,2 @@
|
||||
com.arialyy.aria.sftp.download.SFtpDLoaderUtil
|
||||
com.arialyy.aria.sftp.upload.SFtpULoaderUtil
|
Reference in New Issue
Block a user