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

@ -3,9 +3,11 @@
------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------
## 5.3.5 (2020-05-10) ## 5.3.5 (2020-05-11)
### 新特性 ### 新特性
* 【core 】 增加CollUtil.map方法
* 【extra 】 增加Sftp.lsEntries方法Ftp和Sftp增加recursiveDownloadFolderpr#121@Gitee
### Bug修复 ### Bug修复
------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------

View File

@ -49,6 +49,7 @@ import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue; import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.LinkedBlockingDeque;
import java.util.function.Function;
/** /**
* 集合相关工具类 * 集合相关工具类
@ -1170,16 +1171,31 @@ public class CollUtil {
* @param ignoreNull 是否忽略空值 * @param ignoreNull 是否忽略空值
* @return 抽取后的新列表 * @return 抽取后的新列表
* @since 4.5.7 * @since 4.5.7
* @see #map(Iterable, Function, boolean)
*/ */
public static List<Object> extract(Iterable<?> collection, Editor<Object> editor, boolean ignoreNull) { public static List<Object> extract(Iterable<?> collection, Editor<Object> editor, boolean ignoreNull) {
final List<Object> fieldValueList = new ArrayList<>(); return map(collection, editor::edit, ignoreNull);
}
/**
* 通过func自定义一个规则此规则将原集合中的元素转换成新的元素生成新的列表返回<br>
* 例如提供的是一个Bean列表通过Function接口实现获取某个字段值返回这个字段值组成的新列表
*
* @param collection 原集合
* @param func 编辑函数
* @param ignoreNull 是否忽略空值
* @return 抽取后的新列表
* @since 5.3.5
*/
public static <T, R> List<R> map(Iterable<T> collection, Function<T, R> func, boolean ignoreNull) {
final List<R> fieldValueList = new ArrayList<>();
if (null == collection) { if (null == collection) {
return fieldValueList; return fieldValueList;
} }
Object value; R value;
for (Object bean : collection) { for (T bean : collection) {
value = editor.edit(bean); value = func.apply(bean);
if (null == value && ignoreNull) { if (null == value && ignoreNull) {
continue; continue;
} }
@ -1212,7 +1228,7 @@ public class CollUtil {
* @since 4.5.7 * @since 4.5.7
*/ */
public static List<Object> getFieldValues(Iterable<?> collection, final String fieldName, boolean ignoreNull) { public static List<Object> getFieldValues(Iterable<?> collection, final String fieldName, boolean ignoreNull) {
return extract(collection, bean -> { return map(collection, bean -> {
if (bean instanceof Map) { if (bean instanceof Map) {
return ((Map<?, ?>) bean).get(fieldName); return ((Map<?, ?>) bean).get(fieldName);
} else { } else {

View File

@ -567,7 +567,7 @@ public class FileUtil {
* @return 最后修改时间 * @return 最后修改时间
*/ */
public static Date lastModifiedTime(File file) { public static Date lastModifiedTime(File file) {
if (!exist(file)) { if (false == exist(file)) {
return null; return null;
} }

View File

@ -18,7 +18,7 @@ import java.util.List;
*/ */
public abstract class AbstractFtp implements Closeable { 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; protected FtpConfig ftpConfig;
@ -28,15 +28,15 @@ public abstract class AbstractFtp implements Closeable {
* @param config FTP配置 * @param config FTP配置
* @since 5.3.3 * @since 5.3.3
*/ */
protected AbstractFtp(FtpConfig config){ protected AbstractFtp(FtpConfig config) {
this.ftpConfig = config; this.ftpConfig = config;
} }
/** /**
* 如果连接超时的话重新进行连接 * 如果连接超时的话重新进行连接
* @since 4.5.2
* *
* @return this * @return this
* @since 4.5.2
*/ */
public abstract AbstractFtp reconnectIfTimeout(); public abstract AbstractFtp reconnectIfTimeout();
@ -119,7 +119,7 @@ public abstract class AbstractFtp implements Closeable {
final String[] dirs = StrUtil.trim(dir).split("[\\\\/]+"); final String[] dirs = StrUtil.trim(dir).split("[\\\\/]+");
final String now = pwd(); final String now = pwd();
if(dirs.length > 0 && StrUtil.isEmpty(dirs[0])) { if (dirs.length > 0 && StrUtil.isEmpty(dirs[0])) {
//首位为空表示以/开头 //首位为空表示以/开头
this.cd(StrUtil.SLASH); this.cd(StrUtil.SLASH);
} }
@ -141,7 +141,7 @@ public abstract class AbstractFtp implements Closeable {
* 覆盖模式 * 覆盖模式
* *
* @param destPath 服务端路径可以为{@code null} 或者相对路径或绝对路径 * @param destPath 服务端路径可以为{@code null} 或者相对路径或绝对路径
* @param file 需要上传的文件 * @param file 需要上传的文件
* @return 是否成功 * @return 是否成功
*/ */
public abstract boolean upload(String destPath, File file); public abstract boolean upload(String destPath, File file);
@ -149,7 +149,7 @@ public abstract class AbstractFtp implements Closeable {
/** /**
* 下载文件 * 下载文件
* *
* @param path 文件路径 * @param path 文件路径
* @param outFile 输出文件或目录 * @param outFile 输出文件或目录
*/ */
public abstract void download(String path, File outFile); public abstract void download(String path, File outFile);
@ -157,16 +157,18 @@ public abstract class AbstractFtp implements Closeable {
/** /**
* 递归下载FTP服务器上文件到本地(文件目录和服务器同步), 服务器上有新文件会覆盖本地文件 * 递归下载FTP服务器上文件到本地(文件目录和服务器同步), 服务器上有新文件会覆盖本地文件
* *
* @param sourcePath ftp服务器目录 * @param sourcePath ftp服务器目录
* @param destinationPath 本地目录 * @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 // ---------------------------------------------------------------------------------------------------------------------------------------- Private method start
/** /**
* 是否包含指定字符串忽略大小写 * 是否包含指定字符串忽略大小写
* *
* @param names 文件或目录名列表 * @param names 文件或目录名列表
* @param nameToFind 要查找的文件或目录名 * @param nameToFind 要查找的文件或目录名
* @return 是否包含 * @return 是否包含
*/ */

View File

@ -1,7 +1,9 @@
package cn.hutool.extra.ftp; package cn.hutool.extra.ftp;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Assert; import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.Filter;
import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
@ -158,7 +160,7 @@ public class Ftp extends AbstractFtp {
try { try {
// 连接ftp服务器 // 连接ftp服务器
client.connect(config.getHost(), config.getPort()); client.connect(config.getHost(), config.getPort());
client.setSoTimeout((int)config.getSoTimeout()); client.setSoTimeout((int) config.getSoTimeout());
// 登录ftp服务器 // 登录ftp服务器
client.login(config.getUser(), config.getPassword()); client.login(config.getUser(), config.getPassword());
} catch (IOException e) { } catch (IOException e) {
@ -277,6 +279,34 @@ public class Ftp extends AbstractFtp {
return fileNames; 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服务器上文件到本地(文件目录和服务器同步) * 递归下载FTP服务器上文件到本地(文件目录和服务器同步)
* *
* @param sourcePath ftp服务器目录 * @param sourcePath ftp服务器目录
* @param destinationPath 本地目录 * @param destDir 本地目录
*/ */
@Override @Override
public void recursiveDownloadFolder(String sourcePath, String destinationPath) { public void recursiveDownloadFolder(String sourcePath, File destDir) {
String pathSeparator = "/"; String fileName;
FTPFile[] lsFiles = lsFiles(sourcePath); 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) { if (false == ftpFile.isDirectory()) {
String sourcePathPathFile = sourcePath + pathSeparator + ftpFile.getName();
String destinationPathFile = destinationPath + pathSeparator + ftpFile.getName();
if (!ftpFile.isDirectory()) {
// 本地不存在文件或者ftp上文件有修改则下载 // 本地不存在文件或者ftp上文件有修改则下载
if (!FileUtil.exist(destinationPathFile) if (false == FileUtil.exist(destFile)
|| (ftpFile.getTimestamp().getTimeInMillis() > FileUtil.lastModifiedTime(destinationPathFile).getTime())) { || (ftpFile.getTimestamp().getTimeInMillis() > destFile.lastModified())) {
// Download file from source (source filename, destination filename). download(srcFile, destFile);
download(sourcePathPathFile, FileUtil.file(destinationPathFile));
} }
} else if (!(".".equals(ftpFile.getName()) || "..".equals(ftpFile.getName()))) { } else {
FileUtil.mkdir(destinationPathFile); // 服务端依旧是目录继续递归
recursiveDownloadFolder(sourcePathPathFile, destinationPathFile); FileUtil.mkdir(destFile);
recursiveDownloadFolder(srcFile, destFile);
} }
} }
} }

View File

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

View File

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