Is there an alternative to NSFileCoordinator for opening related files in a sandbox?

人走茶凉 提交于 2020-01-21 17:35:43

问题


This is a follow-up to Access sidecar files in a Mac sandboxed app.

Though not covered in the answer there, the Apple docs teach us that to access a "related file" we must use an NSFileCoordinator for access (ref).

This is a little heavy for my needs, and poses an architectural problem, since the actual file access is in my back-end code, away from the reaches of the Apple library facilities. I don't want to have to use the NSFileCoordinator for obtaining the related file's content, if I can help it. I don't want to require my users to manually identify the sidecar file either (if nothing else, this would be a bad workflow for batch processing). I just want to tell the sandbox "this is okay, this app can open such-and-such a related File.XYZ after the user chose File.ABC".

For normal file accesses this isn't a problem: using an std::ifstream to open a file that's been previously selected from an Open panel seems to work for remainder of the app instance's lifetime.

But opening a "related file" seems to be more limited.

Having added a NSIsRelatedItemType to my app's plist (as indicated in the linked answer), what is the minimal thing I can do in the front-end, presumably immediately after opening the "primary"/requested file, such that I can also later use an std::ifstream to open a related sidecar file? The documentation seems a little sparse on this subject…

Perhaps my best bet is to perform a one-time prompt for the user to authorise access to the encapsulating directory, and save the resulting entitlement as an app-scoped bookmark (ref) but again that's not quite as transparent as I'd like. It would also be perhaps a little "scary" for users to be confronted with such a request.


回答1:


No, because the OS will [potentially] actually copy the file to a different location in order to provide you with access to it, so you must use NSFileCoordinator.

But all is not lost! There is a hack: even if your back-end code is designed to be portable, if you set the file-reading .cpp to be "Objective-C++ Source" in Xcode, you can use Foundation features (#import <Foundation/Foundation.h>) right there.

So wherever you currently instantiate and read-from an std::ifstream, have an #if defined(PLATFORM_MAC_OS_X) (or whatever) and, inside that, wrap your file-reading with the NSFileCoordinator code.

Up top:

#ifdef PLATFORM_MAC_OS_X
#import <Foundation/Foundation.h>

@interface SidecarPresenter : NSObject<NSFilePresenter>
@property(readwrite, copy) NSURL* presentedItemURL;
@property(readwrite, copy) NSURL* primaryPresentedItemURL;
@property(readwrite, assign) NSOperationQueue* presentedItemOperationQueue;

-(instancetype)initWithImageUrl:(NSURL*)imageUrl andSidecarExtension:(NSString*)newExt;
@end

@implementation SidecarPresenter

- (instancetype)initWithImageUrl:(NSURL*)imageUrl andSidecarExtension:(NSString*)newExt
{
    self = [super init];

    if (self)
    {
        [self setPrimaryPresentedItemURL:imageURL];
        [self setPresentedItemURL:[[imageUrl URLByDeletingPathExtension] URLByAppendingPathExtension:newExt]];
        [self setPresentedItemOperationQueue:[NSOperationQueue mainQueue]];
    }

    return self;
}

- (void)dealloc
{
    [_primaryPresentedItemURL release];
    [_presentedItemURL release];

    [super dealloc];
}

@end
#endif

And later:

#ifdef PLATFORM_MAC_OS_X
SidecarPresenter* presenter = [SidecarPresenter alloc];
[presenter initWithImageUrl:[NSURL fileURLWithPath:documentFilename]
        andSidecarExtension:sidecarExtension]];
[presenter autorelease];

[NSFileCoordinator addFilePresenter:presenter];
NSFileCoordinator* coordinator = [[[NSFileCoordinator alloc] initWithFilePresenter:presenter] autorelease];

NSError* error = nil;
[coordinator coordinateReadingItemAtURL:presenter.presentedItemURL
                                options:NSFileCoordinatorReadingWithoutChanges
                                  error:&error
                             byAccessor:^(NSURL* newURL)
{
   std::ifstream strm([newURL fileSystemRepresentation]);
   foo(strm);
}];

[NSFileCoordinator removeFilePresenter:presenter];

#else
std::ifstream strm(documentFilename);
foo(strm);
#endif

In this way, there's no ping-ponging back and forth between the back- and front-end. And the lambda is invoked synchronously so you don't have to worry about race conditions, either (just a bit of extra latency, potentially). The only cost is a bit of platform-specific leakage but at least it's hidden away inside a preprocessor directive.



来源:https://stackoverflow.com/questions/59566959/is-there-an-alternative-to-nsfilecoordinator-for-opening-related-files-in-a-sand

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!