mirror of
https://github.com/apple/swift-nio.git
synced 2026-05-20 20:30:36 +00:00
The createDirectory function will succeed without error if the target directory already exists. ### Motivation This change addresses issue #3404. Currently, `fileSystem.createDirectory` fails if the target directory already exists, forcing users to write boilerplate try/catch blocks to handle this common and expected case. The goal is to make this function's behavior idempotent. ### Modifications To achieve this, I've made the following changes: **(Implementation)** A new private helper function, `_handleCreateDirectoryFileExists`, was introduced. This function is responsible for: 1. Performing a `stat` call on the path that failed. 2. Checking if the existing item is a directory (`S_IFDIR`). 3. Returning a success result if it's a directory, or re-throwing the original `.fileExists` error if it's a file or another type of entity. **(Logic)** The core `_createDirectory` function was updated to call this new helper function whenever `Syscall.mkdir` fails with an `EEXIST` (`.fileExists`) error. This check is applied in both internal loops to correctly handle cases where either an intermediate directory or the final target directory already exists. ### Result With this change, users can now call the function `fileSystem.createDirectory` and the operation will succeed even if the directory is already present, leading to cleaner and more predictable code.
This commit is contained in:
@@ -825,6 +825,20 @@ extension FileSystem {
|
||||
break loop
|
||||
|
||||
case let .failure(errno):
|
||||
if errno == .fileExists {
|
||||
switch self._info(forFileAt: path, infoAboutSymbolicLink: false) {
|
||||
case let .success(maybeInfo):
|
||||
if let info = maybeInfo, info.type == .directory {
|
||||
break loop
|
||||
} else {
|
||||
// A file exists at this path.
|
||||
return .failure(.mkdir(errno: errno, path: path, location: .here()))
|
||||
}
|
||||
case .failure:
|
||||
// Unable to determine what exists at this path.
|
||||
return .failure(.mkdir(errno: errno, path: path, location: .here()))
|
||||
}
|
||||
}
|
||||
guard createIntermediateDirectories, errno == .noSuchFileOrDirectory else {
|
||||
return .failure(.mkdir(errno: errno, path: path, location: .here()))
|
||||
}
|
||||
|
||||
@@ -839,6 +839,20 @@ extension FileSystem {
|
||||
break loop
|
||||
|
||||
case let .failure(errno):
|
||||
if errno == .fileExists {
|
||||
switch self._info(forFileAt: path, infoAboutSymbolicLink: false) {
|
||||
case let .success(maybeInfo):
|
||||
if let info = maybeInfo, info.type == .directory {
|
||||
break loop
|
||||
} else {
|
||||
// A file exists at this path.
|
||||
return .failure(.mkdir(errno: errno, path: path, location: .here()))
|
||||
}
|
||||
case .failure:
|
||||
// Unable to determine what exists at this path.
|
||||
return .failure(.mkdir(errno: errno, path: path, location: .here()))
|
||||
}
|
||||
}
|
||||
guard createIntermediateDirectories, errno == .noSuchFileOrDirectory else {
|
||||
return .failure(.mkdir(errno: errno, path: path, location: .here()))
|
||||
}
|
||||
|
||||
@@ -505,6 +505,37 @@ final class FileSystemTests: XCTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
func testCreateDirectoryIsIdempotentWhenAlreadyExists() async throws {
|
||||
let path = try await self.fs.temporaryFilePath()
|
||||
|
||||
try await self.fs.createDirectory(at: path, withIntermediateDirectories: false)
|
||||
|
||||
try await self.fs.createDirectory(at: path, withIntermediateDirectories: false)
|
||||
try await self.fs.createDirectory(at: path, withIntermediateDirectories: true)
|
||||
|
||||
try await self.fs.withDirectoryHandle(atPath: path) { dir in
|
||||
let info = try await dir.info()
|
||||
XCTAssertEqual(info.type, .directory)
|
||||
XCTAssertGreaterThan(info.size, 0)
|
||||
}
|
||||
}
|
||||
|
||||
func testCreateDirectoryThroughSymlinkToExistingDirectoryIsIdempotent() async throws {
|
||||
let realDir = try await self.fs.temporaryFilePath()
|
||||
try await self.fs.createDirectory(at: realDir, withIntermediateDirectories: false)
|
||||
|
||||
let linkPath = try await self.fs.temporaryFilePath()
|
||||
try await self.fs.createSymbolicLink(at: linkPath, withDestination: realDir)
|
||||
|
||||
try await self.fs.createDirectory(at: linkPath, withIntermediateDirectories: false)
|
||||
|
||||
try await self.fs.withDirectoryHandle(atPath: linkPath) { dir in
|
||||
let info = try await dir.info()
|
||||
XCTAssertEqual(info.type, .directory)
|
||||
XCTAssertGreaterThan(info.size, 0)
|
||||
}
|
||||
}
|
||||
|
||||
func testCurrentWorkingDirectory() async throws {
|
||||
let directory = try await self.fs.currentWorkingDirectory
|
||||
XCTAssert(!directory.underlying.isEmpty)
|
||||
|
||||
Reference in New Issue
Block a user