Search code examples
iosobjective-cfileprovider-extension

File Provider Extension, importDocumentAtURL:: can't read file at given URL (iOS 11.4.1)


I'm having troubles with Paste opertaions into my containers in File Provider extension.

If I paste copied image or text into Files app -> My App -> any folder the file at fileURL can not be read (as a result can't be uploaded to my servers nor stored locally).

- (void)importDocumentAtURL:(NSURL *)fileURL
     toParentItemIdentifier:(NSFileProviderItemIdentifier)parentItemIdentifier
          completionHandler:(void (^)(NSFileProviderItem _Nullable importedDocumentItem, NSError * _Nullable error))completionHandler
{

    NSError *readError = nil;
    NSData *fileData = [NSData dataWithContentsOfURL:fileURL options:NSDataReadingMappedAlways error:&readError];
    NSString *readErrorMessage = readError.localizedDescription;

    NSURL *myFileURL = [NSFileProviderManager.defaultManager.documentStorageURL URLByAppendingPathComponent:@"temp.dat"];
    NSError *copyError = nil;
    BOOL copyResult = [_fileManager copyItemAtURL:fileURL toURL:myFileURL error:&copyError];
    NSString *copyErrorMessage = copyError.localizedDescription;

    ...

Both readErrorMessage and copyErrorMessage are:

The file “text.txt” couldn’t be opened because you don’t have permission to view it.

What am I doing wrong here?

Thanks.

UPD: This happens to any file copied from my container, iCloud container, as well as synthetic files produced from text/image/other data from system Clipboard.


Solution

  • Looks like you are working on a security-scoped URL.

    According to Document Picker Programming Guide

    Any app that accesses documents outside its sandbox must meet the following requirements:

    • Your app must perform all file read and write operations using file coordination.

    • If you display the contents of the document to the user, you must track the document’s state using a file presenter. If you’re only showing a list of files, a file presenter is not necessary.

    • Do not save any URLs accessed through open or move operations. Always open the document using a document picker, a metadata query, or a security-scoped bookmark to the URL.

    • These operations return security-scoped URLs. You must call startAccessingSecurityScopedResource before accessing the URL.

    • If startAccessingSecurityScopedResource returns YES, call stopAccessingSecurityScopedResource when you are done using the file.

    • If you are using a UIDocument subclass, it will automatically consume the security-scoped URLs for you. There’s no need to call startAccessingSecurityScopedResource or stopAccessingSecurityScopedResource. UIDocument also acts as a file presenter and automatically handles file coordination. For these reasons, using a UIDocument subclass is highly recommended for all files outside your app’s sandbox.

    So you need to call startAccessingSecurityScopedResource before the file at this url is copied. Your code may become.

    - (void)importDocumentAtURL:(NSURL *)fileURL
         toParentItemIdentifier:(NSFileProviderItemIdentifier)parentItemIdentifier
              completionHandler:(void (^)(NSFileProviderItem _Nullable importedDocumentItem, NSError * _Nullable error))completionHandler
    {
    
      NSError *readError = nil;
      NSData *fileData = [NSData dataWithContentsOfURL:fileURL options:NSDataReadingMappedAlways error:&readError];
      NSString *readErrorMessage = readError.localizedDescription;
    
      NSURL *myFileURL = [NSFileProviderManager.defaultManager.documentStorageURL URLByAppendingPathComponent:@"temp.dat"];
    
      // Call |startAccessingSecurityScopedResource| before working on the url
      [fileURL startAccessingSecurityScopedResource];
    
      NSError *copyError = nil;
      BOOL copyResult = [_fileManager copyItemAtURL:fileURL toURL:myFileURL error:&copyError];
      NSString *copyErrorMessage = copyError.localizedDescription;
    
      // ....
      // Call |stopAccessingSecurityScopedResource| after everything is done.
      [fileURL stopAccessingSecurityScopedResource];
    }