From d3522e8da63b55c7d3fa14cc9b0b76acd57c60ca Mon Sep 17 00:00:00 2001 From: pathikrit Date: Thu, 2 Feb 2017 11:43:50 -0500 Subject: [PATCH] File.usingTemp util --- CHANGES.md | 1 + README.md | 22 +++++++++++++++- core/src/main/scala/better/files/File.scala | 25 +++++++++++++++++++ .../test/scala/better/files/FileSpec.scala | 21 ++++++++-------- 4 files changed, 58 insertions(+), 11 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index d92e170e..283f462e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,6 @@ ## v3.0.0 +* [File.usingTemp]() * [Optional symbolic operations](https://github.com/pathikrit/better-files/issues/102) * [PR #100](https://github.com/pathikrit/better-files/pull/100): Fix issue in unzip of parents * [PR #101](https://github.com/pathikrit/better-files/pull/101): Removed File.Type diff --git a/README.md b/README.md index e7888a83..dce7cd0e 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ 0. [Java compatibility](#java-interoperability) 0. [Globbing](#globbing) 0. [File system operations](#file-system-operations) + 0. [Temporary files](#temporary-files) 0. [UNIX DSL](#unix-dsl) 0. [File attributes](#file-attributes) 0. [File comparison](#file-comparison) @@ -270,10 +271,29 @@ file.setGroup(group: String) // chgrp group file Seq(file1, file2) `>:` file3 // same as cat file1 file2 > file3 (must import import better.files.Dsl.SymbolicOperations) Seq(file1, file2) >>: file3 // same as cat file1 file2 >> file3 (must import import better.files.Dsl.SymbolicOperations) file.isReadLocked; file.isWriteLocked; file.isLocked -File.newTemporaryDirectory() / File.newTemporaryFile() // create temp dir/file File.numberOfOpenFileDescriptors // number of open file descriptors ``` +### Temporary files +Utils to create temporary files: +```scala +File.newTemporaryDirectory() +File.newTemporaryFile() +``` +The above APIs allow optional specifications of `prefix`, `suffix` and `parentDir`. +These files are [not deleted automatically on exit by the JVM](http://stackoverflow.com/questions/16691437/when-are-java-temporary-files-deleted) (you have to set `deleteOnExit` which adds to `shutdownHook`). + +A cleaner alternative is to use self-deleting file contexts which deletes the file immediately when done: +```scala +File.usingTempFile() {tempFile => + ... +} + +// or equivalently: + +File.newTempFile().applyAndDelete(tempFile => ...) +``` + ### UNIX DSL All the above can also be expressed using [methods](http://pathikrit.github.io/better-files/latest/api/better/files/Dsl$.html) reminiscent of the command line: ```scala diff --git a/core/src/main/scala/better/files/File.scala b/core/src/main/scala/better/files/File.scala index 57ed649c..f15960af 100644 --- a/core/src/main/scala/better/files/File.scala +++ b/core/src/main/scala/better/files/File.scala @@ -759,6 +759,11 @@ class File private(val path: Path) { this } + def deleteOnExit(): this.type = { + toJava.deleteOnExit() + this + } + override def hashCode = path.hashCode() @@ -828,6 +833,20 @@ class File private(val path: Path) { def unzip(zipFilter: ZipEntry => Boolean = _ => true)(implicit codec: Codec): File = unzipTo(destination = File.newTemporaryDirectory(name), zipFilter)(codec) + /** + * Applies the given function on this and then deletes this file + * + * @param f + * @tparam U + * @return + */ + def applyAndDelete[U](f: File => U): U = + try { + f(this) + } finally { + val _ = delete(swallowIOExceptions = true) + } + //TODO: add features from https://github.com/sbt/io } @@ -839,6 +858,9 @@ object File { } } + def usingTemporaryDirectory[U](prefix: String = "", parent: Option[File] = None, attributes: Attributes = Attributes.default)(f: File => U): U = + newTemporaryDirectory(prefix, parent)(attributes).applyAndDelete(f) + def newTemporaryFile(prefix: String = "", suffix: String = "", parent: Option[File] = None)(implicit attributes: Attributes = Attributes.default): File = { parent match { case Some(dir) => Files.createTempFile(dir.path, prefix, suffix, attributes: _*) @@ -846,6 +868,9 @@ object File { } } + def usingTemporaryFile[U](prefix: String = "", suffix: String = "", parent: Option[File] = None, attributes: Attributes = Attributes.default)(f: File => U): U = + newTemporaryFile(prefix, suffix, parent)(attributes).applyAndDelete(f) + implicit def apply(path: Path): File = new File(path.toAbsolutePath.normalize()) diff --git a/core/src/test/scala/better/files/FileSpec.scala b/core/src/test/scala/better/files/FileSpec.scala index a3ef2809..48fa6e07 100644 --- a/core/src/test/scala/better/files/FileSpec.scala +++ b/core/src/test/scala/better/files/FileSpec.scala @@ -233,16 +233,17 @@ class FileSpec extends FlatSpec with BeforeAndAfterEach with Matchers { } it should "detect file locks" in { - val file = File.newTemporaryFile() - def lockInfo() = file.isReadLocked() -> file.isWriteLocked() - // TODO: Why is file.isReadLocked() should be false? - lockInfo() shouldBe (true -> false) - val channel = file.newRandomAccess(File.RandomAccessMode.readWrite).getChannel - val lock = channel.tryLock() - lockInfo() shouldBe (true -> true) - lock.release() - channel.close() - lockInfo() shouldBe (true -> false) + File.usingTemporaryFile() {file => + def lockInfo() = file.isReadLocked() -> file.isWriteLocked() + // TODO: Why is file.isReadLocked() should be false? + lockInfo() shouldBe (true -> false) + val channel = file.newRandomAccess(File.RandomAccessMode.readWrite).getChannel + val lock = channel.tryLock() + lockInfo() shouldBe (true -> true) + lock.release() + channel.close() + lockInfo() shouldBe (true -> false) + } } it should "support ln/cp/mv" in {