add recursiveDownloadFolder

This commit is contained in:
Looly
2020-05-11 23:18:02 +08:00
parent fb6d9d35fd
commit fa936f4742
7 changed files with 186 additions and 99 deletions

View File

@@ -12,13 +12,13 @@ import java.util.List;
/**
* 抽象FTP类用于定义通用的FTP方法
*
*
* @author looly
* @since 4.1.14
*/
public abstract class AbstractFtp implements Closeable {
public static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8 ;
public static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8;
protected FtpConfig ftpConfig;
@@ -28,21 +28,21 @@ public abstract class AbstractFtp implements Closeable {
* @param config FTP配置
* @since 5.3.3
*/
protected AbstractFtp(FtpConfig config){
protected AbstractFtp(FtpConfig config) {
this.ftpConfig = config;
}
/**
* 如果连接超时的话,重新进行连接
* @since 4.5.2
*
*
* @return this
* @since 4.5.2
*/
public abstract AbstractFtp reconnectIfTimeout();
/**
* 打开指定目录
*
*
* @param directory directory
* @return 是否打开目录
*/
@@ -50,7 +50,7 @@ public abstract class AbstractFtp implements Closeable {
/**
* 打开上级目录
*
*
* @return 是否打开目录
* @since 4.0.5
*/
@@ -60,14 +60,14 @@ public abstract class AbstractFtp implements Closeable {
/**
* 远程当前目录(工作目录)
*
*
* @return 远程当前目录
*/
public abstract String pwd();
/**
* 在当前远程目录(工作目录)下创建新的目录
*
*
* @param dir 目录名
* @return 是否创建成功
*/
@@ -75,7 +75,7 @@ public abstract class AbstractFtp implements Closeable {
/**
* 文件或目录是否存在
*
*
* @param path 目录
* @return 是否存在
*/
@@ -88,7 +88,7 @@ public abstract class AbstractFtp implements Closeable {
/**
* 遍历某个目录下所有文件和目录,不会递归遍历
*
*
* @param path 需要遍历的目录
* @return 文件和目录列表
*/
@@ -96,7 +96,7 @@ public abstract class AbstractFtp implements Closeable {
/**
* 删除指定目录下的指定文件
*
*
* @param path 目录路径
* @return 是否存在
*/
@@ -104,7 +104,7 @@ public abstract class AbstractFtp implements Closeable {
/**
* 删除文件夹及其文件夹下的所有文件
*
*
* @param dirPath 文件夹路径
* @return boolean 是否删除成功
*/
@@ -112,14 +112,14 @@ public abstract class AbstractFtp implements Closeable {
/**
* 创建指定文件夹及其父目录,从根目录开始创建,创建完成后回到默认的工作目录
*
*
* @param dir 文件夹路径,绝对路径
*/
public void mkDirs(String dir) {
final String[] dirs = StrUtil.trim(dir).split("[\\\\/]+");
final String now = pwd();
if(dirs.length > 0 && StrUtil.isEmpty(dirs[0])) {
if (dirs.length > 0 && StrUtil.isEmpty(dirs[0])) {
//首位为空,表示以/开头
this.cd(StrUtil.SLASH);
}
@@ -139,17 +139,17 @@ public abstract class AbstractFtp implements Closeable {
/**
* 将本地文件上传到目标服务器目标文件名为destPath若destPath为目录则目标文件名将与file文件名相同。
* 覆盖模式
*
*
* @param destPath 服务端路径,可以为{@code null} 或者相对路径或绝对路径
* @param file 需要上传的文件
* @param file 需要上传的文件
* @return 是否成功
*/
public abstract boolean upload(String destPath, File file);
/**
* 下载文件
*
* @param path 文件路径
*
* @param path 文件路径
* @param outFile 输出文件或目录
*/
public abstract void download(String path, File outFile);
@@ -157,16 +157,18 @@ public abstract class AbstractFtp implements Closeable {
/**
* 递归下载FTP服务器上文件到本地(文件目录和服务器同步), 服务器上有新文件会覆盖本地文件
*
* @param sourcePath ftp服务器目录
* @param destinationPath 本地目录
* @param sourcePath ftp服务器目录
* @param destDir 本地目录
* @since 5.3.5
*/
public abstract void recursiveDownloadFolder(String sourcePath, String destinationPath) throws Exception;
public abstract void recursiveDownloadFolder(String sourcePath, File destDir);
// ---------------------------------------------------------------------------------------------------------------------------------------- Private method start
/**
* 是否包含指定字符串,忽略大小写
*
* @param names 文件或目录名列表
*
* @param names 文件或目录名列表
* @param nameToFind 要查找的文件或目录名
* @return 是否包含
*/

View File

@@ -1,7 +1,9 @@
package cn.hutool.extra.ftp;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.Filter;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
@@ -158,7 +160,7 @@ public class Ftp extends AbstractFtp {
try {
// 连接ftp服务器
client.connect(config.getHost(), config.getPort());
client.setSoTimeout((int)config.getSoTimeout());
client.setSoTimeout((int) config.getSoTimeout());
// 登录ftp服务器
client.login(config.getUser(), config.getPassword());
} catch (IOException e) {
@@ -277,6 +279,34 @@ public class Ftp extends AbstractFtp {
return fileNames;
}
/**
* 遍历某个目录下所有文件和目录,不会递归遍历<br>
* 此方法自动过滤"."和".."两种目录
*
* @param path 目录
* @param filter 过滤器null表示不过滤默认去掉"."和".."两种目录
* @return 文件或目录列表
* @since 5.3.5
*/
public List<FTPFile> lsFiles(String path, Filter<FTPFile> filter) {
final FTPFile[] ftpFiles = lsFiles(path);
if (ArrayUtil.isEmpty(ftpFiles)) {
return ListUtil.empty();
}
final List<FTPFile> result = new ArrayList<>(ftpFiles.length - 2);
String fileName;
for (FTPFile ftpFile : ftpFiles) {
fileName = ftpFile.getName();
if (false == StrUtil.equals(".", fileName) && false == StrUtil.equals("..", fileName)) {
if (null == filter || filter.accept(ftpFile)) {
result.add(ftpFile);
}
}
}
return result;
}
/**
* 遍历某个目录下所有文件和目录,不会递归遍历
*
@@ -479,28 +509,29 @@ public class Ftp extends AbstractFtp {
/**
* 递归下载FTP服务器上文件到本地(文件目录和服务器同步)
*
* @param sourcePath ftp服务器目录
* @param destinationPath 本地目录
* @param sourcePath ftp服务器目录
* @param destDir 本地目录
*/
@Override
public void recursiveDownloadFolder(String sourcePath, String destinationPath) {
String pathSeparator = "/";
FTPFile[] lsFiles = lsFiles(sourcePath);
public void recursiveDownloadFolder(String sourcePath, File destDir) {
String fileName;
String srcFile;
File destFile;
for (FTPFile ftpFile : lsFiles(sourcePath, null)) {
fileName = ftpFile.getName();
srcFile = StrUtil.format("{}/{}", sourcePath, fileName);
destFile = FileUtil.file(destDir, fileName);
for (FTPFile ftpFile : lsFiles) {
String sourcePathPathFile = sourcePath + pathSeparator + ftpFile.getName();
String destinationPathFile = destinationPath + pathSeparator + ftpFile.getName();
if (!ftpFile.isDirectory()) {
if (false == ftpFile.isDirectory()) {
// 本地不存在文件或者ftp上文件有修改则下载
if (!FileUtil.exist(destinationPathFile)
|| (ftpFile.getTimestamp().getTimeInMillis() > FileUtil.lastModifiedTime(destinationPathFile).getTime())) {
// Download file from source (source filename, destination filename).
download(sourcePathPathFile, FileUtil.file(destinationPathFile));
if (false == FileUtil.exist(destFile)
|| (ftpFile.getTimestamp().getTimeInMillis() > destFile.lastModified())) {
download(srcFile, destFile);
}
} else if (!(".".equals(ftpFile.getName()) || "..".equals(ftpFile.getName()))) {
FileUtil.mkdir(destinationPathFile);
recursiveDownloadFolder(sourcePathPathFile, destinationPathFile);
} else {
// 服务端依旧是目录,继续递归
FileUtil.mkdir(destFile);
recursiveDownloadFolder(srcFile, destFile);
}
}
}

View File

@@ -1,5 +1,7 @@
package cn.hutool.extra.ssh;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Filter;
import cn.hutool.core.util.StrUtil;
@@ -37,6 +39,7 @@ public class Sftp extends AbstractFtp {
private ChannelSftp channel;
// ---------------------------------------------------------------------------------------- Constructor start
/**
* 构造
*
@@ -147,7 +150,7 @@ public class Sftp extends AbstractFtp {
*/
public void init(Session session, Charset charset) {
this.session = session;
init(JschUtil.openSftp(session, (int)this.ftpConfig.getConnectionTimeout()), charset);
init(JschUtil.openSftp(session, (int) this.ftpConfig.getConnectionTimeout()), charset);
}
/**
@@ -247,32 +250,62 @@ public class Sftp extends AbstractFtp {
}
/**
* 遍历某个目录下所有文件或目录,不会递归遍历
* 遍历某个目录下所有文件或目录,不会递归遍历<br>
* 此方法自动过滤"."和".."两种目录
*
* @param path 遍历某个目录下所有文件或目录
* @param path 遍历某个目录下所有文件或目录
* @param filter 文件或目录过滤器,可以实现过滤器返回自己需要的文件或目录名列表
* @return 目录或文件名列表
* @since 4.0.5
*/
public List<String> ls(String path, final Filter<LsEntry> filter) {
final List<String> fileNames = new ArrayList<>();
final List<LsEntry> entries = lsEntries(path, filter);
if (CollUtil.isEmpty(entries)) {
return ListUtil.empty();
}
return CollUtil.map(entries, LsEntry::getFilename, true);
}
/**
* 遍历某个目录下所有文件或目录生成LsEntry列表不会递归遍历<br>
* 此方法自动过滤"."和".."两种目录
*
* @param path 遍历某个目录下所有文件或目录
* @return 目录或文件名列表
* @since 5.3.5
*/
public List<LsEntry> lsEntries(String path) {
return lsEntries(path, null);
}
/**
* 遍历某个目录下所有文件或目录生成LsEntry列表不会递归遍历<br>
* 此方法自动过滤"."和".."两种目录
*
* @param path 遍历某个目录下所有文件或目录
* @param filter 文件或目录过滤器,可以实现过滤器返回自己需要的文件或目录名列表
* @return 目录或文件名列表
* @since 5.3.5
*/
public List<LsEntry> lsEntries(String path, Filter<LsEntry> filter) {
final List<LsEntry> entryList = new ArrayList<>();
try {
channel.ls(path, entry -> {
String fileName = entry.getFilename();
final String fileName = entry.getFilename();
if (false == StrUtil.equals(".", fileName) && false == StrUtil.equals("..", fileName)) {
if (null == filter || filter.accept(entry)) {
fileNames.add(entry.getFilename());
entryList.add(entry);
}
}
return LsEntrySelector.CONTINUE;
});
} catch (SftpException e) {
if(false == StrUtil.startWithIgnoreCase(e.getMessage(), "No such file")){
if (false == StrUtil.startWithIgnoreCase(e.getMessage(), "No such file")) {
throw new JschRuntimeException(e);
}
// 文件不存在忽略
}
return fileNames;
return entryList;
}
@Override
@@ -375,7 +408,7 @@ public class Sftp extends AbstractFtp {
* 将本地文件上传到目标服务器目标文件名为destPath若destPath为目录则目标文件名将与srcFilePath文件名相同。覆盖模式
*
* @param srcFilePath 本地文件路径
* @param destPath 目标路径,
* @param destPath 目标路径,
* @return this
*/
public Sftp put(String srcFilePath, String destPath) {
@@ -386,8 +419,8 @@ public class Sftp extends AbstractFtp {
* 将本地文件上传到目标服务器目标文件名为destPath若destPath为目录则目标文件名将与srcFilePath文件名相同。
*
* @param srcFilePath 本地文件路径
* @param destPath 目标路径,
* @param mode {@link Mode} 模式
* @param destPath 目标路径,
* @param mode {@link Mode} 模式
* @return this
*/
public Sftp put(String srcFilePath, String destPath, Mode mode) {
@@ -398,9 +431,9 @@ public class Sftp extends AbstractFtp {
* 将本地文件上传到目标服务器目标文件名为destPath若destPath为目录则目标文件名将与srcFilePath文件名相同。
*
* @param srcFilePath 本地文件路径
* @param destPath 目标路径,
* @param monitor 上传进度监控,通过实现此接口完成进度显示
* @param mode {@link Mode} 模式
* @param destPath 目标路径,
* @param monitor 上传进度监控,通过实现此接口完成进度显示
* @param mode {@link Mode} 模式
* @return this
* @since 4.6.5
*/
@@ -421,30 +454,29 @@ public class Sftp extends AbstractFtp {
/**
* 递归下载FTP服务器上文件到本地(文件目录和服务器同步)
*
* @param sourcePath ftp服务器目录
* @param destinationPath 本地目录
* @param sourcePath ftp服务器目录,必须为目录
* @param destDir 本地目录
*/
@Override
public void recursiveDownloadFolder(String sourcePath, String destinationPath) throws Exception {
String pathSeparator = "/";
Vector<ChannelSftp.LsEntry> fileAndFolderList = channel.ls(sourcePath);
public void recursiveDownloadFolder(String sourcePath, File destDir) throws JschRuntimeException {
String fileName;
String srcFile;
File destFile;
for (LsEntry item : lsEntries(sourcePath)) {
fileName = item.getFilename();
srcFile = StrUtil.format("{}/{}", sourcePath, fileName);
destFile = FileUtil.file(destDir, fileName);
//Iterate through list of folder content
for (ChannelSftp.LsEntry item : fileAndFolderList) {
String sourcePathPathFile = sourcePath + pathSeparator + item.getFilename();
String destinationPathFile = destinationPath + pathSeparator + item.getFilename();
if (!item.getAttrs().isDir()) {
if (false == item.getAttrs().isDir()) {
// 本地不存在文件或者ftp上文件有修改则下载
if (!FileUtil.exist(destinationPathFile)
|| (item.getAttrs().getMTime() > (FileUtil.lastModifiedTime(destinationPathFile).getTime() / 1000))) {
// Download file from source (source filename, destination filename).
channel.get(sourcePathPathFile, destinationPathFile);
if (false == FileUtil.exist(destFile)
|| (item.getAttrs().getMTime() > (destFile.lastModified() / 1000))) {
download(srcFile, destFile);
}
} else if (!(".".equals(item.getFilename()) || "..".equals(item.getFilename()))) {
FileUtil.mkdir(destinationPathFile);
recursiveDownloadFolder(sourcePathPathFile, destinationPathFile);
} else {
// 服务端依旧是目录,继续递归
FileUtil.mkdir(destFile);
recursiveDownloadFolder(srcFile, destFile);
}
}
@@ -453,7 +485,7 @@ public class Sftp extends AbstractFtp {
/**
* 获取远程文件
*
* @param src 远程文件路径
* @param src 远程文件路径
* @param dest 目标文件路径
* @return this
*/
@@ -485,14 +517,19 @@ public class Sftp extends AbstractFtp {
* JSch支持的三种文件传输模式
*
* @author looly
*
*/
public enum Mode {
/** 完全覆盖模式这是JSch的默认文件传输模式即如果目标文件已经存在传输的文件将完全覆盖目标文件产生新的文件。 */
/**
* 完全覆盖模式这是JSch的默认文件传输模式即如果目标文件已经存在传输的文件将完全覆盖目标文件产生新的文件。
*/
OVERWRITE,
/** 恢复模式,如果文件已经传输一部分,这时由于网络或其他任何原因导致文件传输中断,如果下一次传输相同的文件,则会从上一次中断的地方续传。 */
/**
* 恢复模式,如果文件已经传输一部分,这时由于网络或其他任何原因导致文件传输中断,如果下一次传输相同的文件,则会从上一次中断的地方续传。
*/
RESUME,
/** 追加模式,如果目标文件已存在,传输的文件将在目标文件后追加。 */
/**
* 追加模式,如果目标文件已存在,传输的文件将在目标文件后追加。
*/
APPEND
}
}

View File

@@ -1,14 +1,13 @@
package cn.hutool.extra.ftp;
import java.util.List;
import cn.hutool.extra.ssh.Sftp;
import org.junit.Ignore;
import org.junit.Test;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.Console;
import cn.hutool.extra.ssh.Sftp;
import org.junit.Ignore;
import org.junit.Test;
import java.util.List;
public class FtpTest {
@@ -63,21 +62,21 @@ public class FtpTest {
@Test
@Ignore
public void recursiveDownloadFolder() throws Exception {
public void recursiveDownloadFolder() {
Ftp ftp = new Ftp("looly.centos");
ftp.recursiveDownloadFolder("/","d:/test/download");
ftp.recursiveDownloadFolder("/",FileUtil.file("d:/test/download"));
IoUtil.close(ftp);
}
@Test
@Ignore
public void recursiveDownloadFolderSftp() throws Exception {
public void recursiveDownloadFolderSftp() {
Sftp ftp = new Sftp("127.0.0.1", 22, "test", "test");
ftp.cd("/file/aaa");
Console.log(ftp.pwd());
ftp.recursiveDownloadFolder("/","d:/test/download");
ftp.recursiveDownloadFolder("/",FileUtil.file("d:/test/download"));
IoUtil.close(ftp);
}