Get mounted volumes list with Swift? - macos

Does anyone know how to get a list of all removable volumes mounted with Swift?
I've already tried this, but it return a list of all files and subfolders of external drivers:
let filemanager:NSFileManager = NSFileManager()
let files = filemanager.enumeratorAtPath("/Volumes")
while let file = files?.nextObject() {
println(file)
menu.addItem(NSMenuItem(title: file as! String, action: Selector(""), keyEquivalent: ""))
}

This prints the list of all mounted volumes:
let filemanager = NSFileManager()
let keys = [NSURLVolumeNameKey, NSURLVolumeIsRemovableKey, NSURLVolumeIsEjectableKey]
let paths = filemanager.mountedVolumeURLsIncludingResourceValuesForKeys(keys, options: nil)
if let urls = paths as? [NSURL] {
for url in urls {
println(url)
}
}
You can of course filter to get only the paths inside the "Volumes" directory:
let filemanager = NSFileManager()
let keys = [NSURLVolumeNameKey, NSURLVolumeIsRemovableKey, NSURLVolumeIsEjectableKey]
let paths = filemanager.mountedVolumeURLsIncludingResourceValuesForKeys(keys, options: nil)
if let urls = paths as? [NSURL] {
for url in urls {
if url.relativePath?.pathComponents.count > 1 {
if url.relativePath?.pathComponents[1] == "Volumes" {
println(url)
}
}
}
}
And with Swift 2 there's two differences: pass [] instead of nil for the filemanager's options, and there's no need to cast the array of NSURLs:
let filemanager = NSFileManager()
let keys = [NSURLVolumeNameKey, NSURLVolumeIsRemovableKey, NSURLVolumeIsEjectableKey]
let paths = filemanager.mountedVolumeURLsIncludingResourceValuesForKeys(keys, options: [])
if let urls = paths {
for url in urls {
if url.relativePath?.pathComponents.count > 1 {
if url.relativePath?.pathComponents[1] == "Volumes" {
print(url)
}
}
}
}
Update for Swift 2.1
let keys = [NSURLVolumeNameKey, NSURLVolumeIsRemovableKey, NSURLVolumeIsEjectableKey]
let paths = NSFileManager().mountedVolumeURLsIncludingResourceValuesForKeys(keys, options: [])
if let urls = paths {
for url in urls {
if let components = url.pathComponents
where components.count > 1
&& components[1] == "Volumes" {
print(url)
}
}
}
Update for Swift 3
let keys: [URLResourceKey] = [.volumeNameKey, .volumeIsRemovableKey, .volumeIsEjectableKey]
let paths = FileManager().mountedVolumeURLs(includingResourceValuesForKeys: keys, options: [])
if let urls = paths {
for url in urls {
let components = url.pathComponents
if components.count > 1
&& components[1] == "Volumes"
{
print(url)
}
}
}

On Unix systems a filesystem object with a system file number of 2 is a mount, regardless of a remote (nfs, smb, afp) or a local mount.
Here's an example:
let path = "/System/Volumes/Preboot"
let systemAttributes = try FileManager.default.attributesOfItem(atPath: String(describing: path))
if let fileSystemFileNumber = systemAttributes[.systemFileNumber] as? NSNumber {
print("System File Number: \(fileSystemFileNumber)")
}
So maybe this could be a short way to find mounts

let keys: [URLResourceKey] = [
.volumeNameKey,
.volumeIsRemovableKey,
.volumeIsEjectableKey,
.volumeAvailableCapacityKey,
.volumeTotalCapacityKey,
.volumeUUIDStringKey,
.volumeIsBrowsableKey,
.volumeIsLocalKey,
.isVolumeKey,
]
let manager = FileManager.default.mountedVolumeURLs(includingResourceValuesForKeys: keys)
if let urls = manager {
print(urls)
}
This code is working fine for MacOS, however on the iOS side it always returns nil. Is there any known workaround?
Thanks!

Related

How to find the location of file under a directory tree?

