Mas OS X: error in contentsOfDirectoryAtURL method - macos

I have a trouble, I have a folder url, folder, that stored on that url path is exist and it's ok. Problem is that contentsOfDirectoryAtURL:includingPropertiesForKeys:options:error: returns with an error.
My NSURL to folder is NSString, that made from another method that take NSURL and save it as absoluteString object.
Here is my code:
NSURL *folderURL = [NSURL fileURLWithPath:folderPathString isDirectory:YES];
if ([folderURL isFileURL]) {
NSLog(#"it's file"); // I see this in console
}
NSError *error;
// array of NSURL objects
NSArray *contentOfFolder = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:folderURL
includingPropertiesForKeys:#[NSURLContentModificationDateKey,NSURLFileResourceTypeKey, NSURLLocalizedNameKey]
options:NSDirectoryEnumerationSkipsHiddenFiles
error:&error];
if (error) {
NSLog(#"%#",error);
}
This is a part of my method, in console, I see an error:
Error Domain=NSCocoaErrorDomain Code=260 "The file “myFolder” couldn’t be opened because there is no such file." UserInfo=0x10051b0a0 {NSURL=file:/localhost/Users/myUser/myRootFolder/myFolder/ -- file://localhost/Users/myUser/Library/Developer/Xcode/DerivedData/myProject-algooymkavrtmlchwnlbrmvcbvzj/Build/Products/Debug/, NSFilePath=/Users/myUser/Library/Developer/Xcode/DerivedData/myProject-algooymkavrtmlchwnlbrmvcbvzj/Build/Products/Debug/file:/localhost/Users/myUser/myRootFolder/myFolder, NSUnderlyingError=0x100526f40 "The operation couldn’t be completed. No such file or directory"}
I don't understand why I get this error. How I can get No such file or directory error, if this directory exist?!
EDIT
I found that after method fileURLWithPath:isDirectory: my folder url looks strange, when I look at it with NSLog.
NSLog(#"folder url %#",folderURL);
output:
folder url file:/localhost/Users/myUser/myRootFolder/myFolder/
-- file://localhost/Users/myUser/Library/Developer/Xcode/DerivedData/myProject-algooymkavrtmlchwnlbrmvcbvzj/Build/Products/Debug/
Why the second part is appear? (part that starts with -- file://localhost/Users/myUser/Library/...). I think problem with this but what I do wrong? Is method fileURLWithPath:isDirectory: don't acceptable for my purposes?

The folderPathString in
NSURL *folderURL = [NSURL fileURLWithPath:folderPathString isDirectory:YES];
must be a simple path, e.g. "/path/to/dir". In your case, it is a string URL "file://localhost/path/to/dir", which is wrong.
I assume that folderPathString is created from some NSURL using
folderPathString = [anURL absoluteString];
This is wrong and should be
folderPathString = [anURL path];
It might also be possible to avoid the conversion from URL to string and back to URL
altogether.

Related

NSURLThumbnailDictionaryKey empty for local file

I want to get a thumbnail representation of a file I have to display in my app. I'm using NSURL here:
NSDictionary *thumbnails = nil;
BOOL success = [fileURL getResourceValue:&thumbnails
forKey:NSURLThumbnailDictionaryKey
error: &error];
This works fine if I am connected to iCloud, and the URL is a link to a file stored in iCloud. The fileURL is something like:
file:///Users/me/Library/Mobile%20Documents/BJXXGLR9R3~com~myapp~icloud/FileStorage/contact-page%20copy.png
If I use the same code with a NSURL pointing to a local file, however, the thumbnails dictionary is empty.
Here is an example of the URL in this case:
file:///Users/me/Library/Containers/com.mycompany.mymacapp/Data/Library/Application%20Support/com.mycompany.mymacapp/FileStorage/Bn4VaCnCUAEJjLb.png-large.png
Is this API for getResourceValue not supposed to work with locally stored files? Or am I doing something wrong?
This is part of the API. Furthermore you should use a File coordinator while working with files coming from iCloud:
[url startAccessingSecurityScopedResource];
NSFileCoordinator *coordinator = [[NSFileCoordinator alloc] init];
__block NSError *error;
[coordinator coordinateReadingItemAtURL:url options:0 error:&error byAccessor:^(NSURL *newURL) {
[newURL getResourceValue:&image forKey:NSURLThumbnailDictionaryKey error:&error];
}];
[url stopAccessingSecurityScopedResource];

dictionaryWithContentsOfFile and Sandbox

I've created a mac app that load a xml file from an user selected folder, and after using the app, the user saves a customized file (.adgf)
When i try to load the .adgf file (that is a plist file) that has the xml path within one record i call
dictionaryWithContentsOfFile but it return me a "nil". I think the problem is the sandbox (sometime it works sometime not). The string path is correct.
Maybe when the user load the xml file should i save within of particular app "Document folder"?
Edit:
I'm trying right now the Bookmark Data solution and I retraive a NSURL but it doen't work. The code I'm using is this:
- (NSData *)bookmarkFromURL:(NSURL *)url {
NSError *error = nil;
NSData *bookmark = [url bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope
includingResourceValuesForKeys:NULL
relativeToURL:NULL
error:&error];
if (error) {
NSLog(#"Error creating bookmark for URL (%#): %#", url, error);
[NSApp presentError:error];
}
return bookmark;
}
- (NSURL *)urlFromBookmark:(NSData *)bookmark {
NSURL *url = [NSURL URLByResolvingBookmarkData:bookmark
options:NSURLBookmarkResolutionWithSecurityScope
relativeToURL:NULL
bookmarkDataIsStale:NO
error:NULL];
return url;
}
After the user stores the file you should take the bookmark data from the URL using
-[NSURL bookmarkDataWithOptions: includingResourceValuesForKeys: relativeToURL: error:]
Use NSURLBookmarkCreationWithSecurityScope for the options.
This NSData object should be stored somewhere (plist?) and when you want to read the file again in a later session you can create a sandbox compliant NSURL from the bookmark data using +[NSURL
URLByResolvingBookmarkData:options:relativeToURL:bookmarkDataIsStale:error:]

What is the correct way to handle stale NSURL bookmarks?

When resolving an NSURL from a security scoped bookmark, if the user has renamed or moved that file or folder, the bookmark will be stale. Apple's document says this regarding staleness:
isStale
On return, if YES, the bookmark data is stale. Your app should
create a new bookmark using the returned URL and use it in place of
any stored copies of the existing bookmark.
Unfortunately, this rarely works for me. It may work 5% of the time. Attempting to create a new bookmark using the returned URL results in an error, code 256, and looking in Console reveals a message from sandboxd saying deny file-read-data on the updated URL.
Note If regenerating the bookmark does work, it seems to only work the first time it is regenerated. It seems to never work should the folder/file be moved/renamed again.
How I initially create & store the bookmark
-(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"];
}
}];
}
Code that resolves the 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);
}
}
When the bookmark is stale, the resulting resolved URL does point to the correct location, I just can't actually access the file despite the fact that [url startAccessingSecurityScopedResource] returns YES.
Perhaps I'm misinterpreting the documentation regarding stale bookmarks, but I'm hoping I'm just doing something stupid. Popping an NSOpenPanel each time a bookmarked file/folder is renamed or moved, my only other option at this point, seems ridiculous.
I should add that I have com.apple.security.files.bookmarks.app-scope, com.apple.security.files.user-selected.read-write, and com.apple.security.app-sandbox all set to true in my entitlements file.
After a lot of disappointing testing I've come to the following conclusions. Though logical, they're disappointing since the resulting experience for users is far from ideal and a significant pain for developers depending on how far they're willing to go to help users re-establish references to bookmarked resources.
When I say "renew" below, I mean "generate a new bookmark to replace a stale bookmark using the URL resolved from the stale bookmark."
Renewal always works as long as the bookmarked resource is moved or renamed within a directory that your app already has permission to access. So, by default, it always works inside your application's container folder.
Renewal fails if a bookmarked resource is moved into a folder your application does not have permission to access. e.g. User drags a folder from your container folder to some folder outside the container folder. You will be able to resolve the URL, but not access nor renew the bookmark.
Renewal fails if a bookmarked resource lives in a folder your application doesn't have access to and is then renamed. This means a user can explicitly grant your application access to a resource, then inadvertently revoke that access just by renaming it.
Resolution fails if a resource is moved to another volume. Not sure if this is a limitation of bookmarks in general or just when used in a sandboxed application.
For issues 2 & 3 you're in a decent position as the developer since resolution of the bookmarked URL does work. You can at least lead the user by telling them exactly which resources they need to grant your app access to and where they are. The experience could be improved by having them select a folder that contains (directly or indirectly) all resources that you need to renew a bookmark for. This could even be the volume, which solves the problem completely if they're willing to give your application this much access.
For issue 4, resolution doesn't work at all. The user will have to relocate the file without any hints since you can't resolve the new location. One thing I've done in my current app that has reduced the pain of this issue is to add an extended attribute to any resource I store a bookmark for. Doing this at least lets me have the user choose a folder to search for previously associated resources.
Frustrating limitations, but bookmarks still win over storing static paths.

