diff --git a/.gitignore b/.gitignore index 662e06ed..39b573e5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ target .sbtopts project/.sbt +*.tmp # if you are here to add your IDE's files please read this instead: # https://stackoverflow.com/questions/7335420/global-git-ignore#22885996 diff --git a/nio/src/main/scala/zio/nio/file/Files.scala b/nio/src/main/scala/zio/nio/file/Files.scala index f7eed48f..c0f650f6 100644 --- a/nio/src/main/scala/zio/nio/file/Files.scala +++ b/nio/src/main/scala/zio/nio/file/Files.scala @@ -1,6 +1,12 @@ package zio.nio.file +import zio.blocking._ +import zio.nio.charset.Charset +import zio.stream.{ ZSink, ZStream } +import zio.{ Chunk, ZIO, ZManaged } + import java.io.IOException +import java.nio.file.attribute._ import java.nio.file.{ CopyOption, DirectoryStream, @@ -11,14 +17,7 @@ import java.nio.file.{ Files => JFiles, Path => JPath } -import java.nio.file.attribute._ import java.util.function.BiPredicate - -import zio.{ Chunk, ZIO, ZManaged } -import zio.blocking._ -import zio.nio.charset.Charset -import zio.stream.ZStream - import scala.jdk.CollectionConverters._ import scala.reflect._ @@ -57,6 +56,14 @@ object Files { effectBlocking(Path.fromJava(JFiles.createTempFile(dir.javaPath, prefix.orNull, suffix, fileAttributes.toSeq: _*))) .refineToOrDie[IOException] + def createTempFileInManaged( + dir: Path, + suffix: String = ".tmp", + prefix: Option[String] = None, + fileAttributes: Iterable[FileAttribute[_]] = Nil + ): ZManaged[Blocking, IOException, Path] = + ZManaged.make(createTempFileIn(dir, suffix, prefix, fileAttributes))(release = deleteIfExists(_).ignore) + def createTempFile( suffix: String = ".tmp", prefix: Option[String], @@ -65,6 +72,13 @@ object Files { effectBlocking(Path.fromJava(JFiles.createTempFile(prefix.orNull, suffix, fileAttributes.toSeq: _*))) .refineToOrDie[IOException] + def createTempFileManaged( + suffix: String = ".tmp", + prefix: Option[String] = None, + fileAttributes: Iterable[FileAttribute[_]] = Nil + ): ZManaged[Blocking, IOException, Path] = + ZManaged.make(createTempFile(suffix, prefix, fileAttributes))(release = deleteIfExists(_).ignore) + def createTempDirectory( dir: Path, prefix: Option[String], @@ -73,6 +87,13 @@ object Files { effectBlocking(Path.fromJava(JFiles.createTempDirectory(dir.javaPath, prefix.orNull, fileAttributes.toSeq: _*))) .refineToOrDie[IOException] + def createTempDirectoryManaged( + dir: Path, + prefix: Option[String], + fileAttributes: Iterable[FileAttribute[_]] + ): ZManaged[Blocking, IOException, Path] = + ZManaged.make(createTempDirectory(dir, prefix, fileAttributes))(release = deleteRecursive(_).ignore) + def createTempDirectory( prefix: Option[String], fileAttributes: Iterable[FileAttribute[_]] @@ -80,6 +101,12 @@ object Files { effectBlocking(Path.fromJava(JFiles.createTempDirectory(prefix.orNull, fileAttributes.toSeq: _*))) .refineToOrDie[IOException] + def createTempDirectoryManaged( + prefix: Option[String], + fileAttributes: Iterable[FileAttribute[_]] + ): ZManaged[Blocking, IOException, Path] = + ZManaged.make(createTempDirectory(prefix, fileAttributes))(release = deleteRecursive(_).ignore) + def createSymbolicLink( link: Path, target: Path, @@ -97,6 +124,9 @@ object Files { def deleteIfExists(path: Path): ZIO[Blocking, IOException, Boolean] = effectBlocking(JFiles.deleteIfExists(path.javaPath)).refineToOrDie[IOException] + def deleteRecursive(path: Path): ZIO[Blocking, IOException, Long] = + newDirectoryStream(path).mapM(delete).run(ZSink.count) <* delete(path) + def copy(source: Path, target: Path, copyOptions: CopyOption*): ZIO[Blocking, IOException, Unit] = effectBlocking(JFiles.copy(source.javaPath, target.javaPath, copyOptions: _*)).unit .refineToOrDie[IOException] diff --git a/nio/src/test/scala/zio/nio/file/FilesSpec.scala b/nio/src/test/scala/zio/nio/file/FilesSpec.scala new file mode 100644 index 00000000..7ed5b382 --- /dev/null +++ b/nio/src/test/scala/zio/nio/file/FilesSpec.scala @@ -0,0 +1,85 @@ +package zio.nio.file + +import zio.{ Chunk, Ref } +import zio.nio.BaseSpec +import zio.nio.core.file.Path +import zio.test._ +import zio.test.Assertion._ + +object FilesSpec extends BaseSpec { + + override def spec = + suite("FilesSpec")( + testM("createTempFileInManaged cleans up temp file") { + val sampleFileContent = Chunk.fromArray("createTempFileInManaged works!".getBytes) + for { + pathRef <- Ref.make[Option[Path]](None) + readBytes <- Files + .createTempFileInManaged(dir = Path(".")) + .use { tmpFile => + pathRef.set(Some(tmpFile)) *> writeAndThenRead(tmpFile)(sampleFileContent) + } + Some(tmpFilePath) <- pathRef.get + tmpFileExistsAfterUsage <- Files.exists(tmpFilePath) + } yield assert(readBytes)(equalTo(sampleFileContent)) && + assert(tmpFileExistsAfterUsage)(isFalse) + }, + testM("createTempFileManaged cleans up temp file") { + val sampleFileContent = Chunk.fromArray("createTempFileManaged works!".getBytes) + for { + pathRef <- Ref.make[Option[Path]](None) + readBytes <- Files + .createTempFileManaged() + .use { tmpFile => + pathRef.set(Some(tmpFile)) *> writeAndThenRead(tmpFile)(sampleFileContent) + } + Some(tmpFilePath) <- pathRef.get + tmpFileExistsAfterUsage <- Files.exists(tmpFilePath) + } yield assert(readBytes)(equalTo(sampleFileContent)) && + assert(tmpFileExistsAfterUsage)(isFalse) + }, + testM("createTempDirectoryManaged cleans up temp dir") { + val sampleFileContent = Chunk.fromArray("createTempDirectoryManaged works!".getBytes) + for { + pathRef <- Ref.make[Option[Path]](None) + readBytes <- Files + .createTempDirectoryManaged( + prefix = None, + fileAttributes = Nil + ) + .use { tmpDir => + val sampleFile = tmpDir / "createTempDirectoryManaged" + pathRef.set(Some(tmpDir)) *> createAndWriteAndThenRead(sampleFile)(sampleFileContent) + } + Some(tmpFilePath) <- pathRef.get + tmpFileExistsAfterUsage <- Files.exists(tmpFilePath) + } yield assert(readBytes)(equalTo(sampleFileContent)) && + assert(tmpFileExistsAfterUsage)(isFalse) + }, + testM("createTempDirectoryManaged (dir) cleans up temp dir") { + val sampleFileContent = Chunk.fromArray("createTempDirectoryManaged(dir) works!".getBytes) + for { + pathRef <- Ref.make[Option[Path]](None) + readBytes <- Files + .createTempDirectoryManaged( + dir = Path("."), + prefix = None, + fileAttributes = Nil + ) + .use { tmpDir => + val sampleFile = tmpDir / "createTempDirectoryManaged2" + pathRef.set(Some(tmpDir)) *> createAndWriteAndThenRead(sampleFile)(sampleFileContent) + } + Some(tmpFilePath) <- pathRef.get + tmpFileExistsAfterUsage <- Files.exists(tmpFilePath) + } yield assert(readBytes)(equalTo(sampleFileContent)) && + assert(tmpFileExistsAfterUsage)(isFalse) + } + ) + + private def createAndWriteAndThenRead(file: Path)(bytes: Chunk[Byte]) = + Files.createFile(file) *> writeAndThenRead(file)(bytes) + + private def writeAndThenRead(file: Path)(bytes: Chunk[Byte]) = + Files.writeBytes(file, bytes) *> Files.readAllBytes(file) +}