Last active
July 3, 2022 14:00
-
-
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
This file contains hidden or 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
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