Storing UIManagedDocuments when uibiquity container (iCloud) is not available

I've managed to understand how to incorporate UIManagedDocument into a simple test application and it works as expected! However, now I'm adding support to this basic application so it will work if the user does not want to use iCloud.
So when the URLForUbiquityContainerIdentifier: method returns 'nil', I return the URL of the local documents directory using the suggested method
NSString *documentsDirectoryPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
return [NSURL fileURLWithPath:documentsDirectoryPath];
However, when I try saving a UIManagedDocument to the local URL (such as: file://localhost/var/mobile/Applications/some-long-identifier/Documents/d.dox) I get the following error:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'This NSPersistentStoreCoordinator has no persistent stores. It cannot perform a save operation.'
Using this save method:
if (![[NSFileManager defaultManager] fileExistsAtPath:self.managedDocument.fileURL.path]) {
[self.documentDatabase saveToURL:self.managedDocument.fileURL
forSaveOperation:UIDocumentSaveForCreating
completionHandler:^(BOOL success) {
if (success) {
//
// Add default database stuff here.
//
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self.documentDatabase.managedObjectContext performBlock:^{
[Note newNoteInContext:self.managedDocument.managedObjectContext];
}];
});
} else {
NSLog(#"Error saving %#", self.managedDocument.fileURL.lastPathComponent);
}
}];
}
It turns out my persistent store options contained the keys used for the ubiquitous store. These shouldn't be in the documents persistent store options.

