objective-cmacoscocoacore-datapath

Persisting bookmark in core-data


I have an OSX application that is supposed to have a list of files from anywhere in the user's disk.

The first version of the app saves the path to these files in a core-data model.

However, if the file is moved or renamed, the tool loses its purpose and the app can crash.

So I decided to use bookmarks. It seems to be working, but every time I try to recover the data, I get the old path of the files. Why is that? What am I missing?

My core-data entity uses a binary data field to persist the bookmark.

The bookmark itself is done like this:

NSData * bookmark = [filePath bookmarkDataWithOptions:NSURLBookmarkCreationMinimalBookmark
                       includingResourceValuesForKeys:NULL
                                        relativeToURL:NULL
                                                error:NULL];

And on loading the application, I have a loop to iterate all the table and recover the bookmark like this:

while (object = [rowEnumerator nextObject]) {
    NSError * error = noErr;
    NSURL * bookmark = [NSURL URLByResolvingBookmarkData:[object fileBookmark]
                                                 options:NSURLBookmarkResolutionWithoutUI
                                           relativeToURL:NULL
                                     bookmarkDataIsStale:NO
                                                   error:&error];
    if (error != noErr)
        DDLogCError(@"%@", [error description]);

    DDLogCInfo(@"File Path: %@", [bookmark fileReferenceURL]);
}

If I rename the file, the path is null. I see no difference between storing this NSData object and a string with the path. So I am obviously missing something.

Edit: I also often get an error like this: CFURLSetTemporaryResourcePropertyForKey failed because it was passed this URL which has no scheme.

I appreciate any help, thanks!


Solution

  • I can't find any issues in my code, so I changed it.

    After looking for the reason of the "no scheme" message, I came to the conclusion some third-party application is required for this code to work, and that's undesirable.

    I am now using aliases. This is how I create them:

    FSRef fsFile, fsOriginal;
    AliasHandle aliasHandle;
    NSString * fileOriginalPath = [[filePath absoluteString] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    OSStatus status = FSPathMakeRef((unsigned char*)[fileOriginalPath cStringUsingEncoding: NSUTF8StringEncoding], &fsOriginal, NULL);
    status = FSPathMakeRef((unsigned char*)[fileOriginalPath cStringUsingEncoding: NSUTF8StringEncoding], &fsFile, NULL);
    OSErr err = FSNewAlias(&fsOriginal, &fsFile, &aliasHandle);
    NSData * aliasData = [NSData dataWithBytes: *aliasHandle length: GetAliasSize(aliasHandle)];
    

    And now I recover the path like this:

    while (object = [rowEnumerator nextObject]) {
        NSData * aliasData = [object fileBookmark];
        NSUInteger aliasLen = [aliasData length];
        if (aliasLen > 0) {
            FSRef fsFile, fsOriginal;
            AliasHandle aliasHandle;
            OSErr err = PtrToHand([aliasData bytes], (Handle*)&aliasHandle, aliasLen);
            Boolean changed;
            err = FSResolveAlias(&fsOriginal, aliasHandle, &fsFile, &changed);
            if (err == noErr) {
                char pathC[2*1024];
                OSStatus status = FSRefMakePath(&fsFile, (UInt8*) &pathC, sizeof(pathC));
                NSAssert(status == 0, @"FSRefMakePath failed");
                NSLog(@"%@", [NSString stringWithCString: pathC encoding: NSUTF8StringEncoding]);
            } else {
                NSLog(@"The file disappeared!");
            }
        } else {
            NSLog(@"CardCollectionUserDefault was zero length");
        }
    }
    

    However, I am still curious on why my previous code failed. I appreciate any thoughts on that. Thanks!