Изолированное приложение MacOS: доступ к файлам без NSOpenPanel

В изолированном приложении на основе NSDocument к любому совместимому документу можно получить доступ с помощью NSOpenPanel, независимо от того, где документ сохранен. Без NSOpenPanel приложение может получить доступ только к файлам в контейнере песочницы.

Поскольку мое приложение управляет двумя типами подклассов NSdocument (текст как средство чтения/записи и изображение только как средство чтения), я пытаюсь реализовать отдельное меню «Открыть последние» для изображений. Я отключил обычное поведение для них, когда они открываются пользователем, переопределив метод noteNewRecentDocumentURL: (NSURL *)url NSDocumentController, чтобы возвращать НЕТ для URL-адресов изображений. Так что только текстовые документы появляются в обычном меню «Файл» -> «Открыть последние» (и открываются нормально, когда пользователь выбирает их). Изображения перечислены в пользовательском меню.

Проблема возникает с этими URL-адресами изображений, потому что приложение изолировано: приложение не может напрямую открыть любой файл изображения, указанный в специальном меню (любая операция чтения возвращает ошибку -54. Это поведение можно проверить, используя:

[[NSFileManager defaultManager] isReadableFileAtPath:[fileURL path]]

который всегда возвращает FALSE в этой ситуации. Есть только одно исключение: когда я повторно открываю из специального меню Open Recent файл, который ранее был открыт с помощью NSOpenPanel в том же сеансе приложения, а затем закрывался: в этом случае isReadableFileAtPath: возвращает TRUE, и к файлу можно получить доступ . Но когда приложение закрывается и перезапускается, последние файлы изображений не могут быть доступны таким образом.

Я определил три решения для решения этой проблемы:

  1. Перемещение файла изображения в контейнер песочницы, как только пользователь получил к нему "законный" доступ через NSOpenPanel. Работает, конечно, но не дает пользователю самостоятельно определять местонахождение своих файлов! Точно так же дублирование файла в песочнице не является решением.

  2. Создание псевдонима для этих файлов в песочнице. Поскольку я не мог найти способ сделать это, я не мог проверить, является ли это решением или нет.

  3. Отключите песочницу приложения. Но это худшее решение, так как есть много причин использовать песочницу!

Есть ли четвертое решение, которое разрешило бы доступ только для чтения к любому файлу изображения, где бы он ни находился, без отключения песочницы?


person Denis    schedule 17.01.2019    source источник


Ответы (2)


Вы не можете получить доступ ни к одному файлу, несмотря ни на что.

Также я не уверен, что означает ваше второе решение, возможно, поэтому вы не смогли ему следовать. Вы, вероятно, хотели обратиться к «закладкам в области безопасности», а не к «псевдонимам», и они работают очень хорошо, и это путь, по которому вы должны следовать.

person Ivan Ičin    schedule 17.01.2019
comment
Спасибо. Я не знал об этих закладках. Они кажутся очень сложными в управлении, но я попробую. - person Denis; 17.01.2019

Что ж, предложение Ивана было превосходным. После нескольких чтений (менее часа) я смог реализовать эти закладки в области безопасности. Для заинтересованных, вот основные выводы.

  1. добавьте эту функцию в файл прав вашего изолированного приложения, установите для ключа com.apple.security.files.bookmarks.document-scope (или com.apple.security.files.bookmarks.app-scope или обоих) значение TRUE.

  2. Измените метод открытия документа (который вызывает NSOpenPanel) следующим образом:

-(void) openMyDocument:(id)sender{

      // ... do your stuff

    [self.panel beginWithCompletionHandler:^(NSInteger result) {
        if (result == NSModalResponseOK) {
            NSURL* selectedURL = [[self.panel URLs] objectAtIndex:0];            
            NSData *bookmark = nil;
            NSError *error = nil;
            bookmark = [selectedURL bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope
                     includingResourceValuesForKeys:nil
                                      relativeToURL:nil // Make it app-scoped
                                              error:&error];
            if (error) {
                NSLog(@"Error while creating bookmark for URL (%@): %@", selectedURL, error);
            }
            NSString *access = [NSString stringWithFormat:@"%@%@", @"Access:", [selectedURL path]];
            [[NSUserDefaults standardUserDefaults] setObject:bookmark forKey:access];
            [[NSUserDefaults standardUserDefaults] synchronize];

            // ... then open the document your way
        }
    }
}
  1. Измените созданный вами метод для чтения файла без использования NSOpenPanel.
- (void) openDocumentForScopedURL: (NSURL *) fileURL

        NSString *accessKey = [NSString stringWithFormat:@"%@%@", @"Access:", [fileURL path]];
        NSData *bookmarkData = [[NSUserDefaults standardUserDefaults] objectForKey:accessKey];
        NSURL *bookmarkFileURL = nil;
        if (bookmarkData == nil){
            // no secured-scoped bookmark found, alert the user
            return;
        } else {
            NSError *error = nil;
            BOOL bookmarkDataIsStale;

            bookmarkFileURL = [NSURL
                               URLByResolvingBookmarkData:bookmarkData
                               options:NSURLBookmarkResolutionWithSecurityScope
                               relativeToURL:nil
                               bookmarkDataIsStale:&bookmarkDataIsStale
                               error:&error];
            [bookmarkFileURL startAccessingSecurityScopedResource];
        }


        // ... Then open your file, using bookmarkFileURL
        // ... and do your stuff

        // IMPORTANT. You must notify that stopped to access

        [bookmarkFileURL stopAccessingSecurityScopedResource];            
}
person Denis    schedule 17.01.2019
comment
Хорошее решение. К сожалению, он не работает на UIKit для Mac/Catalyst. В нем говорится, что NSURLBookmarkResolutionWithSecurityScope (но в Swift) недоступен в UIKit для Mac :( - person Sunkas; 12.10.2019
comment
Сейчас! а если вы используете более раннюю версию SDK, используйте это: gist.github.com/steipete/40a367b64b57bfd0b44fa8d158fc016c - person steipete; 10.03.2021