Scripting in Swift: Checking free disk space

When writing even quite large files, we often leave it to standard error handling to cope with there being insufficient free space on the selected volume. I do this in both Consolation 3 and WoodPile when creating logarchives, which are frequently over 700 MB in size. It’s far more helpful to the user to perform a quick initial check that they’re not in danger of running short of disk space.

Here’s some example code for doing this in versions of macOS from El Capitan (and before) to High Sierra, taken from my tool VolSizes.

For my quick lunchtime hack of VolSizes, I wanted to get all the ‘official’ estimates of free volume capacity for recent versions of OS X/macOS. I didn’t realise it at the time, but this is one task for which Apple provides example Swift code. My code below uses similar calling structures, so although it may not be particularly idiomatic, and seems rather pedestrian, I hope you will find it useful.

First, set the path constants, get the default File Manager, and write the current date to output:
let volPath = "/Volumes"
let volFullPath = volPath + "/"
let fm = FileManager.default
let theDate: Date = Date()
appendOutputText(string: ("Checked by VolSizes 0.1a1 at: " + theDate.description + "\n"))

Get a shallow listing of the /Volumes folder, giving us a list of mounted volumes. This goes in a try … catch … structure as it can throw an error:
do {
let theVols = try fm.contentsOfDirectory(atPath: volPath)

Iterate through the array of mounted volumes, appending the start of the path to make a full path in each case, and obtain their URL one at a time:
for item in theVols {
let theURL = URL.init(fileURLWithPath: volFullPath + item)

Now the start of the code which you can use for a general check of free capacity. As a first step with each URL, check whether it supports the volume size keys which we require. Note that this can throw, so enter a nested try … throw … structure:
do {
let theVolSizeAvail = try theURL.resourceValues(forKeys: [.volumeSupportsVolumeSizesKey])
if let theVolSizeIsAvail = theVolSizeAvail.volumeSupportsVolumeSizes {
if theVolSizeIsAvail {

Oddly, although the macOS interface provides this volume key, Apple’s example code doesn’t use it to check whether the other keys are available. I think it’s wise to do so.

Here we have to test which version of macOS is running, to determine whether to make the two extra calls added to 10.13:
if #available(OSX 10.13, *) {

For 10.13, get the resource values for selected keys:
let theRes = try theURL.resourceValues(forKeys [.volumeAvailableCapacityKey, .volumeTotalCapacityKey, .volumeAvailableCapacityForImportantUsageKey, .volumeAvailableCapacityForOpportunisticUsageKey])

The code then runs through a sequence of reading each of the volume size keys in turn. Although it has already checked that they should be available, there is still a chance that the call will return nil, so each is framed in a conditional. appendOutputString here just adds the string to the output window contents.
if let theCap = theRes.volumeAvailableCapacity {
appendOutputText(string: "Free space on \(theURL.path) is \(theCap) bytes")
if let theTotal = theRes.volumeTotalCapacity {
appendOutputText(string: " out of total \(theTotal), used space \((theTotal - theCap)), ")
} }
if let theImpFree = theRes.volumeAvailableCapacityForImportantUsage {
appendOutputText(string: "\(theImpFree) available for important usage, ")
}
if let theNonimpFree = theRes.volumeAvailableCapacityForOpportunisticUsage {
appendOutputText(string: "\(theNonimpFree) available for opportunistic usage.\n")
} } else {

Now the same for the two values we obtain from macOS prior to 10.13:
let theRes = try theURL.resourceValues(forKeys: [.volumeAvailableCapacityKey, .volumeTotalCapacityKey])
if let theCap = theRes.volumeAvailableCapacity {
appendOutputText(string: "Free space on \(theURL.path) is \(theCap) bytes")
if let theTotal = theRes.volumeTotalCapacity {
appendOutputText(string: " out of total \(theTotal), used space \((theTotal - theCap)).\n")
} } } } }

In this case, the catches just write error text out to the window contents. You may wish to get the error code and report that in more detail.
} catch {
appendOutputText(string: "Check failed for \(theURL.path).\n")
} }
} catch {
appendOutputText(string: "Check failed altogether.\n")
}

Here it is, laid out prettily:

volsizes04

I’m still at a loss, though, as to which of the estimates available in 10.13 you should use. You could save yourself even worrying about that decision if you keep to volumeAvailableCapacityKey, of course, but that appears in neither of Apple’s recommendations. Perhaps at this stage, when warning the user that there might be a problem, it is generally safer to use the smaller volumeAvailableCapacityForOpportunisticUsageKey in 10.13.