Given a filename, say foo.txt and a base directory URL, say ~/bar/directory what's the best way to find the subdirectory for the first occurrence of the file?
Is there an existing API in Foundation or AppKit?
Is, for example, a breadth-first search through the subtree the way to go? How would that look?
Another way?
One option is to manually enumerate the directories:
func manualSearchFile(withName name: String, in path: String) {
func search(url: URL) {
do {
let contents = try FileManager.default.contentsOfDirectory(at: url, includingPropertiesForKeys: [.nameKey, .pathKey, .isDirectoryKey], options: [])
try contents.forEach {
let metadata = try $0.resourceValues(forKeys: [.nameKey, .pathKey, .isDirectoryKey])
if metadata.name == name {
print("Manual Found: \(metadata.path ?? "unknown path")")
}
if metadata.isDirectory == true {
search(url: $0)
}
}
} catch {
print(error)
}
}
search(url: URL(fileURLWithPath: path))
}
manualSearchFile(withName: "foo.txt", in: "/bar/directory")
Another option is to use Spotlight, which is faster but only works for paths that are indexed. Many system directories are excluded and users can exclude even more in System Preferences > Spotlight > Privacy.
var metadataQuery: NSMetadataQuery?
func spotlightSearchFile(withName name: String, in path: String) {
NotificationCenter.default.addObserver(forName: .NSMetadataQueryDidFinishGathering, object: nil, queue: nil) {
guard let query = $0.object as? NSMetadataQuery else { return }
query.enumerateResults { (result, index, cancel) in
let item = result as? NSMetadataItem
let path = item?.value(forAttribute: NSMetadataItemPathKey) as? String
print("Spotlight Found: \(path ?? "unknown path")")
}
}
metadataQuery = NSMetadataQuery()
metadataQuery?.searchScopes = [path]
metadataQuery?.predicate = NSPredicate(format: "%K like[cd] %#", NSMetadataItemDisplayNameKey, name)
metadataQuery?.start()
}
spotlightSearchFile(withName: "foo.txt", in: "/bar/directory")
For more information about the query syntax see the Comparison of NSPredicate and Spotlight Query Strings.
In the old days we used to have more options with FSCatalogSearch and FSGetCatalogInfoBulk. But these are no longer available, AFAIK.

Sandbox & WKWebView loadFileURL(_, allowingReadAccessTo:) Inconsistency

Using the new shiny WKWebView and sandbox on os/x, require some intervening reset or clear as subsequent calls to load a file URL will be ignored; this is somewhat related to an earlier question on WKWebView loadFileURL works only once -
ios there, here on os/X I do
if loadURL.isFileURL {
webView.loadFileURL(loadURL, allowingReadAccessTo: loadURL)
}
else
{
webView.load(URLRequest(url: loadURL))
}
I've tried to pass loadURL.deletingLastPathComponent() as the second arg but then all breaks - no file URLs get loaded, nor does using the user's home path, or the entire root 'file:///', nor the 'temporary' exception re: absolute file paths. Finally, trying an intervening topLoading() has no affect.
The only solution (yuck) to get a subsequent file URL loaded is to first load a non file URL!
It seems within a sandbox environment this has unintended consequences?
Well, this works but ugly - webView subclass function, as you cannot reuse a webView when a file url was previously loaded. This workaround will instantiate a new window/doc tossing the old - unless as a user preference they want to keep the old window (newWindows flag is true):
func loadNext(url: URL) {
let doc = self.window?.windowController?.document as! Document
let newWindows = UserSettings.createNewWindows.value
var fileURL = url
if !url.isFileURL {
if newWindows {
do
{
let next = try NSDocumentController.shared().openUntitledDocumentAndDisplay(true) as! Document
let oldWindow = self.window
let newWindow = next.windowControllers.first?.window
(newWindow?.contentView?.subviews.first as! MyWebView).load(URLRequest(url: url))
newWindow?.offsetFromWindow(oldWindow!)
}
catch let error {
NSApp.presentError(error)
Swift.print("Yoink, unable to create new url doc for (\(url))")
return
}
}
else
{
self.load(URLRequest(url: url))
}
}
if let origURL = (fileURL as NSURL).resolvedFinderAlias() {
fileURL = origURL
}
if appDelegate.isSandboxed() && !appDelegate.storeBookmark(url: fileURL) {
Swift.print("Yoink, unable to sandbox \(fileURL))")
return
}
if !(self.url?.isFileURL)! && !newWindows {
self.loadFileURL(fileURL, allowingReadAccessTo: fileURL)
doc.update(to: fileURL, ofType: fileURL.pathExtension)
return
}
// We need or want a new window; if need, remove the old afterward
do {
let next = try NSDocumentController.shared().openUntitledDocumentAndDisplay(true) as! Document
let oldWindow = doc.windowControllers.first?.window
let newWindow = next.windowControllers.first?.window
(newWindow?.contentView?.subviews.first as! MyWebView).loadFileURL(fileURL, allowingReadAccessTo: fileURL)
if newWindows {
newWindow?.offsetFromWindow(oldWindow!)
}
else
{
newWindow?.overlayWindow(oldWindow!)
oldWindow?.orderOut(self)
}
next.update(to: fileURL, ofType: fileURL.pathExtension)
}
catch let error
{
NSApp.presentError(error)
Swift.print("Yoink, unable to new doc (\(fileURL))")
}
}

Read & Write file tag in cocoa app OS X with swift or obj-c

