Skip to content

Instantly share code, notes, and snippets.

@victorBaro
Last active July 3, 2022 14:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save victorBaro/a01d1c5b0c58f37dd14ac9ec2e1f6092 to your computer and use it in GitHub Desktop.
Save victorBaro/a01d1c5b0c58f37dd14ac9ec2e1f6092 to your computer and use it in GitHub Desktop.
Calculate total size of a given directory URL. Ready to paste in a Playground. Code uses modern swift concurrency async/await. Part of the code based on the following SO post: https://stackoverflow.com/a/32814710
import UIKit
enum FolderSizeCalculatorError: Error {
case urlUnreachableOrNotDirectory
case failToEnumerateDirectoryContent
case failToGenerateString
}
class FolderSizeCalculator {
private let fileManager: FileManager
private static let byteCountFormatter: ByteCountFormatter = {
let formatter = ByteCountFormatter()
formatter.countStyle = .file
return formatter
}()
init(fileManager: FileManager = .default) {
self.fileManager = fileManager
}
/// Returns formatted string for total size on disk for a given directory URL
/// - Parameters:
/// - url: top directory URL
/// - includingSubfolders: if true, all subfolders will be included
/// - Returns: total byte count, formatted (i.e. "8.7 MB")
func formattedSizeOnDisk(atURLDirectory url: URL,
includingSubfolders: Bool = true) async throws -> String {
let size = try await sizeOnDisk(atURLDirectory: url, includingSubfolders: includingSubfolders)
guard let byteCount = FolderSizeCalculator.byteCountFormatter.string(for: size) else {
throw FolderSizeCalculatorError.failToGenerateString
}
return byteCount
}
/// Returns total size on disk for a given directory URL
/// Note: `totalFileAllocatedSize()` is available for single files.
/// - Parameters:
/// - url: top directory URL
/// - includingSubfolders: if true, all subfolders will be included
/// - Returns: total byte count
func sizeOnDisk(atURLDirectory url: URL,
includingSubfolders: Bool = true) async throws -> Int {
guard try url.isDirectoryAndReachable() else {
throw FolderSizeCalculatorError.urlUnreachableOrNotDirectory
}
return try await withCheckedThrowingContinuation { continuation in
var fileURLs = [URL]()
do {
if includingSubfolders {
// Enumerate directories and sub-directories
guard let urls = fileManager.enumerator(at: url, includingPropertiesForKeys: nil)?.allObjects as? [URL] else {
throw FolderSizeCalculatorError.failToEnumerateDirectoryContent
}
fileURLs = urls
} else {
// Only contents of given directory
fileURLs = try fileManager.contentsOfDirectory(at: url, includingPropertiesForKeys: nil)
}
let totalBytes = try fileURLs.reduce(0) { total, url in
try url.totalFileAllocatedSize() + total
}
continuation.resume(with: .success(totalBytes))
} catch {
continuation.resume(with: .failure(error))
}
}
}
}
extension URL {
/// check if the URL is a directory and if it is reachable
func isDirectoryAndReachable() throws -> Bool {
guard try resourceValues(forKeys: [.isDirectoryKey]).isDirectory == true else {
return false
}
return try checkResourceIsReachable()
}
func totalFileAllocatedSize() throws -> Int {
try resourceValues(forKeys: [.totalFileAllocatedSizeKey]).totalFileAllocatedSize ?? 0
}
}
// USAGE
Task {
let documentsDirectory = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
print("Add documents to the following folder: \n", documentsDirectory)
let calculator = FolderSizeCalculator()
let size = try! await calculator.formattedSizeOnDisk(atURLDirectory: documentsDirectory, includingSubfolders: true)
print("Total formatted size: ", size)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment