Skip to content

Commit

Permalink
Add async overloads of withTemporaryFile and withTemporaryDirectory
Browse files Browse the repository at this point in the history
  • Loading branch information
jakepetroules committed Feb 13, 2023
1 parent 52a962f commit 93784c5
Showing 1 changed file with 137 additions and 0 deletions.
137 changes: 137 additions & 0 deletions Sources/TSCBasic/TemporaryFile.swift
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,39 @@ public func withTemporaryFile<Result>(
}
}

/// Creates a temporary file and evaluates a closure with the temporary file as an argument.
/// The temporary file will live on disk while the closure is evaluated and will be deleted when
/// the cleanup block is called.
///
/// This function is basically a wrapper over posix's mkstemps() function to create disposable files.
///
/// - Parameters:
/// - dir: If specified the temporary file will be created in this directory otherwise environment variables
/// TMPDIR, TEMP and TMP will be checked for a value (in that order). If none of the env variables are
/// set, dir will be set to `/tmp/`.
/// - prefix: The prefix to the temporary file name.
/// - suffix: The suffix to the temporary file name.
/// - body: A closure to execute that receives the TemporaryFile as an argument.
/// If `body` has a return value, that value is also used as the
/// return value for the `withTemporaryFile` function.
/// The cleanup block should be called when the temporary file is no longer needed.
///
/// - Throws: TempFileError and rethrows all errors from `body`.
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
public func withTemporaryFile<Result>(
dir: AbsolutePath? = nil, prefix: String = "TemporaryFile", suffix: String = "", _ body: (TemporaryFile, @escaping (TemporaryFile) async -> Void) async throws -> Result
) async throws -> Result {
return try await body(TemporaryFile(dir: dir, prefix: prefix, suffix: suffix)) { tempFile in
#if os(Windows)
_ = tempFile.path.pathString.withCString(encodedAs: UTF16.self) {
_wunlink($0)
}
#else
unlink(tempFile.path.pathString)
#endif
}
}

/// Creates a temporary file and evaluates a closure with the temporary file as an argument.
/// The temporary file will live on disk while the closure is evaluated and will be deleted afterwards.
///
Expand All @@ -167,6 +200,40 @@ public func withTemporaryFile<Result>(
}
}

/// Creates a temporary file and evaluates a closure with the temporary file as an argument.
/// The temporary file will live on disk while the closure is evaluated and will be deleted afterwards.
///
/// This function is basically a wrapper over posix's mkstemps() function to create disposable files.
///
/// - Parameters:
/// - dir: If specified the temporary file will be created in this directory otherwise environment variables
/// TMPDIR, TEMP and TMP will be checked for a value (in that order). If none of the env variables are
/// set, dir will be set to `/tmp/`.
/// - prefix: The prefix to the temporary file name.
/// - suffix: The suffix to the temporary file name.
/// - deleteOnClose: Whether the file should get deleted after the call of `body`
/// - body: A closure to execute that receives the TemporaryFile as an argument.
/// If `body` has a return value, that value is also used as the
/// return value for the `withTemporaryFile` function.
///
/// - Throws: TempFileError and rethrows all errors from `body`.
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
public func withTemporaryFile<Result>(
dir: AbsolutePath? = nil, prefix: String = "TemporaryFile", suffix: String = "", deleteOnClose: Bool = true, _ body: (TemporaryFile) async throws -> Result
) async throws -> Result {
try await withTemporaryFile(dir: dir, prefix: prefix, suffix: suffix) { tempFile, cleanup in
let result: Result
do {
result = try await body(tempFile)
if (deleteOnClose) { await cleanup(tempFile) }
} catch {
if (deleteOnClose) { await cleanup(tempFile) }
throw error
}
return result
}
}

// FIXME: This isn't right place to declare this, probably POSIX or merge with FileSystemError?
//
/// Contains the error which can be thrown while creating a directory using POSIX's mkdir.
Expand Down Expand Up @@ -252,6 +319,44 @@ public func withTemporaryDirectory<Result>(
}
}

/// Creates a temporary directory and evaluates a closure with the directory path as an argument.
/// The temporary directory will live on disk while the closure is evaluated and will be deleted when
/// the cleanup closure is called. This allows the temporary directory to have an arbitrary lifetime.
///
/// This function is basically a wrapper over posix's mkdtemp() function.
///
/// - Parameters:
/// - dir: If specified the temporary directory will be created in this directory otherwise environment
/// variables TMPDIR, TEMP and TMP will be checked for a value (in that order). If none of the env
/// variables are set, dir will be set to `/tmp/`.
/// - prefix: The prefix to the temporary file name.
/// - body: A closure to execute that receives the absolute path of the directory as an argument.
/// If `body` has a return value, that value is also used as the
/// return value for the `withTemporaryDirectory` function.
/// The cleanup block should be called when the temporary directory is no longer needed.
///
/// - Throws: MakeDirectoryError and rethrows all errors from `body`.
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
public func withTemporaryDirectory<Result>(
dir: AbsolutePath? = nil, prefix: String = "TemporaryDirectory" , _ body: (AbsolutePath, @escaping (AbsolutePath) async -> Void) async throws -> Result
) async throws -> Result {
// Construct path to the temporary directory.
let templatePath = try AbsolutePath(validating: prefix + ".XXXXXX", relativeTo: determineTempDirectory(dir))

// Convert templatePath to a C style string terminating with null char to be an valid input
// to mkdtemp method. The XXXXXX in this string will be replaced by a random string
// which will be the actual path to the temporary directory.
var template = [UInt8](templatePath.pathString.utf8).map({ Int8($0) }) + [Int8(0)]

if TSCLibc.mkdtemp(&template) == nil {
throw MakeDirectoryError(errno: errno)
}

return try await body(AbsolutePath(validating: String(cString: template))) { path in
_ = try? FileManager.default.removeItem(atPath: path.pathString)
}
}

/// Creates a temporary directory and evaluates a closure with the directory path as an argument.
/// The temporary directory will live on disk while the closure is evaluated and will be deleted afterwards.
///
Expand All @@ -277,3 +382,35 @@ public func withTemporaryDirectory<Result>(
}
}

/// Creates a temporary directory and evaluates a closure with the directory path as an argument.
/// The temporary directory will live on disk while the closure is evaluated and will be deleted afterwards.
///
/// This function is basically a wrapper over posix's mkdtemp() function.
///
/// - Parameters:
/// - dir: If specified the temporary directory will be created in this directory otherwise environment
/// variables TMPDIR, TEMP and TMP will be checked for a value (in that order). If none of the env
/// variables are set, dir will be set to `/tmp/`.
/// - prefix: The prefix to the temporary file name.
/// - removeTreeOnDeinit: If enabled try to delete the whole directory tree otherwise remove only if its empty.
/// - body: A closure to execute that receives the absolute path of the directory as an argument.
/// If `body` has a return value, that value is also used as the
/// return value for the `withTemporaryDirectory` function.
///
/// - Throws: MakeDirectoryError and rethrows all errors from `body`.
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
public func withTemporaryDirectory<Result>(
dir: AbsolutePath? = nil, prefix: String = "TemporaryDirectory", removeTreeOnDeinit: Bool = false , _ body: (AbsolutePath) async throws -> Result
) async throws -> Result {
try await withTemporaryDirectory(dir: dir, prefix: prefix) { path, cleanup in
let result: Result
do {
result = try await body(path)
if removeTreeOnDeinit { await cleanup(path) }
} catch {
if removeTreeOnDeinit { await cleanup(path) }
throw error
}
return result
}
}

0 comments on commit 93784c5

Please sign in to comment.