How to retrieve all app bundle IDs which can open file at given URL? Like if file at given URL is .xml I want to get array of all app bundle IDs which can open .xml.
You can use Launch Services' LSCopyAllRoleHandlersForContentType() to get an array of bundle identifiers of capable applications.
Code might look something like the following:
NSURL *fileURL = [[NSBundle mainBundle] URLForResource:#"sample"
withExtension:#"xml"];
NSString *utiType = nil;
NSError *error = nil;
BOOL success = [fileURL getResourceValue:&utiType
forKey:NSURLTypeIdentifierKey error:&error];
if (!success) {
NSLog(#"getResourceValue:forKey:error: returned error == %#", error);
}
NSArray *bundleIdentifiers = [(NSArray *)LSCopyAllRoleHandlersForContentType(
(CFStringRef)utiType, kLSRolesAll) autorelease];
NSLog(#"bundleIdentifiers == %#", bundleIdentifiers);
In most recent versions of OS X, Launch Services is part of the CoreServices.framework umbrella framework. You may need to #import it in the class you want to call the LS* functions in, as well as add it to the Link Binary With Libraries Build Phase of your target. (It seemed to work OK here without linking against it in the OS X 10.8 SDK).
Related
The App Sandbox design guide says:
The related items feature of App Sandbox lets your app access files
that have the same name as a user-chosen file, but a different
extension. This feature consists of two parts: a list of related
extensions in the application’s Info.plist file and code to tell the
sandbox what you’re doing.
My Info.plist defines a document type for .pnd files (the user-chosen file), as well as a document type for .bak files. The entry for the .bak files has, among other properties, the property NSIsRelatedItemType = YES.
I am trying to use Related Items to move an existing file to a backup file (change .pnd suffix to .bak suffix) when the user writes a new version of the .pnd file. The application is sandboxed. I am not proficient with sandboxing.
I am using PasteurOrgManager as the NSFilePresenter class for both the original and backup files:
#interface PasteurOrgData : NSObject <NSFilePresenter>
. . . .
#property (readonly, copy) NSURL *primaryPresentedItemURL;
#property (readonly, copy) NSURL *presentedItemURL;
#property (readwrite) NSOperationQueue *presentedItemOperationQueue;
#property (readwrite) NSFileCoordinator *fileCoordinator;
. . . .
- (void) doBackupOf: (NSString*) path;
. . . .
#end
The doBackupOf: method is as follows. Notice that it also sets the NSFilePresenter properties:
- (void) doBackupOf: (NSString*) path
{
NSError *error = nil;
NSString *appSuffix = #".pnd";
NSURL *const pathAsURL = [NSURL URLWithString: [NSString stringWithFormat: #"file://%#", path]];
NSString *const baseName = [pathAsURL lastPathComponent];
NSString *const prefixToBasename = [path substringToIndex: [path length] - [baseName length] - 1];
NSString *const baseNameWithoutExtension = [baseName substringToIndex: [baseName length] - [appSuffix length]];
NSString *backupPath = [NSString stringWithFormat: #"%#/%#.bak", prefixToBasename, baseNameWithoutExtension];
NSURL *const backupURL = [NSURL URLWithString: [NSString stringWithFormat: #"file://%#", backupPath]];
// Move backup to trash — I am sure this will be my next challenge
// (it's a no-op now because there is no pre-existing .bak file)
[[NSFileManager defaultManager] trashItemAtURL: backupURL
resultingItemURL: nil
error: &error];
// Move file to backup
primaryPresentedItemURL = pathAsURL;
presentedItemURL = backupURL;
presentedItemOperationQueue = [NSOperationQueue mainQueue];
[NSFileCoordinator addFilePresenter: self];
fileCoordinator = [[NSFileCoordinator alloc] initWithFilePresenter: self]; // error here
[self backupItemWithCoordinationFrom: pathAsURL
to: backupURL];
[NSFileCoordinator removeFilePresenter: self];
fileCoordinator = nil;
}
The backupItemWithCoordinationFrom: method does the heavy lifting, basically:
[fileCoordinator coordinateWritingItemAtURL: from
options: NSFileCoordinatorWritingForMoving
error: &error
byAccessor: ^(NSURL *oldURL) {
[self.fileCoordinator itemAtURL: oldURL willMoveToURL: to];
[[NSFileManager defaultManager] moveItemAtURL: oldURL
toURL: to
error: &error];
[self.fileCoordinator itemAtURL: oldURL didMoveToURL: to];
}
but the code doesn't make it that far. I have traced the code and the URL variables are as I expect, and are reasonable. At the point of "error here" in the above code, where I allocate the File Presenter, I get:
NSFileSandboxingRequestRelatedItemExtension: an error was received from pboxd instead of a token. Domain: NSPOSIXErrorDomain, code: 1
[presenter] +[NSFileCoordinator addFilePresenter:] could not get a sandbox extension. primaryPresentedItemURL: file:///Users/cope/Me.pnd, presentedItemURL: file:///Users/cope/Me.bak
Any help is appreciated.
(I have read related posts Where can a sandboxed Mac app save files? and Why do NSFilePresenter protocol methods never get called?. I have taken note of several other sandboxing-related posts that don't seem relevant to this issue.)
MacBook Pro, MacOS 10.13.5, XCode Version 9.3 (9E145)
do not read too much about avoiding sandboxing. Most explenations go too far out of the most obvious problem. Instead of explaining the pitfalls that rightfully triggers sandboxing they explain mostly how to avoid the Sandbox at all. Which is not a solution - it is a thread!
So the most obvious problem is exposing a URL to pasteboard that still needs properly escaped characters in the string before you transform to NSURL.
So your NSString beginning with "file://" should use something like..
NSString *encodeStringForURL = [yourstring stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
before you transform to NSURL with
NSURL *fileurl = [NSURL URLWithString:encodeStringForURL];
NString *output = fileurl.absoluteString;
I'm on Mavericks.
I'm using the simulator.
It appears that NSFileManager URL based methods don't work properly on XCode 6 / iOS 8.
In my code sample at bottom, the path being searched in 'directoryToScan' is ...
/Users/xxxx/Library/Developer/CoreSimulator/Devices/A092C58C-1A43-4AF3-A9B1-109D7BA27F8D/data/Containers/Data/Application/41232C14-CF90-4E5C-72A7-8FF464FE7C32/Documents
In the code at bottom, even though I have files at the path I'm providing, when my code reaches the "for in" enumerator, it jumps right over the "for in" block to the end of it, and continues at the next line, and I get NO ERROR message from the block in the errorHandler.
I also tried a while clause, and that did the same.
Also, I tried two other techniques...
// THIS DOES NOT WORK
NSArray *dirContents = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:directoryToScan includingPropertiesForKeys:nil options:NSDirectoryEnumerationSkipsHiddenFiles error:&theError];
// THIS DOES WORK
NSArray *dirContents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:documentDirectory error:&theError];
So it appears there's a problem with NSURL's in iOS 8.
Any idea what's going on? I would think if there were a permission issue, I would have seen an error in my handler.
NSURL *directoryToScan = [NSURL fileURLWithPath:documentDirectory];
NSDirectoryEnumerator *dirEnumerator = [[NSFileManager defaultManager] enumeratorAtURL:directoryToScan
includingPropertiesForKeys:[NSArray arrayWithObjects:NSURLIsDirectoryKey, nil]
options:NSDirectoryEnumerationSkipsHiddenFiles | NSDirectoryEnumerationSkipsSubdirectoryDescendants | NSDirectoryEnumerationSkipsPackageDescendants
errorHandler:^(NSURL *url, NSError *error){
NSLog(#"Error occurred at url %#",url);
NSLog(#"Error message is %#",error);
return YES;}];
for (NSURL *theURL in dirEnumerator)
{
// Work with the files found by dirEnumerator.
// This whole block is being skipped over.
}
If you are still interesting in investigating the issue can You please provide the code for setting directoryToScan and documentDirectory? Have you tried to print both values during debug to ensure they both address same directory? In Xcode 6.1?
In Xcode 6.1 both for (NSURL *theURL in dirEnumerator) and for (NSURL* theURL = [dirEnumerator nextObject]) work fine for me.
My iOS app wants to play a local audio file. So, in xCode, I’ve added a file "audio.m4a" to my project. It resides on the top level of the file tree, but
NSURL *audioFileURL = [[NSBundle mainBundle]
URLForResource:#"audio"
withExtension:#"m4a"];
returns nil. There must be a stupid oversight. What am I doing wrong?
NSBundle *mainBundle = [NSBundle mainBundle];
NSString *myFile = [mainBundle pathForResource: #"audio" ofType: #"m4a"];
NSURL *audioFileURL = [NSURL URLWithString:myFile];
and check whether that file present in Build Phases" -> "copy bundle Resources"
Swift
Since neither answer is complete, and the solution is disseminated in the comments, let me offer a Swift alternative. and a step-by-step response.
The original Objective-C code is correct:
NSURL *audioFileURL = [[NSBundle mainBundle]
URLForResource:#"audio"
withExtension:#"m4a"];
Swift
let audioFileURL = NSBundle.mainBundle().URLForResource(
"audio",
withExtension: "m4a")
Target Membership
This was probably an oversight when originally adding the resource to the project. You must select adequate targets when adding these files:
Regardless of the language, ensure that your file is Project > Build Phases > Copy Bundle Resources. You need not to do that by hand. Instead, use the File Inspector. Select your resource (on the left panel) and verify it's target membership (on the right panel):
/* Use this code to play an audio file */
NSString *soundFilePath = [[NSBundle mainBundle] pathForResource:#"test" ofType:#"m4a"];
NSURL *soundFileURL = [NSURL fileURLWithPath:soundFilePath];
AVAudioPlayer *player = [[AVAudioPlayer alloc] initWithContentsOfURL:soundFileURL error:nil];
player.numberOfLoops = -1; //Infinite
[player play];
I have a security scope bookmark for a directory, provided by a user via an openDialog request.
I'm trying to create another security scope bookmark for a file inside this directory:
NSURL *musicFolder = /* Secured URL Resolved from a NSData, bookmark not stale */;
if (![musicFolder startAccessingSecurityScopedResource]) {
NSLog(#"Error accessing bookmark.");
}
NSString *file = #"myfile.txt"; /* This file exists inside the directory */
NSURL *pathURL = [musicFolder URLByAppendingPathComponent:file];
NSError *systemError;
NSData *bookmarkData = [pathURL bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope
includingResourceValuesForKeys:nil
relativeToURL:nil
error:&systemError];
[musicFolder stopAccessingSecurityScopedResource];
if (!bookmarkData) {
NSLog(#"%#", systemError);
}
Both bookmarkData and systemError end up nil which is not very useful...
Is this even supported or can you only get valid secured scope bookmarks from the system?
In my test program this works fine. I suspect the append of the file name to the URL is failing in your case (but that's a huge guess) because it's the only thing that seems materially different.
I notice that the url for a security resolved location is: file://localhost/Users/dad/Desktop/TestFolder?applesecurityscope=343335323030663066393432306234363030346263613464636464643130663635353065373030373b30303030303030303b303030303030303030303030303032303b636f6d2e6170706c652e6170702d73616e64626f782e726561642d77726974653b30303030303030313b30313030303030323b303030303030303030326461363838663b2f75736572732f74796c65722f6465736b746f702f74657374666f6c646572
which is the other reason I wonder if the append is the issue.
In my test I have the user choose the folder, create the security scoped bookmark and then save that in user defaults.
Then I quit & relaunch the app and via a menu command I get that bookmark and then resolve it. Then I added a case where I use the resolved bookmark to a folder and make a new bookmark to the file within the folder.
It seems to work fine.
In my test where it's working I'm getting the path to the file like this:
NSURL * resolvedURL = [NSURL URLByResolvingBookmarkData: data
options: NSURLBookmarkResolutionWithSecurityScope
relativeToURL: nil
bookmarkDataIsStale: &isStale
error: &error];
... // (error checking)
[resolvedURL startAccessingSecurityScopedResource];
NSArray * files = [[NSFileManager defaultManager]
contentsOfDirectoryAtURL: resolvedURL
includingPropertiesForKeys: #[NSURLLocalizedNameKey, NSURLCreationDateKey]
options: NSDirectoryEnumerationSkipsHiddenFiles
error: &error];
if ( files != nil )
{
NSURL * fileURL = [files objectAtIndex: 0]; // hard coded for my quick test
NSData * newData = [fileURL bookmarkDataWithOptions: NSURLBookmarkCreationWithSecurityScope
includingResourceValuesForKeys: nil
relativeToURL: nil
error: &error];
if ( newData != nil )
{
NSLog(#"it's good!");
}
.... // error checking and logging.
if that doesn't get you on the right track, I'm going to need to see more code (you'll probably need to make a simple example).
Note that in my case I'm resolving the bookmark and calling startAccessingSecurityScopedResource even when I just got the url & created the bookmark (when I tried to create a bookmark from the path I'd just acquired from PowerBox (openPanel) it failed with an error 256).
Some configuration details: OS X 10.8.4, Xcode 5 (first public release from today 9/18/2013).
For creating bookmarks for locked files, use NSURLBookmarkCreationSecurityScopeAllowOnlyReadAccess flag combined with NSURLBookmarkCreationWithSecurityScope flag in API call for creating bookmark.
For example:
NSURL* fileURL = [NSURL fileURLWithPath:filePath];
NSError* error = NULL;
NSData* bookmarkData = [fileURL bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope|NSURLBookmarkCreationSecurityScopeAllowOnlyReadAccess includingResourceValuesForKeys:nil relativeToURL:nil error:&error];
I have tried this in Mac OS 10.9.5
Following up after reporting the problem with security-scoped bookmarks and locked-files, this is the reply from Apple:
"Also, as you've noticed, creating a security-scoped bookmark requires write access to the target file. That should no longer be the case in OS X Mavericks."
Which would indicate that it is a bug in version of OS X pre-10.9.
my code is this. And the value of ret is always NO, I think the write path is not allowed.
But where I can store my information on MAC OS X for my app? Can you help me to find the right path to store my app's setting? Thank you very much~ :)
NSString* writePath = [[[NSBundle mainBundle] resourcePath]
stringByAppendingPathComponent:#"Brain.plist"];
NSLog(#"%#",writePath);
NSMutableDictionary* dictForRet = [[NSMutableDictionary alloc]init];
NSNumber* applicationNumber = [[NSNumber alloc]initWithInt:0];
NSMutableDictionary* root = [[NSMutableDictionary alloc]init];
NSArray* propertyArray = [[NSArray alloc]initWithObjects:kPropertyArrayApplicationPath,kPropertyArrayApplicationCS, nil];
NSMutableDictionary* brain = [[NSMutableDictionary alloc]init];
[root setObject:propertyArray forKey:kPropertyArrayName];
[dictForRet setObject:applicationNumber forKey:kPropertyKeyApplicationNumber];
[dictForRet setObject:root forKey:kPropertyArrayDictName];
[dictForRet setObject:brain forKey:kPropertyDictBrainName];
NSLog(#"%#",dictForRet);
ret = [dictForRet writeToFile:writePath atomically:YES];
NSLog(#"%d",ret);
The resource path refers to the Resources/ directory in your app bundle. You do not want to write to this path, which is usually not allowed (especially in case of a sandboxed app).
To get paths to standard locations, like the Application Support directory use NSSearchPathForDirectoriesInDomains.
You're probably looking for something like:
NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
This will return an (array of size 1) Application Support URL, automatically picking the right path if your app is sandboxed.
You can't write to the current app's bundle.
Instead, you might stuff it in your sandbox, in ~/Library/Application Support/YOUR_APP/, or in a user-specified location.