Skip to content

Instantly share code, notes, and snippets.

@leogdion
Last active September 23, 2023 18:41
Show Gist options
  • Save leogdion/91fa0cc2601caf4d279f99b3d7eb5a8a to your computer and use it in GitHub Desktop.
Save leogdion/91fa0cc2601caf4d279f99b3d7eb5a8a to your computer and use it in GitHub Desktop.
//
// FileManager+ReduceDirectory.swift
// Copyright (c) 2023 BrightDigit.
//
import Foundation
private extension Error where Self == NSError {
static func fileNotFound(at url: URL) -> NSError {
NSError(
domain: NSCocoaErrorDomain,
code: NSFileNoSuchFileError,
userInfo: [
NSFilePathErrorKey: url.path,
NSUnderlyingErrorKey: POSIXError(.ENOENT)
]
)
}
}
extension FileManager {
func reduce<T: Sendable>(
_ key: URLResourceKey,
directoryAt url: URL,
_ initial: T,
_ nextPartialResult: @escaping (T, T) async throws -> T
) async throws -> T {
try await self.reduce(key, directoryAt: url) { sequence in
try await sequence.reduce(initial, nextPartialResult)
}
}
func reduce<T: Sendable>(
_ key: URLResourceKey,
directoryAt url: URL,
by reduce: @escaping (ThrowingTaskGroup<T, Error>) async throws -> T
) async throws -> T {
try await self.reduce([key], directoryAt: url, fromValues: { values in
values.allValues[key] as! T
}, by: reduce)
}
func reduce<T: Sendable>(
_ keys: [URLResourceKey],
directoryAt url: URL,
fromValues valuesFrom: @escaping @Sendable (URLResourceValues) -> T,
_ initial: T,
_ nextPartialResult: @escaping (T, T) async throws -> T
) async throws -> T {
try await self.reduce(keys, directoryAt: url, fromValues: valuesFrom) { sequence in
try await sequence.reduce(initial, nextPartialResult)
}
}
func reduce<T: Sendable>(
_ keys: [URLResourceKey],
directoryAt url: URL,
fromValues valuesFrom: @escaping (URLResourceValues) -> T,
by reduce: @escaping (ThrowingTaskGroup<T, Error>) async throws -> T
) async throws -> T {
let allKeys: Set<URLResourceKey> = Set([.isDirectoryKey, .isRegularFileKey] + keys)
guard let enumerator = enumerator(at: url, includingPropertiesForKeys: Array(allKeys)) else {
throw .fileNotFound(at: url)
}
return try await withThrowingTaskGroup(of: T.self) { taskGroup in
for case let url as URL in enumerator {
taskGroup.addTask {
try await Task {
let values = try url.resourceValues(forKeys: allKeys)
if values.isDirectory == true {
return try await self.reduce(
keys,
directoryAt: url,
fromValues: valuesFrom,
by: reduce
)
} else {
return valuesFrom(values)
}
}.value
}
}
return try await reduce(taskGroup)
}
}
func accumulateSizeFromDirectory(at url: URL) async throws -> Int {
try await self.reduce(.fileSizeKey, directoryAt: url, 0, +)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment