mirror of
https://github.com/xtool-org/xtool.git
synced 2026-02-04 11:53:30 +01:00
- We now have a single tmpdir "root" that can be recreated at launch to clean up old stragglers - The location of this tmpdir root can be controlled by `XTL_TMPDIR` or `TMPDIR`. In general the path is `$TMPDIR/sh.xtool`. With this change it should be possible to `export XTL_TMPDIR=/var/tmp` if `/tmp` doesn't have enough space, which fixes #23.
98 lines
3.1 KiB
Swift
98 lines
3.1 KiB
Swift
import Foundation
|
|
|
|
package struct TemporaryDirectory: ~Copyable {
|
|
private static let debugTmp = ProcessInfo.processInfo.environment["XTL_DEBUG_TMP"] != nil
|
|
|
|
private var shouldDelete: Bool
|
|
package let url: URL
|
|
|
|
/// Prepares a fresh tmpdir root.
|
|
///
|
|
/// Optional, but try calling this at launch to clean up old resources.
|
|
package static func prepare() {
|
|
_ = TemporaryDirectoryRoot.shared
|
|
}
|
|
|
|
/// Creates a temporary directory where `lastPathComponent` is exactly `name`.
|
|
///
|
|
/// The directory is deleted on deinit or (if the object never deinits) on next launch.
|
|
/// To save the contents, move them elsewhere with ``persist(at:)``.
|
|
package init(name: String) throws {
|
|
do {
|
|
let basename = name.replacingOccurrences(of: ".", with: "_")
|
|
self.url = try TemporaryDirectoryRoot.shared.url
|
|
// ensures uniqueness
|
|
.appendingPathComponent("tmp-\(basename)-\(UUID().uuidString)")
|
|
.appendingPathComponent(name, isDirectory: true)
|
|
self.shouldDelete = true
|
|
} catch {
|
|
// non-copyable types can't be partially initialized so we need a stub value
|
|
self.url = URL(fileURLWithPath: "")
|
|
self.shouldDelete = false
|
|
throw error
|
|
}
|
|
try FileManager.default.createDirectory(at: url, withIntermediateDirectories: true)
|
|
|
|
if Self.debugTmp {
|
|
stderrPrint("Created TemporaryDirectory: \(url.path)")
|
|
}
|
|
}
|
|
|
|
private func _delete() {
|
|
guard !Self.debugTmp else { return }
|
|
try? FileManager.default.removeItem(at: url)
|
|
}
|
|
|
|
package consuming func persist(at location: URL) throws {
|
|
try FileManager.default.moveItem(at: url, to: location)
|
|
// we do this after moving, so that if the move fails we clean up
|
|
shouldDelete = false
|
|
}
|
|
|
|
deinit {
|
|
if shouldDelete { _delete() }
|
|
}
|
|
}
|
|
|
|
private struct TemporaryDirectoryRoot {
|
|
static let shared = TemporaryDirectoryRoot()
|
|
|
|
private let _url: Result<URL, Errors>
|
|
var url: URL {
|
|
get throws(Errors) {
|
|
try _url.get()
|
|
}
|
|
}
|
|
|
|
private init() {
|
|
let base: URL
|
|
let env = ProcessInfo.processInfo.environment
|
|
if let tmpdir = env["XTL_TMPDIR"] ?? env["TMPDIR"] {
|
|
base = URL(fileURLWithPath: tmpdir)
|
|
} else {
|
|
base = FileManager.default.temporaryDirectory
|
|
}
|
|
|
|
let url = base.appendingPathComponent("sh.xtool")
|
|
try? FileManager.default.removeItem(at: url)
|
|
do {
|
|
try FileManager.default.createDirectory(at: url, withIntermediateDirectories: true)
|
|
} catch {
|
|
self._url = .failure(Errors.tmpdirCreationFailed(url, error))
|
|
return
|
|
}
|
|
self._url = .success(url)
|
|
}
|
|
|
|
enum Errors: Error, CustomStringConvertible {
|
|
case tmpdirCreationFailed(URL, Error)
|
|
|
|
var description: String {
|
|
switch self {
|
|
case let .tmpdirCreationFailed(url, error):
|
|
"Could not create temporary directory at '\(url.path)': \(error)"
|
|
}
|
|
}
|
|
}
|
|
}
|