Limits and issues when passing file refs to apps (LSOpenURLsWithRole)

My app Find Any File has the option to pass the found items to a user-selectable app, so that the app can then post-process the found items.

The idea was that users should be able to write scripts in AppleScript or Automator, and then have FAF pass the found files to such scripts.

The current implementation uses the Mac function LSOpenURLsWithRole() for this task - I can specify the app to launch, and specify an array of file URLs that are to be passed to that app.

In small test cases, this works fine.

However, I only much later realized that there are several issues with this:

  • If Alias (or symlink) files are included in the file array, they apparently get resolved before being passed to the app. That’s not desired. But even worse: If the Alias destination does not exist, the LSOpenURLsWithRole returns with fnfErr (-43). Does someone know how to solve this, e.g. is there a hidden flag I could pass to LSOpenURLsWithRole in order to have it not resolve Aliases?

  • There is an arbitrary and variable limit of the number of files I can pass. If I call an AppleScript, I’ve seen the limit being once around 950, another time around 690 items. And the worst of it is that the rest of the files is simply ignored, i.e. the AppleScript only gets a subset of the items I pass, and I don’t learn that the rest has been dropped.

  • If I pass the same files to an Automator script, where I’d then process them via a “Run Shell Script” action ("Pass input as arguments) like this:

… then I even get fewer items processed (somewhere between 100 and 200) - and some items are even missing there, so if I pass 200, it finds 195, and if I pass 195, it finds 190. And I have no idea why. And sometimes, it only gets a single item.

Same when I use “Pass input: to stdin” and read them like this:

Any ideas how to solve all this, i.e. are there other OSX APIs that can do this better? I know of [NSWorkspace openURL:], but that one can (a) only open one at a time, and (b) does not let me specify an app I want to have the URLs passed to.

FAF could, of course, simply write the found item paths to a file, and then let the script pick that up, which will probably be my solution (well, rather a work-around). But I hope there’ll be some revelation that’ll solve this the way it’s supposed to be.

I mean, what about people trying to pass a 1000 files to another Script? Does that work, or is that failing the same way?

Actually, as it turns out in my testing, if I dynamically generate an Applescript in FAF like this:

property file_list : {}
tell application "..."
  open file_list
end

… and then compile it with NSAppleScriptMBS, and then create a [NSAppleEventDescriptorMBS listDescriptor] and add all the files to it, then it even works with 1200 item (haven’t tried more). And no errors with non-resolvable Aliases, either (though I haven’t checked yet whether those still get resolved).

I guess this also means that developers using AppleScript have not run into the limit I ran into because AppleScript handles this better than native calls. Go figure.

Well, this is surely not the first time where AppleScript came to the rescue when dedicated OSX API calls didn’t work right.

You could also try building the event in Objective-C, and dispatching it with AESend(). Something like:

NSAppleEventDescriptor *  fileList = [NSAppleEventDescriptor listDescriptor];
for (NSURL *file in URLs) {
     [fileList insertDescriptor:[NSAppleEventDescriptor descriptorWithDescriptorType:typeFileURL data:[[file absoluteString] dataUsingEncoding:NSUTF8StringEncoding]] atIndex:0];
}
NSAppleEventDescriptor * target = [NSAppleEventDescriptor descriptorWithApplicationURL:appURL];
NSAppleEventDescriptor * openEvent = [NSAppleEventDescriptor appleEventWithEventClass:kCoreEventClass eventID:kAEOpenDocuments targetDescriptor: target returnID:kAutoGenerateReturnID transactionID:kAnyTransactionID];
[openEvent setParamDescriptor:fileList forKeyword:keyDirectObject];
OSErr err = AESEnd(&([openEvent AEDesc]), NULL, kAENoReply, kAENormalPriority, kAEDefaultTimeout, NULL, NULL);

Edited: descriptorWithApplicationURL requires 10.11+, so you might need to use Carbon code instead.