Files
wehub-resource-sync 869b84f27c
Build / build (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:29:30 +08:00

67 lines
3.2 KiB
Swift

import Foundation
/// Allocated-size calculation that works for both files and directories.
///
/// `URLResourceValues.totalFileAllocatedSize` does **not** recurse: on a
/// directory URL it returns only the directory inode's own allocation
/// (~96 bytes to a few KB on APFS), not the sum of the bundle's contents.
/// Reading it directly on an `.app` bundle or a support folder is what made
/// items display as a handful of bytes. For directories we enumerate and sum
/// the regular files instead.
enum FileSizeCalculator {
private static let fileManager = FileManager.default
/// On-disk allocated size of `url`. Recurses into directories.
/// Returns `nil` if the item can't be read at all.
static func size(of url: URL) -> Int64? {
let values = try? url.resourceValues(forKeys: [.isDirectoryKey, .isSymbolicLinkKey])
// Treat a symlink as a file (size of the link itself), never recursing
// into its target. `.isDirectoryKey` resolves symlinks, so without this
// guard a top-level symlink-to-directory would be walked as the target's
// full tree — inflating the size, escaping the item's real footprint,
// and mismatching deletion (removeItem deletes only the link). Check
// isSymbolicLink first so the directory branch only sees real dirs.
if values?.isSymbolicLink != true, values?.isDirectory == true {
return directorySize(of: url)
}
return fileSize(of: url)
}
private static func fileSize(of url: URL) -> Int64? {
if let values = try? url.resourceValues(forKeys: [.totalFileAllocatedSizeKey]),
let size = values.totalFileAllocatedSize {
return Int64(size)
}
if let values = try? url.resourceValues(forKeys: [.fileAllocatedSizeKey]),
let size = values.fileAllocatedSize {
return Int64(size)
}
guard let attrs = try? fileManager.attributesOfItem(atPath: url.path),
let size = (attrs[.size] as? NSNumber)?.int64Value else { return nil }
return size
}
private static func directorySize(of url: URL) -> Int64 {
guard let enumerator = fileManager.enumerator(
at: url,
includingPropertiesForKeys: [.totalFileAllocatedSizeKey, .fileAllocatedSizeKey, .isRegularFileKey, .isSymbolicLinkKey],
options: [.skipsHiddenFiles]
) else { return 0 }
var total: Int64 = 0
for case let fileURL as URL in enumerator {
guard let values = try? fileURL.resourceValues(forKeys: [.totalFileAllocatedSizeKey, .fileAllocatedSizeKey, .isRegularFileKey, .isSymbolicLinkKey]) else { continue }
// Skip symlinks so we don't double-count or follow links that
// escape the directory. Only sum regular-file payload.
if values.isSymbolicLink == true { continue }
guard values.isRegularFile == true else { continue }
if let allocated = values.totalFileAllocatedSize {
total += Int64(allocated)
} else if let allocated = values.fileAllocatedSize {
total += Int64(allocated)
}
}
return total
}
}