Skip to content

Commit 8c3135b

Browse files
authored
Add 'withTemporaryDirectory' (#2708)
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
1 parent f2f4ce8 commit 8c3135b

File tree

3 files changed

+86
-0
lines changed

3 files changed

+86
-0
lines changed

Sources/NIOFileSystem/Docs.docc/Extensions/FileSystemProtocol.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,4 @@ closing it to avoid leaking resources.
4343

4444
- ``currentWorkingDirectory``
4545
- ``temporaryDirectory``
46+
- ``withTemporaryDirectory(prefix:options:execute:)``

Sources/NIOFileSystem/FileSystemProtocol.swift

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,42 @@ extension FileSystemProtocol {
476476
permissions: .defaultsForDirectory
477477
)
478478
}
479+
480+
/// Create a temporary directory and removes it once the function returns.
481+
///
482+
/// You can use `prefix` to specify the directory in which the temporary directory should
483+
/// be created. If `prefix` is `nil` then the value of ``temporaryDirectory`` is used as
484+
/// the prefix.
485+
///
486+
/// The temporary directory, and all of its contents, is removed once `execute` returns.
487+
///
488+
/// - Parameters:
489+
/// - prefix: The prefix to use for the path of the temporary directory.
490+
/// - options: Options used to create the directory.
491+
/// - execute: A closure which provides access to the directory and its path.
492+
/// - Returns: The result of `execute`.
493+
public func withTemporaryDirectory<ReturnType>(
494+
prefix: FilePath? = nil,
495+
options: OpenOptions.Directory = OpenOptions.Directory(),
496+
execute: (_ directory: DirectoryFileHandle, _ path: FilePath) async throws -> ReturnType
497+
) async throws -> ReturnType {
498+
let template: FilePath
499+
500+
if let prefix = prefix {
501+
template = prefix.appending("XXXXXXXX")
502+
} else {
503+
template = try await self.temporaryDirectory.appending("XXXXXXXX")
504+
}
505+
506+
let directory = try await self.createTemporaryDirectory(template: template)
507+
return try await withUncancellableTearDown {
508+
try await withDirectoryHandle(atPath: directory, options: options) { handle in
509+
try await execute(handle, directory)
510+
}
511+
} tearDown: { _ in
512+
try await self.removeItem(at: directory, recursively: true)
513+
}
514+
}
479515
}
480516

481517
#endif

Tests/NIOFileSystemIntegrationTests/FileSystemTests.swift

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1055,6 +1055,55 @@ final class FileSystemTests: XCTestCase {
10551055
XCTAssertEqual(names.sorted(), expected.sorted())
10561056
}
10571057
}
1058+
1059+
func testWithTemporaryDirectory() async throws {
1060+
let fs = FileSystem.shared
1061+
1062+
let createdPath = try await fs.withTemporaryDirectory { directory, path in
1063+
let root = try await fs.temporaryDirectory
1064+
XCTAssert(path.starts(with: root))
1065+
return path
1066+
}
1067+
1068+
// Directory shouldn't exist any more.
1069+
let info = try await fs.info(forFileAt: createdPath)
1070+
XCTAssertNil(info)
1071+
}
1072+
1073+
func testWithTemporaryDirectoryPrefix() async throws {
1074+
let fs = FileSystem.shared
1075+
let prefix = try await fs.currentWorkingDirectory
1076+
1077+
let createdPath = try await fs.withTemporaryDirectory(prefix: prefix) { directory, path in
1078+
XCTAssert(path.starts(with: prefix))
1079+
return path
1080+
}
1081+
1082+
// Directory shouldn't exist any more.
1083+
let info = try await fs.info(forFileAt: createdPath)
1084+
XCTAssertNil(info)
1085+
}
1086+
1087+
func testWithTemporaryDirectoryRemovesContents() async throws {
1088+
let fs = FileSystem.shared
1089+
let createdPath = try await fs.withTemporaryDirectory { directory, path in
1090+
for name in ["foo", "bar", "baz"] {
1091+
try await directory.withFileHandle(forWritingAt: FilePath(name)) { fh in
1092+
_ = try await fh.write(contentsOf: [1, 2, 3], toAbsoluteOffset: 0)
1093+
}
1094+
}
1095+
1096+
let entries = try await directory.listContents().reduce(into: []) { $0.append($1) }
1097+
let names = entries.map { $0.name.string }
1098+
XCTAssertEqual(names.sorted(), ["bar", "baz", "foo"])
1099+
1100+
return path
1101+
}
1102+
1103+
// Directory shouldn't exist any more.
1104+
let info = try await fs.info(forFileAt: createdPath)
1105+
XCTAssertNil(info)
1106+
}
10581107
}
10591108

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

0 commit comments

Comments
 (0)