Skip to content

Instantly share code, notes, and snippets.

@tmspzz
Last active August 27, 2024 20:29
Show Gist options
  • Save tmspzz/a75f589e6bd86aa2121618155cbdf827 to your computer and use it in GitHub Desktop.
Save tmspzz/a75f589e6bd86aa2121618155cbdf827 to your computer and use it in GitHub Desktop.
A method to calculate the accumulated size of a directory on the volume in bytes.
public extension FileManager {
/// This method calculates the accumulated size of a directory on the volume in bytes.
///
/// As there's no simple way to get this information from the file system it 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(atUrl url: URL) throws -> UInt64 {
// We'll sum up content size here:
var accumulatedSize: UInt64 = 0
// prefetching some properties during traversal will speed up things a bit.
let prefetchedProperties = [
URLResourceKey.isRegularFileKey
, URLResourceKey.fileAllocatedSizeKey
, URLResourceKey.totalFileAllocatedSizeKey
]
// The error handler simply signals errors to outside code.
var errorDidOccur: Error?
let errorHandler: (URL, Error) -> Bool = { _, error in
errorDidOccur = error
return false
}
// We have to enumerate all directory contents, including subdirectories.
let enumerator = self.enumerator(at: url,
includingPropertiesForKeys: prefetchedProperties,
options: FileManager.DirectoryEnumerationOptions.init(rawValue: 0),
errorHandler: errorHandler)
// Start the traversal:
while let contentURL = (enumerator?.nextObject() as? URL) {
// Bail out on errors from the errorHandler.
if let error = errorDidOccur { throw error }
// Get the type of this item, making sure we only sum up sizes of regular files.
let resourceValues = try contentItemURL.resourceValues(forKeys: [.isRegularFileKey, .totalFileAllocatedSizeKey, .fileAllocatedSizeKey])
guard resourceValues.isRegularFile ?? false else {
continue
}
// 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.
var fileSize = resourceValues.fileSize
// In case the value is unavailable we use the fallback value (excluding meta data and compression)
// This value should always be available.
fileSize = fileSize ?? resourceValues.totalFileAllocatedSize
// We're good, add up the value.
accumulatedSize += UInt64(fileSize ?? 0)
}
// Bail out on errors from the errorHandler.
if let error = errorDidOccur { throw error }
// We finally got it.
return accumulatedSize
}
}
@FlixMa
Copy link

FlixMa commented Oct 7, 2017

I think you swapped the keys by mistake.
On Line 54 it should be var fileSize = resourceValues.totalFileAllocatedSize
and therefore on line 58 fileSize = fileSize ?? resourceValues.fileAllocatedSize.

@bendodson
Copy link

Also, line 46 should be contentURL not contentItemURL

@Coeur
Copy link

Coeur commented Aug 24, 2018

@yo1995
Copy link

yo1995 commented Aug 27, 2024

A simpler version without error handling or edge case handling.

/// Calculates the size of a directory and all its contents.
/// - Parameter url: The directory's URL.
/// - Returns: The total size in bytes.
func sizeOfDirectory(at url: URL) -> Int? {
    guard let enumerator = enumerator(at: url, includingPropertiesForKeys: [.fileSizeKey]) else { return nil }
    var size = 0
    for case let fileURL as URL in enumerator {
        guard let fileSize = try? fileURL.resourceValues(forKeys: [.fileSizeKey]).fileSize else {
            continue
        }
        size += fileSize
    }
    return size
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment