views:

238

answers:

3

I'm working on a document-based application, and I want to use a document package as my file format. To do that, it seems that the NSDocument method I need to override is
-writeToURL:ofType:error:.

It sometimes works, but only under certain conditions. For example, this code works:

- (BOOL)writeToURL:(NSURL *)absoluteURL ofType:(NSString *)typeName error:(NSError **)outError
{   
    NSFileWrapper *wrapper = [[NSFileWrapper alloc] initDirectoryWithFileWrappers:nil];
    [wrapper addRegularFileWithContents:[@"please work" dataUsingEncoding:NSUTF8StringEncoding] preferredFilename:@"foobar"];
    [wrapper writeToURL:absoluteURL options:NSFileWrapperWritingAtomic originalContentsURL:nil error:outError];

    NSDictionary *metadata = [NSDictionary dictionaryWithObject:@"0.1" forKey:@"Version"];
    NSURL *mdURL = [NSURL fileURLWithPath:[[absoluteURL path] stringByAppendingPathComponent:@"SiteInfo.plist"]];
    [metadata writeToURL:mdURL atomically:YES];

    return YES; 
}

But, this code does not (it's the same as above, but with the NSFileWrapper bit taken out):

- (BOOL)writeToURL:(NSURL *)absoluteURL ofType:(NSString *)typeName error:(NSError **)outError
{   
    NSDictionary *metadata = [NSDictionary dictionaryWithObject:@"0.1" forKey:@"Version"];
    NSURL *mdURL = [NSURL fileURLWithPath:[[absoluteURL path] stringByAppendingPathComponent:@"SiteInfo.plist"]];
    [metadata writeToURL:mdURL atomically:YES];

    return YES; 
}

The above code puts this cryptic error into the console ("Lithograph" is the name of my app, and ".site" is the package extension):

NSDocument could not delete the temporary item at file://localhost/private/var/folders/qX/qXL705byGmC9LN8FpiVjgk+++TI/TemporaryItems/(A%20Document%20Being%20Saved%20By%20Lithograph%207)/Untitled%20Site.site. Here's the error:
Error Domain=NSCocoaErrorDomain Code=4 UserInfo=0x10059d160 "“Untitled Site.site” couldn’t be removed."

Do I have to write something to the original URL before I can add other files to the package?

A: 

I was able to solve my own problem! By adding this line of code at the top of the method, I'm able to manipulate whatever files I want in the package:

[[NSFileManager defaultManager] createDirectoryAtPath:[absoluteURL path] withIntermediateDirectories:YES attributes:nil error:nil];
Justin Voss
Never pass `nil` for `error:`. First off, it's a pointer to a pointer, not a pointer to an object, so the proper null-value is `NULL`. More importantly, you are hiding errors from the user. If they don't have permission to write to that location, then that message will try to give you an error object, but you've declined to receive it, so you can't present it, so **your app will silently fail to save**.
Peter Hosey
A: 

writeToURL... method implementations should always create a complete document. Your second version doesn't seem to do that, so I'm not sure why you think it should work.

Azeem.Butt
+2  A: 

If you are creating a file wrapper from your document, you should use -fileWrapperOfType:error: instead of -writeToURL:ofType:error:.

You would construct a file wrapper for the Info.plist file, insert it into the folder file wrapper and then return the folder wrapper:

- (NSFileWrapper *)fileWrapperOfType:(NSString *)typeName error:(NSError **)outError
{   
    NSFileWrapper *wrapper = [[[NSFileWrapper alloc] initDirectoryWithFileWrappers:nil] autorelease];
    [wrapper addRegularFileWithContents:[@"please work" dataUsingEncoding:NSUTF8StringEncoding] preferredFilename:@"foobar"];
    NSDictionary *metadata = [NSDictionary dictionaryWithObject:@"0.1" forKey:@"Version"];
    NSString* errorDescription = nil;
    NSData* dictionaryData = [NSPropertyListSerialization dataFromPropertyList:metadata format:NSPropertyListBinaryFormat_v1_0 errorDescription:&errorDescription];
    if(!dictionaryData)
    {
        if(!errorDescription)
            errorDescription = @"Unknown error";
        if(error)
            *error = [NSError errorWithDomain:@"YourErrorDomain" code:69 userInfo:[NSDictionary dictionaryWithObject:errorDescription forKey:NSLocalizedDescription]];
        return nil;
    }
    [wrapper addRegularFileWithContents:dictionaryData preferredFilename:@"Info.plist"];
    return wrapper;
}
Rob Keniger
Thanks for your help! I didn't know about `NSPropertyListSerialization`; that should come in handy.
Justin Voss