Files
swift-nio/Sources/_NIOFileSystem/FileSystemProtocol.swift
Joseph Heck 6b33489e64 Updates to present docs on _NIOFileSystem (#3477)
This re-enables documentation lost during refactoring work, under the
target _NIOFileSystem

resolves #3474 

### Motivation:

During the refactoring of NIOFileSystem and recent Swift updates, the
mechanism to "shadow" symbols using `@_exported import` has stopped
working for documentation and imports for symbols, which means that
_NIOFileSystem is the target that needs to host the documentation for
this (for now)

### Modifications:

Moves DocC catalog into _NIOFileSystem target, and updates
disambiguation hashes on overloaded symbols in order to verify no
warnings are presented while generating documentation.
Updates .spi.yml to present _NIOFileSystem instead of NIOFileSystem

### Result:

Previous documentation should be available again, although at a slightly
different URI structure within Swift Package Index.

Co-authored-by: Cory Benfield <lukasa@apple.com>
2026-01-13 14:22:46 +00:00

734 lines
33 KiB
Swift

//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2025 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
import SystemPackage
/// The interface for interacting with a file system.
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
public protocol FileSystemProtocol: Sendable {
/// The type of ``ReadableFileHandleProtocol`` to return when opening files for reading.
associatedtype ReadFileHandle: ReadableFileHandleProtocol
/// The type of ``WritableFileHandleProtocol`` to return when opening files for writing.
associatedtype WriteFileHandle: WritableFileHandleProtocol
/// The type of ``ReadableAndWritableFileHandleProtocol`` to return when opening files for reading and writing.
associatedtype ReadWriteFileHandle: ReadableAndWritableFileHandleProtocol
/// The type of ``DirectoryFileHandleProtocol`` to return when opening directories.
associatedtype DirectoryFileHandle: DirectoryFileHandleProtocol
where
DirectoryFileHandle.ReadFileHandle == ReadFileHandle,
DirectoryFileHandle.ReadWriteFileHandle == ReadWriteFileHandle,
DirectoryFileHandle.WriteFileHandle == WriteFileHandle
// MARK: - File access
/// Opens the file at `path` for reading and returns a handle to it.
///
/// The file being opened must exist otherwise this function will throw a ``FileSystemError``
/// with code ``FileSystemError/Code-swift.struct/notFound``.
///
/// - Parameters:
/// - path: The path of the file to open.
/// - options: How the file should be opened.
/// - Returns: A readable handle to the opened file.
func openFile(
forReadingAt path: FilePath,
options: OpenOptions.Read
) async throws -> ReadFileHandle
/// Opens the file at `path` for writing and returns a handle to it.
///
/// - Parameters:
/// - path: The path of the file to open relative to the open file.
/// - options: How the file should be opened.
/// - Returns: A writable handle to the opened file.
func openFile(
forWritingAt path: FilePath,
options: OpenOptions.Write
) async throws -> WriteFileHandle
/// Opens the file at `path` for reading and writing and returns a handle to it.
///
/// - Parameters:
/// - path: The path of the file to open relative to the open file.
/// - options: How the file should be opened.
func openFile(
forReadingAndWritingAt path: FilePath,
options: OpenOptions.Write
) async throws -> ReadWriteFileHandle
/// Opens the directory at `path` and returns a handle to it.
///
/// The directory being opened must already exist otherwise this function will throw an error.
/// Use ``createDirectory(at:withIntermediateDirectories:permissions:)`` to create directories.
///
/// - Parameters:
/// - path: The path of the directory to open.
/// - options: How the directory should be opened.
/// - Returns: A handle to the opened directory.
func openDirectory(
atPath path: FilePath,
options: OpenOptions.Directory
) async throws -> DirectoryFileHandle
/// Create a directory at the given path.
///
/// If a directory (or file) already exists at `path` then an error will be thrown. If
/// `createIntermediateDirectories` is `false` then the full prefix of `path` must already
/// exist. If set to `true` then all intermediate directories will be created.
///
/// Related system calls: `mkdir(2)`.
///
/// - Parameters:
/// - path: The directory to create.
/// - createIntermediateDirectories: Whether intermediate directories should be created.
/// - permissions: The permissions to set on the new directory; default permissions will be
/// used if not specified.
func createDirectory(
at path: FilePath,
withIntermediateDirectories createIntermediateDirectories: Bool,
permissions: FilePermissions?
) async throws
// MARK: - Common directories
/// Returns the current working directory.
var currentWorkingDirectory: FilePath { get async throws }
/// Returns the current user's home directory.
var homeDirectory: FilePath { get async throws }
/// Returns the path of the temporary directory.
var temporaryDirectory: FilePath { get async throws }
/// Create a temporary directory at the given path, from a template.
///
/// The template for the path of the temporary directory must end in at least
/// three 'X's, which will be replaced with a unique alphanumeric combination.
/// The template can contain intermediary directories which will be created
/// if they do not exist already.
///
/// Related system calls: `mkdir(2)`.
///
/// - Parameters:
/// - template: The template for the path of the temporary directory.
/// - Returns:
/// - The path to the new temporary directory.
func createTemporaryDirectory(
template: FilePath
) async throws -> FilePath
// MARK: - File information
/// Returns information about the file at the given path, if it exists; nil otherwise.
///
/// - Parameters:
/// - path: The path to get information about.
/// - infoAboutSymbolicLink: If the file is a symbolic link and this parameter is `true` then
/// information about the link will be returned, otherwise information about the
/// destination of the symbolic link is returned.
/// - Returns: Information about the file at the given path or `nil` if no file exists.
func info(
forFileAt path: FilePath,
infoAboutSymbolicLink: Bool
) async throws -> FileInfo?
// MARK: - Symbolic links
/// Creates a symbolic link that points to the destination.
///
/// If a file or directory exists at `path` then an error is thrown.
///
/// - Parameters:
/// - path: The path at which to create the symbolic link.
/// - destinationPath: The path that contains the item that the symbolic link points to.`
func createSymbolicLink(
at path: FilePath,
withDestination destinationPath: FilePath
) async throws
/// Returns the path of the item pointed to by a symbolic link.
///
/// - Parameter path: The path of a file or directory.
/// - Returns: The path of the file or directory to which the symbolic link points to.
func destinationOfSymbolicLink(
at path: FilePath
) async throws -> FilePath
// MARK: - File copying, removal, and moving
/// Copies the item at the specified path to a new location.
///
/// The following error codes may be thrown:
/// - ``FileSystemError/Code-swift.struct/notFound`` if the item at `sourcePath` does not exist,
/// - ``FileSystemError/Code-swift.struct/invalidArgument`` if an item at `destinationPath`
/// exists prior to the copy or its parent directory does not exist.
///
/// Note that other errors may also be thrown.
///
/// If `sourcePath` is a symbolic link then only the link is copied. The copied file will
/// preserve permissions and any extended attributes (if supported by the file system).
///
/// - Parameters:
/// - sourcePath: The path to the item to copy.
/// - destinationPath: The path at which to place the copy.
/// - copyStrategy: How to deal with concurrent aspects of the copy, only relevant to directories.
/// - shouldProceedAfterError: A closure which is executed to determine whether to continue
/// copying files if an error is encountered during the operation. See Errors section for full details.
/// - shouldCopyItem: A closure which is executed before each copy to determine whether each
/// item should be copied. See Filtering section for full details
///
/// #### Errors
///
/// No errors should be throw by implementors without first calling `shouldProceedAfterError`,
/// if that returns without throwing this is taken as permission to continue and the error is swallowed.
/// If instead the closure throws then ``copyItem(at:to:strategy:shouldProceedAfterError:shouldCopyItem:)``
/// will throw and copying will stop, though the precise semantics of this can depend on the `strategy`.
///
/// if using ``CopyStrategy/parallel(maxDescriptors:)``
/// Already started work may continue for an indefinite period of time. In particular, after throwing an error
/// it is possible that invocations of `shouldCopyItem` may continue to occur!
///
/// If using ``CopyStrategy/sequential`` only one invocation of any of the `should*` closures will occur at a time,
/// and an error will immediately stop further activity.
///
/// The specific error thrown from copyItem is undefined, it does not have to be the same error thrown from
/// `shouldProceedAfterError`.
/// In the event of any errors (ignored or otherwise) implementations are under no obbligation to
/// attempt to 'tidy up' after themselves. The state of the file system within `destinationPath`
/// after an aborted copy should is undefined.
///
/// When calling `shouldProceedAfterError` implementations of this method
/// MUST:
/// - Do so once and only once per item.
/// - Not hold any locks when doing so.
/// MAY:
/// - invoke the function multiple times concurrently (except when using ``CopyStrategy/sequential``)
///
/// #### Filtering
///
/// When invoking `shouldCopyItem` implementations of this method
/// MUST:
/// - Do so once and only once per item.
/// - Do so before attempting any operations related to the copy (including determining if they can do so).
/// - Not hold any locks when doing so.
/// - Check parent directories *before* items within them,
/// * if a parent is ignored no items within it should be considered or checked
/// - Skip all contents of a directory which is filtered out.
/// - Invoke it for the `sourcePath` itself.
/// MAY:
/// - invoke the function multiple times concurrently (except when using ``CopyStrategy/sequential``)
/// - invoke the function an arbitrary point before actually trying to copy the file
func copyItem(
at sourcePath: FilePath,
to destinationPath: FilePath,
strategy copyStrategy: CopyStrategy,
shouldProceedAfterError:
@escaping @Sendable (
_ source: DirectoryEntry,
_ error: Error
) async throws -> Void,
shouldCopyItem:
@escaping @Sendable (
_ source: DirectoryEntry,
_ destination: FilePath
) async -> Bool
) async throws
/// Deletes the file or directory (and its contents) at `path`.
///
/// The item to be removed must be a regular file, symbolic link or directory. If no file exists
/// at the given path then this function returns zero.
///
/// If the item at the `path` is a directory and `removeItemRecursively` is `true` then the
/// contents of all of its subdirectories will be removed recursively before the directory at
/// `path`. Symbolic links are removed (but their targets are not deleted).
///
/// - Parameters:
/// - path: The path to delete.
/// - removalStrategy: Whether to delete files sequentially (one-by-one), or perform a
/// concurrent scan of the tree at `path` and delete files when they are found. Ignored if
/// the item being removed isn't a directory.
/// - removeItemRecursively: If the item being removed is a directory, remove it by
/// recursively removing its children. Setting this to `true` is synonymous with calling
/// `rm -r`, setting this false is synonymous to calling `rmdir`. Ignored if the item
/// being removed isn't a directory.
/// - Returns: The number of deleted items which may be zero if `path` did not exist.
@discardableResult
func removeItem(
at path: FilePath,
strategy removalStrategy: RemovalStrategy,
recursively removeItemRecursively: Bool
) async throws -> Int
/// Moves the file or directory at the specified path to a new location.
///
/// The following error codes may be thrown:
/// - ``FileSystemError/Code-swift.struct/notFound`` if the item at `sourcePath` does not exist,
/// - ``FileSystemError/Code-swift.struct/invalidArgument`` if an item at `destinationPath`
/// exists prior to the copy or its parent directory does not exist.
///
/// Note that other errors may also be thrown.
///
/// If the file at `sourcePath` is a symbolic link then only the link is moved to the new path.
///
/// - Parameters:
/// - sourcePath: The path to the item to move.
/// - destinationPath: The path at which to place the item.
func moveItem(at sourcePath: FilePath, to destinationPath: FilePath) async throws
/// Replaces the item at `destinationPath` with the item at `existingPath`.
///
/// The following error codes may be thrown:
/// - ``FileSystemError/Code-swift.struct/notFound`` if the item at `existingPath` does
/// not exist,
/// - ``FileSystemError/Code-swift.struct/io`` if the file at `existingPath` was successfully
/// copied to `destinationPath` but an error occurred while removing it from `existingPath.`
///
/// Note that other errors may also be thrown.
///
/// The item at `destinationPath` is not required to exist. Note that it is possible to replace
/// a file with a directory and vice versa. After the file or directory at `destinationPath`
/// has been replaced, the item at `existingPath` will be removed.
///
/// - Parameters:
/// - destinationPath: The path of the file or directory to replace.
/// - existingPath: The path of the existing file or directory.
func replaceItem(at destinationPath: FilePath, withItemAt existingPath: FilePath) async throws
}
// MARK: - Open existing files/directories
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
extension FileSystemProtocol {
/// Opens the file at the given path and provides scoped read-only access to it.
///
/// The file remains open during lifetime of the `execute` block and will be closed
/// automatically before the call returns.
/// Files may also be opened in read-write or write-only mode by calling
/// ``FileSystemProtocol/withFileHandle(forReadingAndWritingAt:options:execute:)`` and
/// ``FileSystemProtocol/withFileHandle(forWritingAt:options:execute:)``.
///
/// - Parameters:
/// - path: The path of the file to open for reading.
/// - options: How the file should be opened.
/// - execute: A closure which provides read-only access to the open file. The file is closed
/// automatically after the closure exits.
/// - Important: The handle passed to `execute` must not escape the closure.
/// - Returns: The result of the `execute` closure.
public func withFileHandle<Result>(
forReadingAt path: FilePath,
options: OpenOptions.Read = OpenOptions.Read(),
execute: (_ read: ReadFileHandle) async throws -> Result
) async throws -> Result {
let handle = try await self.openFile(forReadingAt: path, options: options)
return try await withUncancellableTearDown {
try await execute(handle)
} tearDown: { _ in
try await handle.close()
}
}
/// Opens the file at the given path and provides scoped write-only access to it.
///
/// The file remains open during lifetime of the `execute` block and will be closed
/// automatically before the call returns.
/// Files may also be opened in read-write or write-only mode by calling
/// ``FileSystemProtocol/withFileHandle(forReadingAndWritingAt:options:execute:)`` and
/// ``FileSystemProtocol/withFileHandle(forWritingAt:options:execute:)``.
///
/// - Parameters:
/// - path: The path of the file to open for reading.
/// - options: How the file should be opened.
/// - execute: A closure which provides write-only access to the open file. The file is closed
/// automatically after the closure exits.
/// - Important: The handle passed to `execute` must not escape the closure.
/// - Returns: The result of the `execute` closure.
public func withFileHandle<Result>(
forWritingAt path: FilePath,
options: OpenOptions.Write = .newFile(replaceExisting: false),
execute: (_ write: WriteFileHandle) async throws -> Result
) async throws -> Result {
let handle = try await self.openFile(forWritingAt: path, options: options)
return try await withUncancellableTearDown {
try await execute(handle)
} tearDown: { result in
switch result {
case .success:
try await handle.close()
case .failure:
try await handle.close(makeChangesVisible: false)
}
}
}
/// Opens the file at the given path and provides scoped read-write access to it.
///
/// The file remains open during lifetime of the `execute` block and will be closed
/// automatically before the function returns.
/// Files may also be opened in read-only or
/// write-only mode by with ``FileSystemProtocol/withFileHandle(forReadingAt:options:execute:)`` and
/// ``FileSystemProtocol/withFileHandle(forReadingAndWritingAt:options:execute:)``.
///
/// - Parameters:
/// - path: The path of the file to open for reading and writing.
/// - options: How the file should be opened.
/// - execute: A closure which provides access to the open file. The file is closed
/// automatically after the closure exits.
/// - Important: The handle passed to `execute` must not escape the closure.
/// - Returns: The result of the `execute` closure.
public func withFileHandle<Result>(
forReadingAndWritingAt path: FilePath,
options: OpenOptions.Write = .newFile(replaceExisting: false),
execute: (_ readWrite: ReadWriteFileHandle) async throws -> Result
) async throws -> Result {
let handle = try await self.openFile(forReadingAndWritingAt: path, options: options)
return try await withUncancellableTearDown {
try await execute(handle)
} tearDown: { _ in
try await handle.close()
}
}
/// Opens the directory at the given path and provides scoped access to it.
///
/// - Parameters:
/// - path: The path of the directory to open.
/// - options: How the file should be opened.
/// - execute: A closure which provides access to the directory.
/// - Important: The handle passed to `execute` must not escape the closure.
/// - Returns: The result of the `execute` closure.
public func withDirectoryHandle<Result>(
atPath path: FilePath,
options: OpenOptions.Directory = OpenOptions.Directory(),
execute: (_ directory: DirectoryFileHandle) async throws -> Result
) async throws -> Result {
let handle = try await self.openDirectory(atPath: path, options: options)
return try await withUncancellableTearDown {
try await execute(handle)
} tearDown: { _ in
try await handle.close()
}
}
}
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
extension FileSystemProtocol {
/// Opens the file at `path` for reading and returns a handle to it.
///
/// The file being opened must exist otherwise this function will throw a ``FileSystemError``
/// with code ``FileSystemError/Code-swift.struct/notFound``.
///
/// - Parameters:
/// - path: The path of the file to open.
/// - Returns: A readable handle to the opened file.
public func openFile(
forReadingAt path: FilePath
) async throws -> ReadFileHandle {
try await self.openFile(forReadingAt: path, options: OpenOptions.Read())
}
/// Opens the directory at `path` and returns a handle to it.
///
/// The directory being opened must already exist otherwise this function will throw an error.
/// Use ``createDirectory(at:withIntermediateDirectories:permissions:)`` to create directories.
///
/// - Parameters:
/// - path: The path of the directory to open.
/// - Returns: A handle to the opened directory.
public func openDirectory(
atPath path: FilePath
) async throws -> DirectoryFileHandle {
try await self.openDirectory(atPath: path, options: OpenOptions.Directory())
}
/// Returns information about the file at the given path, if it exists; nil otherwise.
///
/// Calls ``info(forFileAt:infoAboutSymbolicLink:)`` setting `infoAboutSymbolicLink` to `false`.
///
/// - Parameters:
/// - path: The path to get information about.
/// - Returns: Information about the file at the given path or `nil` if no file exists.
public func info(forFileAt path: FilePath) async throws -> FileInfo? {
try await self.info(forFileAt: path, infoAboutSymbolicLink: false)
}
/// Copies the item at the specified path to a new location.
///
/// The following error codes may be thrown:
/// - ``FileSystemError/Code-swift.struct/notFound`` if the item at `sourcePath` does not exist,
/// - ``FileSystemError/Code-swift.struct/invalidArgument`` if an item at `destinationPath`
/// exists prior to the copy or its parent directory does not exist.
///
/// Note that other errors may also be thrown. If any error is encountered during the copy
/// then the copy is aborted. You can modify the behaviour with the `shouldProceedAfterError`
/// parameter of ``FileSystemProtocol/copyItem(at:to:strategy:shouldProceedAfterError:shouldCopyItem:)``.
///
/// If the file at `sourcePath` is a symbolic link then only the link is copied to the new path.
///
/// - Parameters:
/// - sourcePath: The path to the item to copy.
/// - destinationPath: The path at which to place the copy.
/// - copyStrategy: This controls the concurrency used if the file at `sourcePath` is a directory.
public func copyItem(
at sourcePath: FilePath,
to destinationPath: FilePath,
strategy copyStrategy: CopyStrategy = .platformDefault
) async throws {
try await self.copyItem(at: sourcePath, to: destinationPath, strategy: copyStrategy) { path, error in
throw error
} shouldCopyItem: { source, destination in
true
}
}
/// Copies the item at the specified path to a new location.
///
/// The item to be copied must be a:
/// - regular file,
/// - symbolic link, or
/// - directory.
///
/// If `sourcePath` is a symbolic link then only the link is copied. The copied file will
/// preserve permissions and any extended attributes (if supported by the file system).
///
/// #### Errors
///
/// Error codes thrown include:
/// - ``FileSystemError/Code-swift.struct/notFound`` if `sourcePath` doesn't exist.
/// - ``FileSystemError/Code-swift.struct/fileAlreadyExists`` if `destinationPath` exists.
///
/// #### Backward Compatibility details
///
/// This is implemented in terms of ``copyItem(at:to:strategy:shouldProceedAfterError:shouldCopyItem:)``
/// using ``CopyStrategy/sequential`` to avoid changing the concurrency semantics of the should callbacks
///
/// - Parameters:
/// - sourcePath: The path to the item to copy.
/// - destinationPath: The path at which to place the copy.
/// - shouldProceedAfterError: Determines whether to continue copying files if an error is
/// thrown during the operation. This error does not have to match the error passed
/// to the closure.
/// - shouldCopyFile: A closure which is executed before each file to determine whether the
/// file should be copied.
@available(*, deprecated, message: "please use copyItem overload taking CopyStrategy")
public func copyItem(
at sourcePath: FilePath,
to destinationPath: FilePath,
shouldProceedAfterError:
@escaping @Sendable (
_ entry: DirectoryEntry,
_ error: Error
) async throws -> Void,
shouldCopyFile:
@escaping @Sendable (
_ source: FilePath,
_ destination: FilePath
) async -> Bool
) async throws {
try await self.copyItem(
at: sourcePath,
to: destinationPath,
strategy: .sequential,
shouldProceedAfterError: shouldProceedAfterError,
shouldCopyItem: { (source, destination) in
await shouldCopyFile(source.path, destination)
}
)
}
/// Copies the item at the specified path to a new location.
///
/// The following error codes may be thrown:
/// - ``FileSystemError/Code-swift.struct/notFound`` if the item at `sourcePath` does not exist,
/// - ``FileSystemError/Code-swift.struct/invalidArgument`` if an item at `destinationPath`
/// exists prior to the copy or its parent directory does not exist.
///
/// Note that other errors may also be thrown.
///
/// If `sourcePath` is a symbolic link then only the link is copied. The copied file will
/// preserve permissions and any extended attributes (if supported by the file system).
///
/// - Parameters:
/// - sourcePath: The path to the item to copy.
/// - destinationPath: The path at which to place the copy.
/// - shouldProceedAfterError: A closure which is executed to determine whether to continue
/// copying files if an error is encountered during the operation. See Errors section for full details.
/// - shouldCopyItem: A closure which is executed before each copy to determine whether each
/// item should be copied. See Filtering section for full details
///
/// #### Parallelism
///
/// This overload uses ``CopyStrategy/platformDefault`` which is likely to result in multiple concurrency domains being used
/// in the event of copying a directory.
/// See the detailed description on ``copyItem(at:to:strategy:shouldProceedAfterError:shouldCopyItem:)``
/// for the implications of this with respect to the `shouldProceedAfterError` and `shouldCopyItem` callbacks
public func copyItem(
at sourcePath: FilePath,
to destinationPath: FilePath,
shouldProceedAfterError:
@escaping @Sendable (
_ source: DirectoryEntry,
_ error: Error
) async throws -> Void,
shouldCopyItem:
@escaping @Sendable (
_ source: DirectoryEntry,
_ destination: FilePath
) async -> Bool
) async throws {
try await self.copyItem(
at: sourcePath,
to: destinationPath,
strategy: .platformDefault,
shouldProceedAfterError: shouldProceedAfterError,
shouldCopyItem: shouldCopyItem
)
}
/// Deletes the file or directory (and its contents) at `path`.
///
/// The item to be removed must be a regular file, symbolic link or directory. If no file exists
/// at the given path then this function returns zero.
///
/// If the item at the `path` is a directory then the contents of all of its subdirectories will
/// be removed recursively before the directory at `path`. Symbolic links are removed (but their
/// targets are not deleted).
///
/// The strategy for deletion will be determined automatically depending on the discovered
/// platform.
///
/// - Parameters:
/// - path: The path to delete.
/// - Returns: The number of deleted items which may be zero if `path` did not exist.
@discardableResult
public func removeItem(
at path: FilePath
) async throws -> Int {
try await self.removeItem(at: path, strategy: .platformDefault, recursively: true)
}
/// Deletes the file or directory (and its contents) at `path`.
///
/// The item to be removed must be a regular file, symbolic link or directory. If no file exists
/// at the given path then this function returns zero.
///
/// If the item at the `path` is a directory then the contents of all of its subdirectories will
/// be removed recursively before the directory at `path`. Symbolic links are removed (but their
/// targets are not deleted).
///
/// The strategy for deletion will be determined automatically depending on the discovered
/// platform.
///
/// - Parameters:
/// - path: The path to delete.
/// - removeItemRecursively: If the item being removed is a directory, remove it by
/// recursively removing its children. Setting this to `true` is synonymous with calling
/// `rm -r`, setting this false is synonymous to calling `rmdir`. Ignored if the item
/// being removed isn't a directory.
/// - Returns: The number of deleted items which may be zero if `path` did not exist.
@discardableResult
public func removeItem(
at path: FilePath,
recursively removeItemRecursively: Bool
) async throws -> Int {
try await self.removeItem(at: path, strategy: .platformDefault, recursively: removeItemRecursively)
}
/// Deletes the file or directory (and its contents) at `path`.
///
/// The item to be removed must be a regular file, symbolic link or directory. If no file exists
/// at the given path then this function returns zero.
///
/// If the item at the `path` is a directory then the contents of all of its subdirectories will
/// be removed recursively before the directory at `path`. Symbolic links are removed (but their
/// targets are not deleted).
///
/// - Parameters:
/// - path: The path to delete.
/// - removalStrategy: Whether to delete files sequentially (one-by-one), or perform a
/// concurrent scan of the tree at `path` and delete files when they are found.
/// - Returns: The number of deleted items which may be zero if `path` did not exist.
@discardableResult
public func removeItem(
at path: FilePath,
strategy removalStrategy: RemovalStrategy
) async throws -> Int {
try await self.removeItem(at: path, strategy: removalStrategy, recursively: true)
}
/// Create a directory at the given path.
///
/// If a directory (or file) already exists at `path` then an error will be thrown. If
/// `createIntermediateDirectories` is `false` then the full prefix of `path` must already
/// exist. If set to `true` then all intermediate directories will be created.
///
/// New directories will be given read-write-execute owner permissions and read-execute group
/// and other permissions.
///
/// Related system calls: `mkdir(2)`.
///
/// - Parameters:
/// - path: The directory to create.
/// - createIntermediateDirectories: Whether intermediate directories should be created.
public func createDirectory(
at path: FilePath,
withIntermediateDirectories createIntermediateDirectories: Bool
) async throws {
try await self.createDirectory(
at: path,
withIntermediateDirectories: createIntermediateDirectories,
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<Result>(
prefix: FilePath? = nil,
options: OpenOptions.Directory = OpenOptions.Directory(),
execute: (_ directory: DirectoryFileHandle, _ path: FilePath) async throws -> Result
) async throws -> Result {
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, strategy: .platformDefault, recursively: true)
}
}
}