Bob Vork
Bob Vork

Reputation: 2947

NSFileWrapper, lazy loading and saving

I have an NSDocument based application that uses filewrappers to save and load its data. The document can have all kinds of resources, so I don't want to load everything into memory. I might be doing something fundamentally wrong, but as soon as I change one (inner) file and then save, I can't read any file that hasn't been loaded into memory.

I have separated the relevant code into a separate project to reproduce this behaviour, and I get the same results. The basic flow is this:

Here is the relevant code in my project:

This code might be easier to read in this gist: https://gist.github.com/bob-codingdutchmen/6869871

#define FileName01 @"testfile1.txt"
#define FileName02 @"testfile2.txt"

/**
 *  Only called when initializing a NEW document
 */
-(id)initWithType:(NSString *)typeName error:(NSError *__autoreleasing *)outError {
    self = [self init];
    if (self) {
        self.myWrapper = [[NSFileWrapper alloc] initDirectoryWithFileWrappers:nil];

        NSLog(@"Initializing new document...");

        NSString *testString1 = @"Lorem ipsum first sub file";
        NSString *testString2 = @"This is the second sub file with completely unrelated contents";

        NSFileWrapper *w1 = [[NSFileWrapper alloc] initRegularFileWithContents:[testString1 dataUsingEncoding:NSUTF8StringEncoding]];
        NSFileWrapper *w2 = [[NSFileWrapper alloc] initRegularFileWithContents:[testString2 dataUsingEncoding:NSUTF8StringEncoding]];

        w1.preferredFilename = FileName01;
        w2.preferredFilename = FileName02;

        [self.myWrapper addFileWrapper:w1];
        [self.myWrapper addFileWrapper:w2];

    }
    return self;
}

-(NSFileWrapper *)fileWrapperOfType:(NSString *)typeName error:(NSError *__autoreleasing *)outError {

    // This obviously wouldn't happen here normally, but it illustrates
    // how the contents of the first file would be replaced
    NSFileWrapper *w1 = [self.myWrapper.fileWrappers objectForKey:FileName01];
    [self.myWrapper removeFileWrapper:w1];

    NSFileWrapper *new1 = [[NSFileWrapper alloc] initRegularFileWithContents:[@"New file contents" dataUsingEncoding:NSUTF8StringEncoding]];
    new1.preferredFilename = FileName01;

    [self.myWrapper addFileWrapper:new1];

    return self.myWrapper;
}

-(BOOL)readFromFileWrapper:(NSFileWrapper *)fileWrapper ofType:(NSString *)typeName error:(NSError *__autoreleasing *)outError {
    self.myWrapper = fileWrapper;
    return YES;
}

- (IBAction)button1Pressed:(id)sender {
    // Read from file1 and show result in field1
    NSFileWrapper *w1 = [[self.myWrapper fileWrappers] objectForKey:FileName01];
    NSString *string1 = [[NSString alloc] initWithData:w1.regularFileContents encoding:NSUTF8StringEncoding];
    [self.field1 setStringValue:string1];
}

- (IBAction)button2Pressed:(id)sender {
    // Read from file2 and show result in field2
    NSFileWrapper *w2 = [[self.myWrapper fileWrappers] objectForKey:FileName02];
    NSString *string2 = [[NSString alloc] initWithData:w2.regularFileContents encoding:NSUTF8StringEncoding];
    [self.field2 setStringValue:string2];
}

The bottom two methods are only for updating the UI so I can see what happens.

The Apple docs say that NSFileWrapper supports lazy loading and incremental saving, so I'm assuming that my code has some fundamental flaw that I can't see.

Upvotes: 13

Views: 2600

Answers (3)

macrene
macrene

Reputation: 314

Following the documentation to addFileWrapper: you add a child (subdirectory) to it, means

directory/

  1. addfileWrapper:fileName1 directory/fileName1/

  2. addfileWrapper:fileName2 directory/fileName1/fileName2. That file doesn't exist.

You have to use addRegularFileWithContents:preferredFilename: instead.

Upvotes: 0

tjw
tjw

Reputation: 972

In your -fileWrapperOfType:error: method, try building a new file wrapper that has new contents for the changed members and references the old file wrappers for the unchanged members.

Upvotes: 1

Cocoanetics
Cocoanetics

Reputation: 8247

An NSFileWrapper is essentially a wrapper around a unix file node. If the file is moved the wrapper stays valid.

The problem yo seem to have is that creating a new file wrapper during saving is a new folder. And the system deletes your previous wrapper including sub2.

To achieve what you want you need to change to incremental saving, i.e. Only saving changed parts in place. See "save in place" in NSDocument.

Upvotes: 1

Related Questions