5

I am currently building a mac app which in the future should be able to kill and start apps on OS X.

For that to be possible, I need to find a way to get a list of all the installed applications on the machine.

I already did quite a bit of research and have decided to use Spotlight with NSMetadataQuery to be able to get the list.

I was able to find this post about the mentioned topic and started to implement the functionality in Swift 2.2 (the weapon of choice for the project). With a bit of translation I was able to make it work and the code now successfully builds and runs. During runtime, however, I seem to be having a problem with the query itself:

<NSMetadataQuery: 0x6080000e3880> is being deallocated without first calling -stopQuery. To avoid race conditions, you should first invoke -stopQuery on the run loop on which -startQuery was called

This is the code I am currently using.

    public func doSpotlightQuery() {
    query = NSMetadataQuery()
    let predicate = NSPredicate(format: "kMDItemKind ==[c] %@", "Application")
    let defaultNotificationCenter = NSNotificationCenter()
    defaultNotificationCenter.addObserver(self, selector: #selector(queryDidFinish(_:)), name: NSMetadataQueryDidFinishGatheringNotification, object: nil)
    query.predicate = predicate
    query.startQuery()
}

public func queryDidFinish(notification: NSNotification) {
    for i in 0 ... query.resultCount {
        print(query.resultAtIndex(i).valueForAttribute(kMDItemDisplayName as String))
    }
}

Testing the

mdfind "kMDItemKind == 'Application'"

command (with variations of all kind) in the terminal of my mac didn't give me any results either which brings me to my question:

Did I set up the query in a wrong way or does this command not work in 'El Capitan'?

Can someone please help me find my mistake? I'd sure love to finally make this work!

Community
  • 1
  • 1
bob_mosh
  • 267
  • 2
  • 17

2 Answers2

2

The dealloc message seems like the query is missing a strong reference.

var query: NSMetadataQuery? {
    willSet {
        if let query = self.query {
            query.stopQuery()
        }
    }
}

public func doSpotlightQuery() {
    query = NSMetadataQuery()
    let predicate = NSPredicate(format: "kMDItemKind ==[c] %@", "Application")
    let defaultNotificationCenter = NSNotificationCenter()
    defaultNotificationCenter.addObserver(self, selector: #selector(queryDidFinish(_:)), name: NSMetadataQueryDidFinishGatheringNotification, object: nil)
    query?.predicate = predicate
    query?.startQuery()
}

public func queryDidFinish(notification: NSNotification) {
    guard let query = notification.object as? NSMetadataQuery else {
        return
    }

    for i in 0 ... query.resultCount {
        print(query.resultAtIndex(i).valueForAttribute(kMDItemDisplayName as String))
    }
}

I'd suggest using a different predicate as kMDItemKind is a localized key according to John's comment here

so let predicate = NSPredicate(format: "kMDItemContentType == 'com.apple.application-bundle'") would work for what we're doing.

In swift 3 this could look like this:

var query: NSMetadataQuery? {
    willSet {
        if let query = self.query {
            query.stop()
        }
    }
}

public func doSpotlightQuery() {
    query = NSMetadataQuery()
    let predicate = NSPredicate(format: "kMDItemContentType == 'com.apple.application-bundle'")
    NotificationCenter.default.addObserver(self, selector: #selector(queryDidFinish(_:)), name: NSNotification.Name.NSMetadataQueryDidFinishGathering, object: nil)
    query?.predicate = predicate
    query?.start()
}

public func queryDidFinish(_ notification: NSNotification) {
    guard let query = notification.object as? NSMetadataQuery else {
        return
    }

    for result in query.results {
        guard let item = result as? NSMetadataItem else {
            print("Result was not an NSMetadataItem, \(result)")
            continue
        }
        print(item.value(forAttribute: kMDItemDisplayName as String))
    }
}
Community
  • 1
  • 1
0

Here is a solution that gets the contents of /applications and /applications/utilities and converts the contents to NSMetaDataItems.

public func getAllApplications() -> [NSMetadataItem] {
    let fileManager = FileManager()

    guard let applicationsFolderUrl = try? FileManager.default.url(for: .applicationDirectory, in: .localDomainMask, appropriateFor: nil, create: false) else { return [] }

    let applicationUrls = try! fileManager.contentsOfDirectory(at: applicationsFolderUrl , includingPropertiesForKeys: [], options: [FileManager.DirectoryEnumerationOptions.skipsPackageDescendants, FileManager.DirectoryEnumerationOptions.skipsSubdirectoryDescendants])

    guard let systemApplicationsFolderUrl = try? FileManager.default.url(for: .applicationDirectory, in: .systemDomainMask, appropriateFor: nil, create: false) else { return [] }

    let utilitiesFolderUrl = NSURL.init(string: "\(systemApplicationsFolderUrl.path)/Utilities") as! URL

    guard let utilitiesUrls = try? fileManager.contentsOfDirectory(at: utilitiesFolderUrl, includingPropertiesForKeys: [], options: [FileManager.DirectoryEnumerationOptions.skipsPackageDescendants, FileManager.DirectoryEnumerationOptions.skipsSubdirectoryDescendants]) else { return [] }

    let urls = applicationUrls + utilitiesUrls

    var applications = [NSMetadataItem]()

    for url in urls {
        print(url.path, fileManager.isExecutableFile(atPath: url.path))
        if fileManager.isExecutableFile(atPath: url.path) {
            guard let mdi = NSMetadataItem(url: url) else { continue }
            applications.append(mdi)
        }
    }

    for app in applications {
        print(app.value(forAttribute: kMDItemDisplayName as String))
    }
    return applications
}

(There is some clean up that can be done to it but I wrote it in a hurry)

rohaldb
  • 589
  • 7
  • 24
  • I wonder is it possible to retireve icon too? – user4150758 Aug 06 '21 at 19:51
  • 1
    It is. See an example here https://github.com/rohaldb/Summon/blob/52d24812b3bfbe2b0c111b822b643d93b2d6ca60/Summon/ApplicationSearcher.swift#L27 – rohaldb Aug 08 '21 at 00:13
  • Thank you it worked. I am wondering is there an easy way to identify what all meta information can be retrieved from each app? Example: App category, when was it downloaded, app size etc – user4150758 Aug 09 '21 at 09:32