File: TemporaryFile.swift

package info (click to toggle)
swiftlang 6.0.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,519,992 kB
  • sloc: cpp: 9,107,863; ansic: 2,040,022; asm: 1,135,751; python: 296,500; objc: 82,456; f90: 60,502; lisp: 34,951; pascal: 19,946; sh: 18,133; perl: 7,482; ml: 4,937; javascript: 4,117; makefile: 3,840; awk: 3,535; xml: 914; fortran: 619; cs: 573; ruby: 573
file content (104 lines) | stat: -rw-r--r-- 5,002 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift open source project
//
// Copyright (c) 2022 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import _Concurrency
import Foundation
import enum TSCBasic.TempFileError

/// 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.
///
/// - Parameters:
///     - fileSystem: `FileSystem` which is used to construct temporary directory.
///     - 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: An error when creating directory and rethrows all errors from `body`.
public func withTemporaryDirectory<Result>(
    fileSystem: FileSystem = localFileSystem,
    dir: AbsolutePath? = nil,
    prefix: String = "TemporaryDirectory",
    _ body: @escaping @Sendable (AbsolutePath, @escaping (AbsolutePath) -> Void) async throws -> Result
) throws -> Task<Result, Error> {
    let temporaryDirectory = try createTemporaryDirectory(fileSystem: fileSystem, dir: dir, prefix: prefix)

    let task: Task<Result, Error> = Task {
        try await withTaskCancellationHandler {
            try await body(temporaryDirectory) { path in
                try? fileSystem.removeFileTree(path)
            }
        } onCancel: {
            try? fileSystem.removeFileTree(temporaryDirectory)
        }
    }

    return task
}

/// 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.
///
/// - Parameters:
///     - fileSystem: `FileSystem` which is used to construct temporary directory.
///     - 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: An error when creating directory and rethrows all errors from `body`.
@discardableResult
public func withTemporaryDirectory<Result>(
    fileSystem: FileSystem = localFileSystem,
    dir: AbsolutePath? = nil,
    prefix: String = "TemporaryDirectory",
    removeTreeOnDeinit: Bool = false,
    _ body: @escaping @Sendable (AbsolutePath) async throws -> Result
) throws -> Task<Result, Error> {
    try withTemporaryDirectory(fileSystem: fileSystem, dir: dir, prefix: prefix) { path, cleanup in
        defer { if removeTreeOnDeinit { cleanup(path) } }
        return try await body(path)
    }
}

private func createTemporaryDirectory(
    fileSystem: FileSystem,
    dir: AbsolutePath?,
    prefix: String
) throws -> AbsolutePath {
    // This random generation is needed so that
    // it is more or less equal to generation using `mkdtemp` function
    let letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"

    let randomSuffix = String((0 ..< 6).map { _ in letters.randomElement()! })

    let tempDirectory = try dir ?? fileSystem.tempDirectory
    guard fileSystem.isDirectory(tempDirectory) else {
        throw TempFileError.couldNotFindTmpDir(tempDirectory.pathString)
    }

    // Construct path to the temporary directory.
    let templatePath = try AbsolutePath(validating: prefix + ".\(randomSuffix)", relativeTo: tempDirectory)

    try fileSystem.createDirectory(templatePath, recursive: true)
    return templatePath
}