cocoa nsurl security-scoped-bookmarks

cocoa - ¿Cuál es la forma correcta de manejar los marcadores NSURL obsoletos?



security-scoped-bookmarks (1)

Al resolver un NSURL desde un marcador de ámbito de seguridad, si el usuario ha renombrado o movido ese archivo o carpeta, el marcador quedará obsoleto. El documento de Apple dice esto con respecto a la caducidad:

isStale

A su regreso, si es SÍ, la información del marcador está obsoleta. Su aplicación debe crear un nuevo marcador usando la URL devuelta y usarla en lugar de cualquier copia almacenada del marcador existente.

Desafortunadamente, esto rara vez funciona para mí. Puede funcionar el 5% del tiempo. Intentar crear un nuevo marcador utilizando la URL devuelta da como resultado un error, código 256, y al buscar en la consola, se muestra un mensaje de sandboxd que dice "denegar archivo-lectura-datos" en la URL actualizada.

Nota Si la regeneración del marcador funciona, parece que solo funciona la primera vez que se regenera. Parece que nunca funcionará si la carpeta / archivo se mueve / renombra de nuevo.

Cómo creo y almaceno el marcador

-(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"]; } }]; }

Código que resuelve el marcador

-(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); } }

Cuando el marcador está obsoleto, la URL resuelta resultante apunta a la ubicación correcta, no puedo acceder al archivo a pesar de que [url startAccessingSecurityScopedResource] devuelve SÍ.

Tal vez estoy malinterpretando la documentación sobre marcadores obsoletos, pero espero que esté haciendo algo estúpido. Hacer estallar un NSOpenPanel cada vez que se cambia el nombre o se mueve un archivo / carpeta marcado, mi única otra opción en este momento, parece ridículo.

Debo agregar que tengo com.apple.security.files.bookmarks.app-scope , com.apple.security.files.user-selected.read-write y com.apple.security.app-sandbox todo configurado en true en mi archivo de derechos.


Después de muchas pruebas decepcionantes, he llegado a las siguientes conclusiones. Aunque lógico, son decepcionantes ya que la experiencia resultante para los usuarios dista mucho de ser ideal y un dolor significativo para los desarrolladores, dependiendo de qué tan lejos estén dispuestos a ayudar a los usuarios a restablecer las referencias a los recursos marcados.

Cuando digo "renovar" a continuación, me refiero a "generar un nuevo marcador para reemplazar un marcador obsoleto utilizando la URL resuelta del marcador obsoleto".

  1. La renovación siempre funciona siempre que el recurso marcado como favorito se mueva o cambie de nombre dentro de un directorio al que su aplicación ya tenga permiso de acceso. Por lo tanto, de manera predeterminada, siempre funciona dentro de la carpeta del contenedor de la aplicación.

  2. La renovación falla si un recurso marcado como favorito se mueve a una carpeta a la que su aplicación no tiene permiso de acceso. por ejemplo, el usuario arrastra una carpeta desde su carpeta de contenedor a alguna carpeta fuera de la carpeta del contenedor. Podrá resolver la URL, pero no acceder ni renovar el marcador.

  3. La renovación falla si un recurso marcado como favorito vive en una carpeta a la que su aplicación no tiene acceso y luego se renombra. Esto significa que un usuario puede otorgar explícitamente el acceso de su aplicación a un recurso, y luego revocar inadvertidamente ese acceso con solo cambiarle el nombre.

  4. La resolución falla si un recurso se mueve a otro volumen. No estoy seguro de si esto es una limitación de los marcadores en general o solo cuando se usa en una aplicación de espacio aislado.

Para los números 2 y 3 está en una posición decente ya que el desarrollador desde la resolución de la URL marcada funciona. Al menos, puede dirigir al usuario diciéndole exactamente qué recursos necesita para garantizar el acceso de su aplicación y dónde se encuentran. La experiencia podría mejorarse al hacer que seleccionen una carpeta que contenga (directa o indirectamente) todos los recursos para los que necesite renovar un marcador. Incluso podría ser el volumen, que resuelve el problema completamente si están dispuestos a darle a su aplicación este acceso.

Para el problema 4, la resolución no funciona en absoluto. El usuario tendrá que reubicar el archivo sin ninguna pista ya que no puede resolver la nueva ubicación. Una cosa que he hecho en mi aplicación actual y que ha reducido el dolor de este problema es agregar un atributo extendido a cualquier recurso para el que guarde un marcador. Hacer esto al menos me permite hacer que el usuario elija una carpeta para buscar recursos previamente asociados.

Limitaciones frustrantes, pero los marcadores aún ganan al almacenar rutas estáticas.