What is the encoding for URL bookmarks stored as NSData?

What is the best way to get the path from the NSData bookmark object, if the bookmark will not resolve?
Normally, you just resolve the bookmark, you get a URL, and off you go. But if the bookmark is to an NFS mount that is not currently present, it won't resolve. So now I have an NSData pointing somewhere that won't resolve, but I don't know where it points.
Here is the code block I have that loads the bookmarks, tries to resolve them, and attempts to decode the NSData if the resolve fails, but I can't figure out the encoding - is this even possible?
NSError* error = [[NSError alloc] init];
NSURL* resolvedURL = [NSURL URLByResolvingBookmarkData:bookmarkData
options:NSURLBookmarkResolutionWithSecurityScope | NSURLBookmarkResolutionWithoutUI
relativeToURL:nil
bookmarkDataIsStale:NULL
error:&error];
if (resolvedURL) {
// do some stuff
...
} else {
NSString* msg = [NSString stringWithFormat:#"Error Resolving Bookmark: %#", error];
NSLog(msg);
// the below certainly doesn't get me a path from the bookmark, any idea what will?
// NSString* path = [[NSString alloc] initWithData:bookmarkData encoding:NSUTF32StringEncoding];
}
I never did figure out the encoding, but I found a workaround.
Originally, I encoded the sandboxed NSURLs into NSData objects, and then stored those as an NSArray in NSDefaults. Therefore, I had no way to determine the path for the NSData, unless it would resolve.
The workaround was to change the design - now I encode the sandboxed NSURL, store it as an object into an NSDictionary with the key being the URL path, and store the NSDictionary in NSDefaults.
With this approach, I can easily retrieve the NSData for any given path, even if it will not resolve.

Resources