Skip to content

Commit

Permalink
Add 'withTemporaryDirectory' (#2708)
Browse files Browse the repository at this point in the history
Motivation:

NIOFileSystem can create the path of a temporary directory but doesn't
offer any API to create and then remove a directory.

Modifications:

- Add `withTemporaryDirectory`

Result:

- Users can get scoped access to a temporary directory which is
  subsequently removed for them
- Resolves #2664
  • Loading branch information
glbrntt committed Apr 26, 2024
1 parent f2f4ce8 commit 8c3135b
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,4 @@ closing it to avoid leaking resources.

- ``currentWorkingDirectory``
- ``temporaryDirectory``
- ``withTemporaryDirectory(prefix:options:execute:)``
36 changes: 36 additions & 0 deletions Sources/NIOFileSystem/FileSystemProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,42 @@ extension FileSystemProtocol {
permissions: .defaultsForDirectory
)
}

/// Create a temporary directory and removes it once the function returns.
///
/// You can use `prefix` to specify the directory in which the temporary directory should
/// be created. If `prefix` is `nil` then the value of ``temporaryDirectory`` is used as
/// the prefix.
///
/// The temporary directory, and all of its contents, is removed once `execute` returns.
///
/// - Parameters:
/// - prefix: The prefix to use for the path of the temporary directory.
/// - options: Options used to create the directory.
/// - execute: A closure which provides access to the directory and its path.
/// - Returns: The result of `execute`.
public func withTemporaryDirectory<ReturnType>(
prefix: FilePath? = nil,
options: OpenOptions.Directory = OpenOptions.Directory(),
execute: (_ directory: DirectoryFileHandle, _ path: FilePath) async throws -> ReturnType
) async throws -> ReturnType {
let template: FilePath

if let prefix = prefix {
template = prefix.appending("XXXXXXXX")
} else {
template = try await self.temporaryDirectory.appending("XXXXXXXX")
}

let directory = try await self.createTemporaryDirectory(template: template)
return try await withUncancellableTearDown {
try await withDirectoryHandle(atPath: directory, options: options) { handle in
try await execute(handle, directory)
}
} tearDown: { _ in
try await self.removeItem(at: directory, recursively: true)
}
}
}

#endif
49 changes: 49 additions & 0 deletions Tests/NIOFileSystemIntegrationTests/FileSystemTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1055,6 +1055,55 @@ final class FileSystemTests: XCTestCase {
XCTAssertEqual(names.sorted(), expected.sorted())
}
}

func testWithTemporaryDirectory() async throws {
let fs = FileSystem.shared

let createdPath = try await fs.withTemporaryDirectory { directory, path in
let root = try await fs.temporaryDirectory
XCTAssert(path.starts(with: root))
return path
}

// Directory shouldn't exist any more.
let info = try await fs.info(forFileAt: createdPath)
XCTAssertNil(info)
}

func testWithTemporaryDirectoryPrefix() async throws {
let fs = FileSystem.shared
let prefix = try await fs.currentWorkingDirectory

let createdPath = try await fs.withTemporaryDirectory(prefix: prefix) { directory, path in
XCTAssert(path.starts(with: prefix))
return path
}

// Directory shouldn't exist any more.
let info = try await fs.info(forFileAt: createdPath)
XCTAssertNil(info)
}

func testWithTemporaryDirectoryRemovesContents() async throws {
let fs = FileSystem.shared
let createdPath = try await fs.withTemporaryDirectory { directory, path in
for name in ["foo", "bar", "baz"] {
try await directory.withFileHandle(forWritingAt: FilePath(name)) { fh in
_ = try await fh.write(contentsOf: [1, 2, 3], toAbsoluteOffset: 0)
}
}

let entries = try await directory.listContents().reduce(into: []) { $0.append($1) }
let names = entries.map { $0.name.string }
XCTAssertEqual(names.sorted(), ["bar", "baz", "foo"])

return path
}

// Directory shouldn't exist any more.
let info = try await fs.info(forFileAt: createdPath)
XCTAssertNil(info)
}
}

@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
Expand Down

0 comments on commit 8c3135b

Please sign in to comment.