Created
August 24, 2018 08:45
-
-
Save NikolaiRuhe/408cefb953c4bea15506a3f80a3e5b96 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// Copyright (c) 2016, 2018 Nikolai Ruhe. All rights reserved. | |
// | |
import Foundation | |
public extension FileManager { | |
/// Calculate the allocated size of a directory and all its contents on the volume. | |
/// | |
/// As there's no simple way to get this information from the file system the method | |
/// has to crawl the entire hierarchy, accumulating the overall sum on the way. | |
/// The resulting value is roughly equivalent with the amount of bytes | |
/// that would become available on the volume if the directory would be deleted. | |
/// | |
/// - note: There are a couple of oddities that are not taken into account (like symbolic links, meta data of | |
/// directories, hard links, ...). | |
public func allocatedSizeOfDirectory(at directoryURL: URL) throws -> UInt64 { | |
// The error handler simply stores the error and stops traversal | |
var enumeratorError: Error? = nil | |
func errorHandler(_: URL, error: Error) -> Bool { | |
enumeratorError = error | |
return false | |
} | |
// We have to enumerate all directory contents, including subdirectories. | |
let enumerator = self.enumerator(at: directoryURL, | |
includingPropertiesForKeys: Array(allocatedSizeResourceKeys), | |
options: [], | |
errorHandler: errorHandler)! | |
// We'll sum up content size here: | |
var accumulatedSize: UInt64 = 0 | |
// Perform the traversal. | |
for item in enumerator { | |
// Bail out on errors from the errorHandler. | |
if enumeratorError != nil { break } | |
// Add up individual file sizes. | |
let contentItemURL = item as! URL | |
accumulatedSize += try contentItemURL.regularFileAllocatedSize() | |
} | |
// Rethrow errors from errorHandler. | |
if let error = enumeratorError { throw error } | |
return accumulatedSize | |
} | |
} | |
fileprivate let allocatedSizeResourceKeys: Set<URLResourceKey> = [ | |
.isRegularFileKey, | |
.fileAllocatedSizeKey, | |
.totalFileAllocatedSizeKey, | |
] | |
fileprivate extension URL { | |
func regularFileAllocatedSize() throws -> UInt64 { | |
let resourceValues = try self.resourceValues(forKeys: allocatedSizeResourceKeys) | |
// We only look at regular files. | |
guard resourceValues.isRegularFile ?? false else { | |
return 0 | |
} | |
// To get the file's size we first try the most comprehensive value in terms of what | |
// the file may use on disk. This includes metadata, compression (on file system | |
// level) and block size. | |
// In case totalFileAllocatedSize is unavailable we use the fallback value (excluding | |
// meta data and compression) This value should always be available. | |
return UInt64(resourceValues.totalFileAllocatedSize ?? resourceValues.fileAllocatedSize ?? 0) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment