处理陈旧的NSURL书签的正确方法是什么?

时间:2014-05-30 12:28:48

标签: cocoa nsurl security-scoped-bookmarks

从安全范围书签解析NSURL时,如果用户重命名或移动了该文件或文件夹,则书签将过时。 Apple的文档说明了陈旧性:

  

isStale

     

返回时,如果是,则书签数据过时。你的应用应该   使用返回的URL创建一个新书签,并使用它代替   现有书签的任何存储副本。

不幸的是,这对我很少有用。它有可能在5%的时间内工作。尝试使用返回的URL 创建新书签会导致错误,代码256,并且在控制台中查看会显示来自沙盒的消息,说明在更新的URL上拒绝文件读取数据。

注意如果重新生成书签确实有效,它似乎只在第一次重新生成时才起作用。如果再次移动/重命名文件夹/文件,它似乎永远不会起作用。

我最初如何创建&存储书签

-(IBAction)bookmarkFolder:(id)sender {
  _openPanel = [NSOpenPanel openPanel];
  _openPanel.canChooseFiles = NO;
  _openPanel.canChooseDirectories = YES;
  _openPanel.canCreateDirectories = YES;
  [_openPanel beginSheetModalForWindow:self.window completionHandler:^(NSInteger result) {
    if (_openPanel.URL != nil) {
      NSError *error;
      NSData *bookmark = [_openPanel.URL bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope
                                  includingResourceValuesForKeys:nil
                                                   relativeToURL:nil
                                                           error:&error];
      if (error != nil) {
        NSLog(@"Error bookmarking selected URL: %@", error);
        return;
      }
      NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
      [userDefaults setObject:bookmark forKey:@"bookmark"];
    }
  }];
}

解析书签的代码

-(void)resolveStoredBookmark {
  NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
  NSData *bookmark = [userDefaults objectForKey:@"bookmark"];
  if (bookmark == nil) {
    NSLog(@"No bookmark stored");
    return;
  }
  BOOL isStale;
  NSError *error;
  NSURL *url = [NSURL URLByResolvingBookmarkData:bookmark
                                         options:NSURLBookmarkResolutionWithSecurityScope
                                   relativeToURL:nil
                             bookmarkDataIsStale:&isStale
                                           error:&error];
  if (error != nil) {
    NSLog(@"Error resolving URL from bookmark: %@", error);
    return;
  } else if (isStale) {
    if ([url startAccessingSecurityScopedResource]) {
      NSLog(@"Attempting to renew bookmark for %@", url);
      // NOTE: This is the bit that fails, a 256 error is 
      //       returned due to a deny file-read-data from sandboxd
      bookmark = [url bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope
               includingResourceValuesForKeys:nil
                                relativeToURL:nil
                                        error:&error];
      [url stopAccessingSecurityScopedResource];
      if (error != nil) {
        NSLog(@"Failed to renew bookmark: %@", error);
        return;
      }
      [userDefaults setObject:bookmark forKey:@"bookmark"];
      NSLog(@"Bookmark renewed, yay.");
    } else {
      NSLog(@"Could not start using the bookmarked url");
    }
  } else {
    NSLog(@"Bookmarked url resolved successfully!");
    [url startAccessingSecurityScopedResource];
    NSArray *contents = [NSFileManager.new contentsOfDirectoryAtPath:url.path error:&error];
    [url stopAccessingSecurityScopedResource];
    if (error != nil) {
      NSLog(@"Error reading contents of bookmarked folder: %@", error);
      return;
    }
    NSLog(@"Contents of bookmarked folder: %@", contents);
  }
}

当书签过时时,生成的已解析URL确实指向了正确的位置,尽管[url startAccessingSecurityScopedResource]返回YES,我实际上无法访问该文件。

也许我错误地解释了有关陈旧书签的文档,但我希望我只是做一些愚蠢的事情。每次重命名或移动书签文件/文件夹时弹出NSOpenPanel,此时我唯一的另一种选择,似乎很荒谬。

我应该补充一点,我有 com.apple.security.files.bookmarks.app-scope com.apple.security.files.user-selected.read-write ,和 com.apple.security.app-sandbox 都在我的权利文件中设置为true。

1 个答案:

答案 0 :(得分:19)

经过大量令人失望的测试后,我得出以下结论。虽然合乎逻辑,但它们令人失望,因为用户的体验远非理想,对开发人员来说是一个巨大的痛苦,取决于他们愿意帮助用户重新建立对书签资源的引用的程度。

当我在下面说“续订”时,我的意思是“生成一个新书签,使用从过时书签中解析的URL替换旧书签。”

  1. 只要在您的应用已有权访问的目录中移动或重命名已添加书签的资源,续订就会一直有效。因此,默认情况下,它始终在应用程序的容器文件夹中运行。

  2. 如果书签资源被移动到您的应用程序无权访问的文件夹中,则续订失败。例如用户将容器文件夹中的文件夹拖到容器文件夹外的某个文件夹中。您将能够解析URL,但不能访问或更新书签。

  3. 如果书签资源存在于您的应用程序无权访问的文件夹中,则重新生成失败,然后重命名。这意味着用户可以明确授予您的应用程序对资源的访问权限,然后只是通过重命名来无意中撤消该访问权。

  4. 如果将资源移动到另一个卷,则解析失败。不确定这是书签的一般限制还是仅在沙盒应用程序中使用时。

  5. 问题2& 3由于书签URL的解析确实有效,因此您作为开发人员处于一个不错的位置。您至少可以通过告诉用户他们授予您的应用访问权限以及他们所在位置所需的资源来引导用户。通过让他们选择包含(直接或间接)更新书签所需的所有资源的文件夹,可以改善体验。这甚至可以是卷,如果他们愿意为您的应用程序提供这么多的访问权限,它就完全解决了问题。

    对于问题4,解决方案根本不起作用。由于无法解析新位置,用户必须在没有任何提示的情况下重新定位文件。我在当前的应用程序中做了一件事,减少了这个问题的痛苦,就是为我存储书签的任何资源添加扩展属性。这样做至少让我让用户选择一个文件夹来搜索以前关联的资源。

    令人沮丧的限制,但书签仍然胜过存储静态路径。