I need to open a NSSavePanel with the users Library folder as destination folder. Normally I would do this by entering ~/Library/ in [NSSavePanel beginSheetForDirectory].
This works fine as long as the application is not sandboxed. For sandboxed applications this will result in the NSSavePanel trying to access a folder inside the applications document "box".
I cannot refer to /Users/username/Library/ as I do not know the users username at runtime. So how do I link to this path in cocoa?
I'm not sure how sandboxing fits in with this, but you can find the user's library directory using:
NSArray* paths = NSSearchPathForDirectoriesInDomains( NSLibraryDirectory, NSUserDomainMask, YES );
Not sure if this will work on a sandboxed application but this is how I do it right now. This will return /User/TheirUserName
-(NSString *)homeDirectory
{
return NSHomeDirectory();
}
It depends what you are trying to achieve.
If the behavior is required by your application, then you can request a temporary exception entitlement when submitting the application to the Mac App Store. But sooner or later, you will have to find a solution to remove this exception.
If you want to access data that were previously stored in the ~/Library/ folder, you can define a migration strategy to move back the data into the sandbox.
Related
My OS X app (currently not sandboxed) accesses files contained inside a directory set by the user (one chooses the path with a NSOpenPanel and a reference to this path is kept throughout execution). The list of files is generated via NSDirectoryEnumerator and I then read from and write to those files using AVAsset and taglib (in C++ with a bridging header) respectively.
As expected, enabling Sandboxing in Xcode rendered the app useless, the list of files given by NSDirectoryEnumerator is empty and even if it weren't, I would not be able to read from and write to the files. What are the steps I need to take to make my app sandbox-compliant?
Does my app need to be document based? Can my app really be "document-based" since I don't really have proper documents (as in: I don't have a window per file, it doesn't seem to comply to the standard document-based app model)? My app is basically just a table view with files references as rows.
Another important point: can I still use taglib to write to my files if my app is document-based ? I need to pass taglib the path to my file as a string pointer in order for it to work.
Thanks a lot, this topic is quite confusing at the moment.
You don't have to convert your app to be document-based to gain access to user selected files and security scoped bookmarks.
I can think of 2 reasons why your current code does not work in a sandboxed environment:
You don't have the "User Selected File Access" capability set (Xcode > target > Capabilities > App Sandbox > File Access)
You are using the path/NSString based API of the directory enumerator instead of the URL NSURL based one.
A vanilla Xcode project with Sandboxing enabled and the User selected files capabilities set, should enumerate any path obtained via NSOpenPanel:
NSOpenPanel* panel =[NSOpenPanel openPanel];
panel.canChooseDirectories = YES;
[panel beginSheetModalForWindow:self.view.window completionHandler:^(NSInteger result) {
NSFileManager *fileManager = [[NSFileManager alloc] init];
NSURL *directoryURL = panel.URL;
NSDirectoryEnumerator *enumerator = [fileManager
enumeratorAtURL:directoryURL
includingPropertiesForKeys:nil
options:0
errorHandler:nil];
for (NSURL *url in enumerator) {
NSLog(#"url:%#", url);
}
}];
If you want to store the ability to access specific folders from the sandbox across app launch/quit cycles, you will need to store a security scoped bookmark.
This post contains information persisting user selected file/directory access via app scoped bookmark:
Trouble creating Security-Scoped Bookmark
It sounds like the current functionality will convert to Sandboxing just fine.
The user selects the directory via NSOpenPanel (which will invoke something called Powerbox in the Sandboxed environment).
This directory is now writable, as the user has explicitly selected it.
You can even maintain write access to this directory by creating a Security Scoped Bookmark and storing it away between sessions.
This has got nothing at all to do with being Document based; that is an internal design that is unrelated to Sandboxing.
I'm attempting to add functionality to an application that is similar in function to the "Open With..." menu in the Finder. In the application I'm working on, a user can select a file and chose to open it with a specific application, rather than with the default one.
The problem I'm experiencing is that there appears to be a mis-match in the APIs I think I should be using in order for this to work properly. Specifically, the recommended NSWorkspace API for opening files with a specific application takes a bundle identifier to specifying the application:
[NSWorkspace openURLs:
withAppBundleIdentifier:
options:
additionalEventParamDescriptor:
launchIdentifiers:]
However, the Launch Services API for getting a list of applications that can open a given file returns an array of URLs, each pointing to a compatible application:
CFArrayRef LSCopyApplicationURLsForURL(CFURLRef inURL, LSRolesMask inRoleMask);
Currently, I'm iterating over the array that launch services returns and extracting the bundle identifier by creating a new NSBundle each time:
for (NSURL *applicationURL in applicationURLs) {
NSBundle *applicationBundle = [NSBundle bundleWithURL:applicationURL];
NSString *bundleIdentifier = applicationBundle.bundleIdentifier;
// Do something with bundle identifier...
}
The problem I'm experiencing is that if the applicationURL points to an application that is not under /Applications (or any other directory my sandboxed application has read access to), then I cannot create an NSBundle to read the bundle identifier. Instead, Gate Keeper will output a sandbox violation to Console.app and NSBundle bundleWithURL will return nil.
Is there a way I've overlooked to get the bundle identifier from a URL that I've overlooked? Or is there a different way to read the bundle identifier from an arbitrary file without causing a sandbox violation? Or maybe there's a different way to get a list of all applications that can open a specific URL?
(Note that the Launch Services method to open multiple URLs, LSOpenURLsWithRole has been deprecated. The header file simply says "Use NSWorkspace".)
I enabled sandbox and I want to create data by bookmarkDataWithOptions.
If the URL is created by NSPanel that work very well. But, If I obtain URL without using NSOpenPanel, the bookmarkDataWithOptions method always return nil. why?
thank about If I want to set a special folder default can read/write without using NSOpenPanel.
How can i do?
Thanks
The main feature of the Sandbox is security. If an application could read/write an arbitrary folder without user permission, the security would be broken.
The App Sandbox Design Guide states clearly:
• Simulation of user input in Open and Save dialogs:
if your app depends on programmatically manipulating Open or Save dialogs to simulate or alter user input, your app is unsuitable for sandboxing.
The only way to achieve something similar, is to add a read/write entitlement to one of the preset directories (Documents, Pictures, Music, etc…). For further documentation, take a look at this guide.
One of the nice features of BBEdit is how it keeps track of files no matter what happens to those files. The application that I am working on needs to keep track of the location of a number of files owned by the user. The user can move or delete these files and my application needs to know where those files are even if my application was not running at the time the change in location happened.
What is a recommended strategy for this problem or what could it be?
Keeping track of files while the application is running is not the problem. I want the correct path to the files when my application is running no matter what happened to those files when my application was not running.
Is manually searching for the files the best approach or is there an API that I am overlooking that makes this easier?
Starting from 10.6, it is done by “Bookmarks” functionality of NSURL:
Bookmarks are a new facility for generating persistent references to resources identified by URLs. A bookmark is a data object generated by the system from a resource URL. The bookmark data encapsulates a durable, opaque reference to the underlying resource as well as value of resource properties captured when the bookmark was created. A bookmark can be stored in memory or on disk and later used to access the resource property values it contains, or resolved to cover the underlying resource’s URL. In the case of file system resources, the bookmark is capable of locating resources that have been moved or renamed since the bookmark was created, similar to Alias Manager aliases. Note that in this release, bookmarks resolve only by path.
The following new NSURL methods are further documented in NSURL.h:
- (NSData *)bookmarkDataWithOptions:(NSURLBookmarkCreationOptions)options includingResourceValuesForKeys:(NSArray *)keys relativeToURL:( NSURL*)relativeURL error:(NSURL **)error;
- (NSURL*)initByResolvingBookmarkData:(NSData*)bookmarkData options:(NSURLBookmarkResolutionOptions)options relativeToURL:(NSURL *)relativeURL bookmarkDataIsStale:(BOOL *)isStale error:(NSError **)error;
+ (NSURL *)URLByResolvingBookmarkData:(NSData *)bookmarkData options:(NSURLBookmarkResolutionOptions)options relativeToURL:(NSURL *)relativeURL bookmarkDataIsStale:(BOOL *)isStale error:(NSError **)error;
+ (NSDictionary *)resourceValuesForKeys:(NSArray *)keys fromBookmarkData:(NSData *)bookmarkData;
Before 10.6, it was done with aliases.
I'm trying to save a CFPropertyList to a location in the user's home folder. Using the code below I'm getting errorCode = -10 (unknown error).
CFURLRef fileURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, CFSTR("~/testfile.txt"), kCFURLPOSIXPathStyle, false );
SInt32 errorCode;
Boolean status = CFURLWriteDataAndPropertiesToResource(fileURL, xmlData, NULL, &errorCode);
If I change the path to something like "/testfile.txt" without the '~' then everything works. How can one save a property list to the current user's home folder? Must one obtain the user's name first and include it in the path such as /users/toffler/testfile.txt?
With Foundation, you can call the NSHomeDirectory function to get the absolute path to the user's home directory. You can then use the path-manipulation methods of NSString or of NSURL, or the equivalent function for CFURL, to tack a sub-path onto that.
However, please don't put files into the top level of my Home folder unless I, as the user, tell you to.
If you want to save a file at the user's request, run a save panel, and then save it where the user told you to.
If you want to save a preferences file, you probably should be using NSUserDefaults or CFPreferences, instead of handling plists yourself.
If you have some other reason to save a user-specific file, it should generally go into either the chewable items folder, the Caches folder, or the Preferences folder. The two functions that I linked to are the easiest ways to access those two of those three folders; the harder way, and the only one that works on all three, is the FSFindFolder function. Unlike most of the Folder Manager, FSFindFolder is not deprecated and is available in 64-bit.
Updates from the year 2012
Nowadays, you should use URLs, not paths. NSFileManager has a method for getting a URL to a desired directory.
Stapling paths (or file URLs) together does not work in a sandboxed application, because you don't have authorization to access the path you just made up. You need to get the path (or URL) from a system API, whether it's NSFileManager, NSSavePanel, or something else.
For saving and restoring file references, use NSURL's security-scoped bookmarks feature.
NSUserDefaults and CFPreferences work as expected in a sandbox.
The Folder Manager is now fully deprecated as of 10.8.
Polluting the user's Home folder is still bad manners.
Automatic ~ expansion is a feature of the shell.
If you are using Cocoa/Foundation, you can use the NSString methods
- (NSString *)stringByAbbreviatingWithTildeInPath;
- (NSString *)stringByExpandingTildeInPath;
otherwise, you'll have to write some code yourself to do this. Getting the user's login name and constructing the path /Users/login-name/ is not the correct way to do this. (While most users will have their home directory here, some will not.)