From fa936f47424836d385cf0f6a3f963c166c9821eb Mon Sep 17 00:00:00 2001 From: Looly Date: Mon, 11 May 2020 23:18:02 +0800 Subject: [PATCH] add recursiveDownloadFolder --- CHANGELOG.md | 4 +- .../cn/hutool/core/collection/CollUtil.java | 26 +++- .../main/java/cn/hutool/core/io/FileUtil.java | 2 +- .../java/cn/hutool/extra/ftp/AbstractFtp.java | 54 +++++---- .../main/java/cn/hutool/extra/ftp/Ftp.java | 67 ++++++++--- .../main/java/cn/hutool/extra/ssh/Sftp.java | 113 ++++++++++++------ .../java/cn/hutool/extra/ftp/FtpTest.java | 19 ++- 7 files changed, 186 insertions(+), 99 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eda3d77df..633b3c474 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,9 +3,11 @@ ------------------------------------------------------------------------------------------------------------- -## 5.3.5 (2020-05-10) +## 5.3.5 (2020-05-11) ### 新特性 +* 【core 】 增加CollUtil.map方法 +* 【extra 】 增加Sftp.lsEntries方法,Ftp和Sftp增加recursiveDownloadFolder(pr#121@Gitee) ### Bug修复 ------------------------------------------------------------------------------------------------------------- diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java index 3efe98bfa..a4ccaf888 100644 --- a/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java @@ -49,6 +49,7 @@ import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.LinkedBlockingDeque; +import java.util.function.Function; /** * 集合相关工具类 @@ -1170,16 +1171,31 @@ public class CollUtil { * @param ignoreNull 是否忽略空值 * @return 抽取后的新列表 * @since 4.5.7 + * @see #map(Iterable, Function, boolean) */ public static List extract(Iterable collection, Editor editor, boolean ignoreNull) { - final List fieldValueList = new ArrayList<>(); + return map(collection, editor::edit, ignoreNull); + } + + /** + * 通过func自定义一个规则,此规则将原集合中的元素转换成新的元素,生成新的列表返回
+ * 例如提供的是一个Bean列表,通过Function接口实现获取某个字段值,返回这个字段值组成的新列表 + * + * @param collection 原集合 + * @param func 编辑函数 + * @param ignoreNull 是否忽略空值 + * @return 抽取后的新列表 + * @since 5.3.5 + */ + public static List map(Iterable collection, Function func, boolean ignoreNull) { + final List fieldValueList = new ArrayList<>(); if (null == collection) { return fieldValueList; } - Object value; - for (Object bean : collection) { - value = editor.edit(bean); + R value; + for (T bean : collection) { + value = func.apply(bean); if (null == value && ignoreNull) { continue; } @@ -1212,7 +1228,7 @@ public class CollUtil { * @since 4.5.7 */ public static List getFieldValues(Iterable collection, final String fieldName, boolean ignoreNull) { - return extract(collection, bean -> { + return map(collection, bean -> { if (bean instanceof Map) { return ((Map) bean).get(fieldName); } else { diff --git a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java index 05da50528..2b30df3a2 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java @@ -567,7 +567,7 @@ public class FileUtil { * @return 最后修改时间 */ public static Date lastModifiedTime(File file) { - if (!exist(file)) { + if (false == exist(file)) { return null; } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ftp/AbstractFtp.java b/hutool-extra/src/main/java/cn/hutool/extra/ftp/AbstractFtp.java index 3e21149a4..2b45d1ed9 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ftp/AbstractFtp.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ftp/AbstractFtp.java @@ -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 是否包含 */ diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java index ed7b7841e..7f64b16b0 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java @@ -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; } + /** + * 遍历某个目录下所有文件和目录,不会递归遍历
+ * 此方法自动过滤"."和".."两种目录 + * + * @param path 目录 + * @param filter 过滤器,null表示不过滤,默认去掉"."和".."两种目录 + * @return 文件或目录列表 + * @since 5.3.5 + */ + public List lsFiles(String path, Filter filter) { + final FTPFile[] ftpFiles = lsFiles(path); + if (ArrayUtil.isEmpty(ftpFiles)) { + return ListUtil.empty(); + } + + final List 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); } } } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ssh/Sftp.java b/hutool-extra/src/main/java/cn/hutool/extra/ssh/Sftp.java index 5ed1f8f63..6ca943627 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ssh/Sftp.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ssh/Sftp.java @@ -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 { } /** - * 遍历某个目录下所有文件或目录,不会递归遍历 + * 遍历某个目录下所有文件或目录,不会递归遍历
+ * 此方法自动过滤"."和".."两种目录 * - * @param path 遍历某个目录下所有文件或目录 + * @param path 遍历某个目录下所有文件或目录 * @param filter 文件或目录过滤器,可以实现过滤器返回自己需要的文件或目录名列表 * @return 目录或文件名列表 * @since 4.0.5 */ public List ls(String path, final Filter filter) { - final List fileNames = new ArrayList<>(); + final List entries = lsEntries(path, filter); + if (CollUtil.isEmpty(entries)) { + return ListUtil.empty(); + } + return CollUtil.map(entries, LsEntry::getFilename, true); + } + + /** + * 遍历某个目录下所有文件或目录,生成LsEntry列表,不会递归遍历
+ * 此方法自动过滤"."和".."两种目录 + * + * @param path 遍历某个目录下所有文件或目录 + * @return 目录或文件名列表 + * @since 5.3.5 + */ + public List lsEntries(String path) { + return lsEntries(path, null); + } + + /** + * 遍历某个目录下所有文件或目录,生成LsEntry列表,不会递归遍历
+ * 此方法自动过滤"."和".."两种目录 + * + * @param path 遍历某个目录下所有文件或目录 + * @param filter 文件或目录过滤器,可以实现过滤器返回自己需要的文件或目录名列表 + * @return 目录或文件名列表 + * @since 5.3.5 + */ + public List lsEntries(String path, Filter filter) { + final List 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 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 } } diff --git a/hutool-extra/src/test/java/cn/hutool/extra/ftp/FtpTest.java b/hutool-extra/src/test/java/cn/hutool/extra/ftp/FtpTest.java index f2609ebe3..34a1e19d3 100644 --- a/hutool-extra/src/test/java/cn/hutool/extra/ftp/FtpTest.java +++ b/hutool-extra/src/test/java/cn/hutool/extra/ftp/FtpTest.java @@ -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); }