浏览代码

fix copy #I2CKTI

Looly 5 年之前
父节点
当前提交
49fab3986f

+ 3 - 2
CHANGELOG.md

@@ -3,10 +3,11 @@
 
 -------------------------------------------------------------------------------------------------------------
 
-# 5.5.8 (2021-01-08)
+# 5.5.8 (2021-01-09)
 
 ### 新特性
 ### Bug修复
+* 【core   】     修复FileUtil.move以及PathUtil.copy等无法自动创建父目录的问题(issue#I2CKTI@Gitee)
 
 -------------------------------------------------------------------------------------------------------------
 
@@ -29,7 +30,7 @@
 * 【core   】     修复CsvReader读取双引号未转义问题(issue#I2BMP1@Gitee)
 * 【json   】     JSONUtil.parse修复config无效问题(issue#1363@Github)
 * 【http   】     修复SimpleServer返回响应内容Content-Length不正确的问题(issue#1358@Github)
-* 【http   】     修复Https请求部分环境下报证书验证异常问题(issue#I2C1BZ@Github
+* 【http   】     修复Https请求部分环境下报证书验证异常问题(issue#I2C1BZ@Gitee
 
 -------------------------------------------------------------------------------------------------------------
 

+ 10 - 5
hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java

@@ -638,12 +638,10 @@ public class FileUtil extends PathUtil {
 	 * @return 父目录
 	 */
 	public static File mkParentDirs(File file) {
-		final File parentFile = file.getParentFile();
-		if (null != parentFile && false == parentFile.exists()) {
-			//noinspection ResultOfMethodCallIgnored
-			parentFile.mkdirs();
+		if(null == file){
+			return null;
 		}
-		return parentFile;
+		return mkdir(file.getParentFile());
 	}
 
 	/**
@@ -1022,6 +1020,7 @@ public class FileUtil extends PathUtil {
 	 * @param isOverride  是否覆盖目标文件
 	 * @return 目标文件
 	 * @since 3.0.9
+	 * @see PathUtil#rename(Path, String, boolean)
 	 */
 	public static File rename(File file, String newName, boolean isRetainExt, boolean isOverride) {
 		if (isRetainExt) {
@@ -3192,6 +3191,12 @@ public class FileUtil extends PathUtil {
 				contentType = "application/x-javascript";
 			}
 		}
+
+		// 补充
+		if(null == contentType){
+			contentType = getMimeType(Paths.get(filePath));
+		}
+
 		return contentType;
 	}
 

+ 55 - 9
hutool-core/src/main/java/cn/hutool/core/io/file/PathUtil.java

@@ -153,7 +153,8 @@ public class PathUtil {
 	}
 
 	/**
-	 * 通过JDK7+的 {@link Files#copy(Path, Path, CopyOption...)} 方法拷贝文件
+	 * 通过JDK7+的 {@link Files#copy(Path, Path, CopyOption...)} 方法拷贝文件<br>
+	 * 此方法不支持递归拷贝目录,如果src传入是目录,只会在目标目录中创建空目录
 	 *
 	 * @param src     源文件路径,如果为目录只在目标中创建新目录
 	 * @param dest    目标文件或目录,如果为目录使用与源文件相同的文件名
@@ -166,7 +167,8 @@ public class PathUtil {
 	}
 
 	/**
-	 * 通过JDK7+的 {@link Files#copy(Path, Path, CopyOption...)} 方法拷贝文件
+	 * 通过JDK7+的 {@link Files#copy(Path, Path, CopyOption...)} 方法拷贝文件<br>
+	 * 此方法不支持递归拷贝目录,如果src传入是目录,只会在目标目录中创建空目录
 	 *
 	 * @param src     源文件路径,如果为目录只在目标中创建新目录
 	 * @param target  目标文件或目录,如果为目录使用与源文件相同的文件名
@@ -180,6 +182,8 @@ public class PathUtil {
 		Assert.notNull(target, "Destination File or directiory is null !");
 
 		final Path targetPath = isDirectory(target) ? target.resolve(src.getFileName()) : target;
+		// 创建级联父目录
+		mkParentDirs(targetPath);
 		try {
 			return Files.copy(src, targetPath, options);
 		} catch (IOException e) {
@@ -188,9 +192,15 @@ public class PathUtil {
 	}
 
 	/**
-	 * 拷贝文件或目录
+	 * 拷贝文件或目录,拷贝规则为:
 	 *
-	 * @param src     源文件路径,如果为目录只在目标中创建新目录
+	 * <ul>
+	 *     <li>源文件为目录,目标也为目录或不存在,则拷贝整个目录到目标目录下</li>
+	 *     <li>源文件为文件,目标为目录或不存在,则拷贝文件到目标目录下</li>
+	 *     <li>源文件为文件,目标也为文件,则在{@link StandardCopyOption#REPLACE_EXISTING}情况下覆盖之</li>
+	 * </ul>
+	 *
+	 * @param src     源文件路径,如果为目录会在目标中创建新目录
 	 * @param target  目标文件或目录,如果为目录使用与源文件相同的文件名
 	 * @param options {@link StandardCopyOption}
 	 * @return Path
@@ -198,17 +208,21 @@ public class PathUtil {
 	 * @since 5.5.1
 	 */
 	public static Path copy(Path src, Path target, CopyOption... options) throws IORuntimeException {
-		if (isFile(src, false)) {
-			return copyFile(src, target, options);
+		if (isDirectory(src)) {
+			return copyContent(src, target.resolve(src.getFileName()), options);
 		}
-		return copyContent(src, target.resolve(src.getFileName()), options);
+		return copyFile(src, target, options);
 	}
 
 	/**
-	 * 拷贝目录下的所有文件或目录到目标目录中
+	 * 拷贝目录下的所有文件或目录到目标目录中,此方法不支持文件对文件的拷贝。
+	 * <ul>
+	 *     <li>源文件为目录,目标也为目录或不存在,则拷贝目录下所有文件和目录到目标目录下</li>
+	 *     <li>源文件为文件,目标为目录或不存在,则拷贝文件到目标目录下</li>
+	 * </ul>
 	 *
 	 * @param src     源文件路径,如果为目录只在目标中创建新目录
-	 * @param target  目标文件或目录,如果为目录使用与源文件相同的文件名
+	 * @param target  目标目录,如果为目录使用与源文件相同的文件名
 	 * @param options {@link StandardCopyOption}
 	 * @return Path
 	 * @throws IORuntimeException IO异常
@@ -450,6 +464,8 @@ public class PathUtil {
 		if (isDirectory(target)) {
 			target = target.resolve(src.getFileName());
 		}
+		// 自动创建目标的父目录
+		mkParentDirs(target);
 		try {
 			return Files.move(src, target, options);
 		} catch (IOException e) {
@@ -546,6 +562,7 @@ public class PathUtil {
 	 * @param file 文件
 	 * @return MimeType
 	 * @since 5.5.5
+	 * @see Files#probeContentType(Path)
 	 */
 	public static String getMimeType(Path file) {
 		try {
@@ -554,4 +571,33 @@ public class PathUtil {
 			throw new IORuntimeException(e);
 		}
 	}
+
+	/**
+	 * 创建所给目录及其父目录
+	 *
+	 * @param dir 目录
+	 * @return 目录
+	 * @since 5.5.7
+	 */
+	public static Path mkdir(Path dir) {
+		if (null != dir && false == exists(dir, false)) {
+			try {
+				Files.createDirectories(dir);
+			} catch (IOException e) {
+				throw new IORuntimeException(e);
+			}
+		}
+		return dir;
+	}
+
+	/**
+	 * 创建所给文件或目录的父目录
+	 *
+	 * @param path 文件或目录
+	 * @return 父目录
+	 * @since 5.5.7
+	 */
+	public static Path mkParentDirs(Path path) {
+		return mkdir(path.getParent());
+	}
 }

+ 30 - 4
hutool-core/src/main/java/cn/hutool/core/io/file/visitor/CopyVisitor.java

@@ -1,5 +1,7 @@
 package cn.hutool.core.io.file.visitor;
 
+import cn.hutool.core.io.file.PathUtil;
+
 import java.io.IOException;
 import java.nio.file.FileAlreadyExistsException;
 import java.nio.file.FileVisitResult;
@@ -9,17 +11,28 @@ import java.nio.file.SimpleFileVisitor;
 import java.nio.file.attribute.BasicFileAttributes;
 
 /**
- * 文件拷贝的FileVisitor实现,用于递归遍历拷贝目录
+ * 文件拷贝的FileVisitor实现,用于递归遍历拷贝目录,此类非线程安全<br>
+ * 此类在遍历源目录并复制过程中会自动创建目标目录中不存在的上级目录。
  *
  * @author looly
  * @since 5.5.1
  */
 public class CopyVisitor extends SimpleFileVisitor<Path> {
 
-	final Path source;
-	final Path target;
+	private final Path source;
+	private final Path target;
+	private boolean isTargetCreated;
 
+	/**
+	 * 构造
+	 *
+	 * @param source 源Path
+	 * @param target 目标Path
+	 */
 	public CopyVisitor(Path source, Path target) {
+		if(PathUtil.exists(target, false) && false == PathUtil.isDirectory(target)){
+			throw new IllegalArgumentException("Target must be a directory");
+		}
 		this.source = source;
 		this.target = target;
 	}
@@ -27,11 +40,13 @@ public class CopyVisitor extends SimpleFileVisitor<Path> {
 	@Override
 	public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
 			throws IOException {
+		initTarget();
+		// 将当前目录相对于源路径转换为相对于目标路径
 		final Path targetDir = target.resolve(source.relativize(dir));
 		try {
 			Files.copy(dir, targetDir);
 		} catch (FileAlreadyExistsException e) {
-			if (!Files.isDirectory(targetDir))
+			if (false == Files.isDirectory(targetDir))
 				throw e;
 		}
 		return FileVisitResult.CONTINUE;
@@ -40,7 +55,18 @@ public class CopyVisitor extends SimpleFileVisitor<Path> {
 	@Override
 	public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
 			throws IOException {
+		initTarget();
 		Files.copy(file, target.resolve(source.relativize(file)));
 		return FileVisitResult.CONTINUE;
 	}
+
+	/**
+	 * 初始化目标文件或目录
+	 */
+	private void initTarget(){
+		if(false == this.isTargetCreated){
+			PathUtil.mkdir(this.target);
+			this.isTargetCreated = true;
+		}
+	}
 }

+ 14 - 2
hutool-core/src/test/java/cn/hutool/core/io/file/PathUtilTest.java

@@ -25,7 +25,16 @@ public class PathUtilTest {
 	public void copyTest(){
 		PathUtil.copy(
 				Paths.get("d:/Red2_LYY"),
-				Paths.get("d:/test/")
+				Paths.get("d:/test/aaa/aaa.txt")
+		);
+	}
+
+	@Test
+	@Ignore
+	public void copyContentTest(){
+		PathUtil.copyContent(
+				Paths.get("d:/Red2_LYY"),
+				Paths.get("d:/test/aaa/")
 		);
 	}
 
@@ -37,7 +46,10 @@ public class PathUtilTest {
 
 	@Test
 	public void getMimeTypeTest(){
-		final String mimeType = PathUtil.getMimeType(Paths.get("d:/test/test.jpg"));
+		String mimeType = PathUtil.getMimeType(Paths.get("d:/test/test.jpg"));
 		Assert.assertEquals("image/jpeg", mimeType);
+
+		mimeType = PathUtil.getMimeType(Paths.get("d:/test/test.mov"));
+		Assert.assertEquals("video/quicktime", mimeType);
 	}
 }