Objective-C - Loop through all installed applications - objective-c

I've been trying to use NSWorkspace and fullPathForApplication but this isn't meeting my needs as it doesn't return the correct application.
I could do:
[NSBundle bundleWithPath: [[NSWorkspace sharedWorkspace] fullPathForApplication: #"My Application"]];
And this will return whichever (I believe) was installed most recently out of:
/Applications/My Application
/Applications/My Application 2
I want "My Application" to be returned, but the second is being returnd. I don't want to assume all applications are installed in /Applications/ so I'm not just going to loop through a folder.
Does anyone know how I can manually just loop through all applications and verify which is correct? Or a similar method to fullPathForApplication that would return all results, vs. just choosing one at random?

I believe the NSWorkspace method you're using is a facade for LSFindApplicationForInfo(), which is documented to return just a single application:
If more than one application is found matching the specified characteristics, Launch Services chooses one in the same manner as when locating the preferred application for opening an item.
If your app opens a specific file type or URL, use LSCopyApplicationURLsForURL(), which returns an array of matching apps.

Related

Prevent NSDocument saving in temporary dictionary

I have an app with subclass of NSDocument that has overridden method writeToURL:(NSURL *) ofType:(NSString *) error:(NSError **) which saves data at given NSURL location, but also can save additional file (with appended .my2ext) with debug information. Previously it worked well (I created the app several years ago), but now I see that instead of user selected location the method gets some temporary directory:
file:///var/folders/yv/gwf3_hjs0ps7sb3psh3d0w3m0000gn/T/TemporaryItems/(A%20Document%20Being%20Saved%20By%20MyApp%202)/myfilename.myext
Then, as I understand, the framework relocates the main file (at given url), but the additional file gets lost. So, can I somehow obtain the user selected path to save directly into it? Or prevent using temp directories at all?
I've already turned off the SandBox mode, but this didn't help. I also know that I can use "File Package" approach, but my app is created for a few people only, so, there is not interest in good production approach, only in simplicity.
I tried to google any possible solution, but found nothing helpful or just related. Even the documentation says nothing about using temporary directories! So, I decided to override different NSDocument methods. After several experiments I almost lost hope, but then I found that the method
saveToURL: ofType: forSaveOperation: delegate: didSaveSelector: contextInfo: provides real, user selected location. And this finally solved the problem.

Sandboxed Mac app exhausting security scoped URL resources

I am developing a Mac application that prompts the user for files using the NSOpenPanel. The application is sandboxed (testing on OSX 10.9.4). I noticed that if I open a large amount of files (~3000), the open panel starts to emit errors to the log. This also happens if I try to open less amount of files in chucks for several times.
After the errors start to appear the first time, every time the NSOpenPanel is used again to open files, no matter for how many files, these errors will be generated again (until the application is closed).
The error message looks like this:
TestPanel[98508:303] __41+[NSSavePanel _consumeSandboxExtensions:]_block_invoke: sandbox_consume_fs_extension failed
One line for each file I try to open.
I managed to reproduce this behavior with a simple app: A sandboxed application with a single button invoking the following code:
NSOpenPanel* panel = [NSOpenPanel openPanel];
[panel setAllowsMultipleSelection:YES];
[panel setCanChooseDirectories:NO];
[panel setCanChooseFiles:YES];
[panel beginSheetModalForWindow:[self window] completionHandler:^(NSInteger result) {
NSLog(#"%lu", [panel.URLs count]);
}];
The errors appear before the code reaches the completion handler.
It seems that I can still get the URLs from the panel in the completion handler but it really pollutes the system log.
EDIT:
Seems that this problem is not directly related to the NSOpenPanel/NSSavePanel panels. A very similar thing happens when using drap/drop with files. Something like this:
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender {
...
NSPasteboard *pboard = [sender draggingPasteboard];
if ([[pboard types] containsObject:NSURLPboardType]) {
NSArray *urls = [pboard readObjectsForClasses:#[[NSURL class]] options:nil];
}
...
}
This will generate the following log messages when dragging a large amount of files (the "magic" number seems to be somewhere around 2900):
Consume sandbox extension for itemIdentifier (2937) from pasteboard failed!
As with the NSOpenPanel, after the first occurrence of this, every single file dropped will generate the same error in the log.
EDIT 2:
#mahal tertin's reply pointed me to the right direction. The problem is indeed with the number of files and the fact that security scoped URL resources are limited.
However, there seems to be no reasonable solution found. The problem is that when the user clicks "OK" on the NSOpenPanel (or drops the files on a drag&drop aware control), behind the scenes the OS already attempts to create these security scoped URLs and implicitly calls startAccessingSecurityScopedResource for you. So if the user attempts to open more files than the limit, the resources are exhausted and the only option is to close and restart the application.
Calling stopAccessingSecurityScopedResource on the returned URLs seem to free the resources however this solution was discouraged by Apple's representative on the official developers forums (link is behind login).
It seems that the app is at the mercy of the user not to open too many files. And that is not even at once, since there is no approved way to release these resources. You can warn the user in documentation or even with an in-app alert but there is no way to prevent them from messing up the app and forcing a restart.
So if the app runs long enough and the user keeps opening files, the app will eventually become unusable.
Still looking for a reasonable solution for this.
After searching high and low and asking in various places, I am going to close this question and conclude there is no answer or solution to this. I am posting the known information on this for future reference.
All the solutions suggested are just workarounds that may minimize the problem and try to guide the user toward not trying to open too many files. But there nothing that can be done to actually solve this.
Here are the known facts about this issue:
No matter what you do, the user can attempt to open too many files in the NSOpenPanel dialog and exhaust the security scoped URL resources
Once these resources are exhausted, it is not possible to open any more files for reading/writing. The application needs to be closed and reopened
Even if the user doesn't attempt to open too many files at once, the application may still exhaust these resources if it runs long enough and the user opens enough files over time since startAccessingSecurityScopedResource is called automatically for files opened with NSOpenPanel (or the drag/drop mechanism) and nothing ever closes these resources
Calling stopAccessingSecurityScopedResource on all URL retrieved by the open panel will free these resources but this practice is discouraged by Apple, saying it might not be compatible with future solutions
When you receive the list of URLs from NSOpenPanel (or drag/drop), there is no way to tell if all URLs were successfully accessed or if there are URLs that are over the limit and therefore invalid.
Apple is aware of this and may fix it in the future. It is still not fixed in 10.10 and of course, that will not help current applications running on current/previous OSX version.
It seems Apple has really dropped the ball on this one, the Sandbox implementation seems very sloppy and short sighted.
The behavior you experience is because the security scoped resources are limited:
NSURL - (BOOL)startAccessingSecurityScopedResource tells
If sufficient kernel resources are leaked, your app loses its ability
to add file-system locations to its sandbox...
The current limit is roughly what you experienced. See:
What are the current kernel resource limits on security-scoped bookmarks?
To prevent it:
only start accessing those SSBs you need at a given time and subsequently stop accessing them
start access not files but enclosing folders: ask the user not to choose files but a full folder. This will grant you access to the whole tree beneath that directory
on draggingEntered: show a NSOpenPanel with the enclosing directory(ies) to grant access

What exactly should I pass to -[NSApp activateIgnoringOtherApps:] to get my application to start "naturally" in comparison to most other OS X apps?

When I learned how to start NSApplications on my own, the code I used (based on here and here) did
[NSApp activateIgnoringOtherApps:YES];
which forces the app to the front at startup.
I'd like to know what most other apps do. I want to be able to run programs both directly from the binary and from an app bundle, and I'm not using Xcode to build this (raw building). So I'd rather this act naturally, so to speak.
The docs do say Finder issues NO, but... why Finder? Isn't this a method that's run from within the process, not outside? (I'm not in control of the choice.) And what about the Dock and other possible entry points?
I even went so far as to disassemble 10.8's NSApplicationMain() to see what it did, but as far as I can tell from the 32-bit version, unless this "light launch" thing issues this selector, this selector is never called.
Is there an answer to this question? Thanks... and sorry if this is confusing; I tried to word it as clearly as possible.
Apps normally do not call -activateIgnoringOtherApps: at all. And, generally speaking, shouldn't. Certainly, it wouldn't be in NSApplicationMain(), which is too early and fairly distantly related to actual app start-up.
Apps are normally launched by Launch Services (which is what is used by the Finder, the Dock, and /usr/bin/open, as well as any other app that might open yours or a document which yours handles). Roughly what happens is that Launch Services deactivates the app which called it to open something else and then, in the launched app, Cocoa's internals do something like (but not necessarily identical to) [NSApp activateIgnoringOtherApps:NO]. In this way, the launched app only activates if nothing else was activated in the interval between those two events. If that interval is long (because something was slow) and the user switched to something else in the meantime, you don't want to steal focus from whatever they switched to.
You should only call [NSApp activateIgnoringOtherApps:YES] in response to a user request to activate your app in a context which won't include the automatic deactivation of the current app by Launch Services. For example, if you have a command-line program which transforms itself into a GUI app (using -[NSApplication setActivationPolicy:] or the deprecated TransformProcessType()), then the user running that tool means they want it active. But Terminal is active and won't be deactivated spontaneously just by virtue of having run your program. So, the program has to steal focus.
If your program is a bundled app, then running it from the command line should be done with /usr/bin/open rather than directly executing the executable inside the bundle. Then, you don't need to call -activateIgnoringOtherApps: at all and the question of what value to pass is moot.

cocoa get list of installed applications

Is there a way to get all installed applications for the current user in cocoa?
NSArray *runningApps = [[NSWorkspace sharedWorkspace] launchedApplications];
The above gives me currently running applications but for my app I need to list all installed applications. I need the application key (e.g. com.apple.appname) so system_profiler will does not work.
For OSX, the key library for gathering information about launchable applications is Launch Services (see Apple's Launch Services Programming Guide), which will give you the information about an application such as bundle id, file types that it accepts, etc.
For actually locating all executables on the machine, you're going to want to use Spotlight in one form or the other (either the API or by calling out to mdfind).
Example of using the command line version:
mdfind "kMDItemContentType == 'com.apple.application-bundle'"
will return a list of all application paths.
Using a similar term in the spotlight API will result in an appropriate list, from which you can then either open the main bundle using NSBundle or use Launch Services to retrieve information about the app.
I don't have time to do a thorough test of this, but the basic code would be:
NSMetadataQuery *query = [[NSMetadataQuery alloc] init];
[query setSearchScopes: #[#"/Applications"]]; // if you want to isolate to Applications
NSPredicate *pred = [NSPredicate predicateWithFormat:#"kMDItemContentType == 'com.apple.application-bundle'"];
// Register for NSMetadataQueryDidFinishGatheringNotification here because you need that to
// know when the query has completed
[query setPredicate:pred];
[query startQuery];
(Revised to use #John's localization-independent query instead of my original)

NSNetServiceBrowser did NOT find published service

On an iPhone (the server), I've tried to publish a service and my code ran into the NSNetService object's delegate method:
-(void)netServiceDidPublish:(NSNetService *)sender
So I believe that my service #"_chatty._tcp." has published successfully. Then on another iPhone (the client), I use NSNetServiceBrowser to find my service, but it did NOT run into the delegate method:
-(void)netServiceBrowser:(NSNetServiceBrowser *)netServiceBrowser didFindService:(NSNetService *)netService moreComing:(BOOL)moreServicesComing
I found some questions related to my case on this site, most of the answer remind to check the delegate object whether is out of scope or not. I'm sure my delegate work well because it ran into another delegate method like:
-(void)netServiceBrowserWillSearch:(NSNetServiceBrowser *)aNetServiceBrowser
Can anybody help me find out the reason?
Here are some parts of my code:
I init the service like that:
#define MY_PROTOCOL #"_chatty._tcp."
self.myService = [[NSNetService alloc]
initWithDomain:#"" type:MY_PROTOCOL
name:#"thaith" port:self.port];
The port is initialized with a given listeningSocket in the Browser class:
NSNetServiceBrowser* finder = [[NSNetServiceBrowser alloc] init];
//I also retain the finder.
finder.delegate = self;
[finder searchForServicesOfType:MY_PROTOCOL inDomain:#""];
After having come across the same problem and giving up for a month. I've just come back to it and solved it:
Even though the sample code in the docs seems to imply otherwise, don't use a local variable for the NSNetServiceBrowser. As soon as it goes out of scope it gets garbage collected. Make finder an instance variable or property so its sticks around. I didn't spot this straight away as the netServiceBrowserWillSearch: delegate was getting called so I assumed everything was ok...
Possible Solutions
Check both WiFi identifiers are same
Check both are in same WiFi network
Check the NSNetServiceBrowser delegate assigned as same class
At last download sample Apple.Developer Witap Application , install in two devices , test and confirm it working.
Instead of downloading bonjour browser, I suggest using the terminal command:
dns-sd -B _chatty._tcp local.
For me, it shows that the server side is working fine.
Currently, I can find the service when my application starts, my only issue is that once I stop the server, I get the "removed" event but running it again, I cant discover it anymore. I know the problem is on my client side, thanks to dns-sd - B
I would narrow the scope and try to find the problem place. First, find out whether the service is published correctly. Use Bonjour Browser application (you can find it in the Internet) on a computer within the same local network where you publish the service. I hope you publish and browse in the same local net. If the Bonjour Browser can see your service then you know it is published correctly. Then work on the browser side to connect to it.