0

How do you determine the Total, Available, and Used space of a USB drive in Swift (MacOS)?

There are several good posts about this (example: How to get the Total Disk Space and Free Disk space using AttributesOfFileSystemForpaths in swift 2.0 and https://developer.apple.com/documentation/foundation/urlresourcekey/checking_volume_storage_capacity), but they all just get the space of the operating system drive and not of a USB drive.

For example, I have a 64GB USB drive with volume name "myusb", so the MacOS mounts the drive at /Volumes/myusb. Finder shows the total space of the USB drive as 62.91GB, available as 62.29GB, and used as 625,999,872 bytes.

The trouble seems to be that when I give the path of the USB drive, since it obviously is part of the main / path, it is returning the information for / which is my OS drive, not the USB drive.

Here is what I am doing when trying to determine free space of the USB drive and which returns a value of 292298430687 bytes (which is the available space of my OS drive, not USB drive):

/**
   Returns URL of root of USB drive - i.e. /Volumes/myusb
   Uses bundleURL as .app file being executed is located on the USB drive
*/
static func getRootURL() -> URL {
    let bundlePath = Bundle.main.bundleURL
    let bundlePathComponents = bundlePath.pathComponents
        
    let destinationRootPathURL = URL(fileURLWithPath: bundlePathComponents[0])
        .appendingPathComponent(bundlePathComponents[1])
        .appendingPathComponent(bundlePathComponents[2])
        
    return destinationRootPathURL
}

/**
   returns free space of USB drive
*/
func getAvailableSpaceInBytes() -> Int64 {
    if #available(OSX 10.13, *) {
        if let freeSpace = try? getRootURL().resourceValues(forKeys: [URLResourceKey.volumeAvailableCapacityForImportantUsageKey])
                .volumeAvailableCapacityForImportantUsage {
                return freeSpace
        }
    } else {
        // Fallback on earlier versions
        guard let systemAttributes = try? FileManager.default.attributesOfFileSystem(forPath: getRootURL().path),
              let freeSize = systemAttributes[FileAttributeKey.systemFreeSize] as? NSNumber
        else {
            // something failed so return nil
            return 0
        }
            
        return freeSize.int64Value
    }
        
        return 0
    }
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
Monty
  • 69
  • 1
  • 7

2 Answers2

1

You can use FileManager's mountedVolumeURLs method to get all mounted volumes and get the volumeAvailableCapacityForImportantUsage resource key/value from it:


extension FileManager {
    static var mountedVolumes: [URL] {
        (FileManager.default.mountedVolumeURLs(includingResourceValuesForKeys: nil) ?? []).filter({$0.path.hasPrefix("/Volumes/")})
    }
}

extension URL {
    var volumeTotalCapacity: Int? {
        (try? resourceValues(forKeys: [.volumeTotalCapacityKey]))?.volumeTotalCapacity
    }
    var volumeAvailableCapacityForImportantUsage: Int64? {
        (try? resourceValues(forKeys: [.volumeAvailableCapacityForImportantUsageKey]))?.volumeAvailableCapacityForImportantUsage
    }
    var name: String? {
        (try? resourceValues(forKeys: [.nameKey]))?.name
    }
    
}

Usage:

for url in FileManager.mountedVolumes {
    print(url.name ?? "Untitled")
    print("Capacity:", url.volumeTotalCapacity ?? "nil")
    print("Available:", url.volumeAvailableCapacityForImportantUsage ?? "nil")
    print("Used:", (try? url.sizeOnDisk()) ?? "nil") // check the other link below
}

For a 16GB USB drive with BigSur installer the code above will print

Install macOS Big Sur
Capacity: 15180193792
Available: 2232998976
Used: 12.93 GB on disk


To get the used space of a volume "sizeOnDisk" you can check this post

Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
0

Instead of filtering on the /Volumes string, it is possible to use some keys provided by the system to check for external drive.

First, with FileManager's mountedVolumeURLs method, add the option skipHiddenVolumes, so that no internal volumes are returned. But you still get /.

Second, get resource information on each url for keys like isVolume, volumeIsBrowsable and volumeIsRemovable. If all are true, you most likely have your USB key/drive.

Something like

let urls = FileManager.default.mountedVolumeURLs(includingResourceValuesForKeys: nil, options: [.skipHiddenVolumes]) ?? []

for url in urls {
    guard let resourceInformation = try? url.resourceValues(forKeys: [.isVolumeKey, .volumeIsBrowsableKey, .volumeIsRemovableKey]) else { continue }
    guard let isVolume = resourceInformation.isVolume, let volumeIsBrowsable = resourceInformation.volumeIsBrowsable, let volumeIsRemovable = resourceInformation.volumeIsRemovable else { continue }

    if isVolume && volumeIsBrowsable && volumeIsEjectable {
        // do something with the URL
    }
}
GregOriol
  • 142
  • 2
  • 5