How do I keep track of file locations on Mac OS X? - cocoa

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.

Related

OS X App Sandboxing and arbitrary files access - Update to Document-based?

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.

Document-scope, Security scoped bookmarks for file packages

I'm trying to create document-scope security scoped bookmarks for file packages. That is, directories where NSURLIsPackageKey is YES. I know you're not normally supposed to be able to create document-scope bookmarks to directories, but I would have expected packages to be exempt from that (after all, I get access to them using an NSOpenPanel which isn't allowed to select directories, but there's no problem there).
I've got my entitlements set up with com.apple.security.files.bookmarks.document-scope = true, and I'm doing a basic bookmark creation call with a file URL that I've just received from an NSOpenPanel (so I have access):
NSError *bookmarkError = nil;
NSData *bookmark = [fileURL
bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope
includingResourceValuesForKeys:nil
relativeToURL:[self fileURL]
error:&bookmarkError];
After this call though, both bookmark and bookmarkError are nil.
Do I just need to give up on this and create app-scope bookmarks, even though I have a document-based app? That seems inappropriate, but I don't see another workaround.
I asked about this elsewhere and was told:
...we didn't implement support for it because it's complicated and there have been very few requests for it.
So that's that, you can't create this kind of bookmark because it's not implemented. I also filed a bug with Apple but the response just quoted the docs at me (i.e. telling me stuff I already knew and had mentioned in my report) before closing it. So, as of now and probably for the foreseeable future, this is not possible.

Mac OS: Where to save NSData object with bookmark of file url?

I'm new to Mac OS development, so I have simple question. I have a NSURL object of a folder, I want to keep this URL and make it bulletproof to app quit/reboot/folder rename. So, I made a NSData bookmark. I'm a little confuse right now, where I must save this NSData object for future use.
It is better to save object to NSUserDefaults?
Or I must make a file in my bundle and store this object in it, if so, what must be an extension of that file, and is it "safe"?
Or I must save that file in Application Support Directory ?
After I write this question, I found another one, what if I will have more data to save like NSString objects or NSNumber or else, I must make another files with info, or could make a one file with all that necessary info?
NSUserDefaults is used to store user preference and other data that will be used by application in the next run. For your instance you can use it.
Application bundle usually not used for writing data. Specifically user may be standard user in that case he/she will have only read,execute permission to App bundle and attempt writing inside bundle will fail.
Application Support directory is used to save files and other big data that application needs in the next run .If data size is less then it is preferred to store in NSUserDefaults.
You can define your own keys to save other NSString, NSNumber etc objects to NSUserDefaults. Same keys is used to read from NSUserDefaults.

Getting path to users Library folder in OS X

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.

Saving CFPropertyLists To Users Folder

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.)

Resources