Is there any way to read/write file tags without shell commands? Already tried NSFileManager and CGImageSource classes. No luck so far.
An NSURL object has a resource for key NSURLTagNamesKey. The value is an array of strings.
This Swift example reads the tags, adds the tag Foo and write the tags back.
let url = NSURL(fileURLWithPath: "/Path/to/file.ext")
var resource : AnyObject?
do {
try url.getResourceValue(&resource, forKey: NSURLTagNamesKey)
var tags : [String]
if resource == nil {
tags = [String]()
} else {
tags = resource as! [String]
}
print(tags)
tags += ["Foo"]
try url.setResourceValue(tags, forKey: NSURLTagNamesKey)
} catch let error as NSError {
print(error)
}
The Swift 3+ version is a bit different. In URL the tagNames property is get-only so it's necessary to bridge cast the URL to Foundation NSURL
var url = URL(fileURLWithPath: "/Path/to/file.ext")
do {
let resourceValues = try url.resourceValues(forKeys: [.tagNamesKey])
var tags : [String]
if let tagNames = resourceValues.tagNames {
tags = tagNames
} else {
tags = [String]()
}
tags += ["Foo"]
try (url as NSURL).setResourceValue(tags, forKey: .tagNamesKey)
} catch {
print(error)
}
#vadian's answer in Swift 4.0
('NSURLTagNamesKey' has been renamed to 'URLResourceKey.tagNamesKey')
let url = NSURL(fileURLWithPath: "/Path/to/file.ext")
var resource : AnyObject?
do {
try url.getResourceValue(&resource, forKey: URLResourceKey.tagNamesKey)
var tags : [String]
if resource == nil {
tags = [String]()
} else {
tags = resource as! [String]
}
print(tags)
tags += ["Foo"]
try url.setResourceValue(tags, forKey: URLResourceKey.tagNamesKey)
} catch let error as NSError {
print(error)
}

Cocoa/Swift: Loop through names of folder in path

I'm currently programming an os x application with swift, but I can't figure how to loop through or even get the names of all folders at a certain path. Maybe something with fm.enumeratorAtPath?
I use enumeratorAtURL. Here's some code that shows an example of how to print the directories in the user's home directory.
if let dirURL = NSURL(fileURLWithPath: NSHomeDirectory()) {
let keys = [NSURLIsDirectoryKey, NSURLLocalizedNameKey]
let fileManager = NSFileManager.defaultManager()
let enumerator = fileManager.enumeratorAtURL(
dirURL,
includingPropertiesForKeys: keys,
options: (NSDirectoryEnumerationOptions.SkipsPackageDescendants |
NSDirectoryEnumerationOptions.SkipsSubdirectoryDescendants |
NSDirectoryEnumerationOptions.SkipsHiddenFiles),
errorHandler: {(url, error) -> Bool in
return true
}
)
while let element = enumerator?.nextObject() as? NSURL {
var getter: AnyObject?
element.getResourceValue(&getter, forKey: NSURLIsDirectoryKey, error: nil)
let isDirectory = getter! as Bool
element.getResourceValue(&getter, forKey: NSURLLocalizedNameKey, error: nil)
let itemName = getter! as String
if isDirectory {
println("\(itemName) is a directory in \(dirURL.absoluteString)")
//do something with element here.
}
}
}

How do I get current network location name?

In system network preference there are some location names.How to get the current or active network location name and list of all network locations? I guess SystemConfiguration.framework supports this but i didn't get exactly which API to use.Thanks in advance for your answer. RegardsDevara Gudda
You can use SCPreferencesCreate to get the preferences, then SCNetworkSetCopyAll to get just the network locations. SCNetworkSetGetName will get the name of a location.
SCPreferencesRef prefs = SCPreferencesCreate(NULL, #"SystemConfiguration", NULL);
NSArray *locations = (NSArray *)SCNetworkSetCopyAll(prefs);
for (id item in locations) {
NSString *name = (NSString *)SCNetworkSetGetName((SCNetworkSetRef)item);
...
}
CFRelease(locations);
CFRelease(prefs);
Read "System Configuration Programming Guidelines" for more.
This is a swift 5.1 implementation to get list of all available network location
func getAvailabeNetworkLocation() -> [String]
{
var results = [String]()
var prefs : SCPreferences
prefs = SCPreferencesCreate (nil, "Softech" as CFString, nil)!
var sets : CFArray
sets = SCNetworkSetCopyAll (prefs)!
let count = CFArrayGetCount(sets) as Int
var newSet : SCNetworkSet? = nil
var bFound : Bool = false
for nIndex in 0..<count {
let mySet = CFArrayGetValueAtIndex (sets, nIndex)
let key = Unmanaged<SCNetworkSet>.fromOpaque(mySet!)
newSet = key.takeUnretainedValue()
let name : String = SCNetworkSetGetName(newSet!)! as String
print("Network location name: \(name)")
results.append(name)
}
return results
}

Resources