29

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

是陈旧的

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

不幸的是,这对我来说很少有用。它可能有 5% 的时间工作。尝试使用返回的 URL 创建新书签会导致错误(代码 256),并且在控制台中查看会显示来自 sandboxd 的消息,说在更新的 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-scopecom.apple.security.files.user-selected.read-writecom.apple.security.app-sandbox都设置为 true在我的权利文件中。

4

1 回答 1

25

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

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

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

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

  3. 如果已添加书签的资源位于您的应用程序无权访问的文件夹中并且随后被重命名,则续订将失败。这意味着用户可以显式授予您的应用程序对资源的访问权限,然后仅通过重命名即可无意中撤消该访问权限。

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

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

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

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

于 2014-08-11T16:01:36.820 回答