views:

95

answers:

2

I have a tool that writes package-style documents. It's implemented using NSDocument and overrides the following NSDocument methods:

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

- (BOOL)readFromFileWrapper:(NSFileWrapper *)fileWrapper
                    ofType:(NSString *)typeName
                     error:(NSError **)outError;

This is all lovely, except when I save a document that's under version control. (The .svn directories aren't preserved, etc.)

Is there a good recipe somewhere for making my documents play well with svn?

+1  A: 

I'm guessing your code works by creating a fresh file wrapper each time -fileWrapperOfType:error: is called. This is convenient, but disregards any additional files that may exist on disk inside the package.

So instead, what if you start by creating/using a file wrapper that refers to the existing contents on disk. Modify that wrapper to match the document's current state, and return the result. When that wrapper is written out to disk, svn files should be properly maintained.

Mike Abdullah
Nice, thanks! Yeah, it was a bit of a hassle to refactor everything to make this work… But once you know the requirement it's really a piece of cake!
Kaelin Colclasure
A: 

Here is my solution based on Mike's answer!

My document packages are bundles, with the usual hierarchical structure… So there are four directories that I mutate during saves:

  1. The save creates a new top-level (My.bundle)
  2. The Contents directory is changed (My.bundle/Contents)
  3. The Resources directory is changed (My.bundle/Contents/Resources)
  4. The localized resources are updated (My.bundle/Contents/Resources/en.lproj)

The recipe starts with adding a mutable dictionary slot to your document class to preserve the contents of each of these directories.

@interface LMDocument : NSDocument {
@private
    // All this is for preserving SCM artifacts across saves…
    NSMutableDictionary * bundleWrappers;
    NSMutableDictionary * contentsWrappers;
    NSMutableDictionary * resourcesWrappers;
    NSMutableDictionary * localizedWrappers;
}

When creating a new document, these start out as empty dictionaries.

- (id)init;
{
    if ((self = [super init]) != nil) {
        bundleWrappers = [[NSMutableDictionary alloc] initWithCapacity:0];
        contentsWrappers = [[NSMutableDictionary alloc] initWithCapacity:0];
        resourcesWrappers = [[NSMutableDictionary alloc] initWithCapacity:0];
        localizedWrappers = [[NSMutableDictionary alloc] initWithCapacity:0];
    }
    return self;
}

When reading in an existing document, replace these with mutable copies of the relevant fileWrapper contents.

- (BOOL)readFromFileWrapper:(NSFileWrapper *)fileWrapper
                     ofType:(NSString *)typeName
                      error:(NSError **)outError;
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    bundleWrappers = [[fileWrapper fileWrappers] mutableCopy];
    contentsWrappers = [[[bundleWrappers objectForKey:@"Contents"] fileWrappers] mutableCopy];
    resourcesWrappers = [[[contentsWrappers objectForKey:@"Resources"] fileWrappers] mutableCopy];
    localizedWrappers = [[[resourcesWrappers objectForKey:@"en.lproj"] fileWrappers] mutableCopy];
    NSFileWrapper * infoPlistWrapper = [contentsWrappers objectForKey:@"Info.plist"];
    [contentsWrappers removeObjectForKey:@"Info.plist"]; // Replaced during save…
    // …
    NSMutableDictionary * localizedWrappersCopy = [localizedWrappers mutableCopy];
    [localizedWrappers enumerateKeysAndObjectsUsingBlock:^(id key,
                                                           id obj,
                                                           BOOL * stop)
     {
         if (mumble) { // If it's a file that will be replaced during save…
             [localizedWrappersCopy removeObjectForKey:key]; // Replaced during save…
             // …
         }
     }];
    localizedWrappers = localizedWrappersCopy;
    [pool drain];
    return YES;
}

And finally, when saving a document use the dictionaries so painstakingly prepared.

- (NSFileWrapper *)fileWrapperOfType:(NSString *)typeName
                               error:(NSError **)outError;
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    NSFileWrapper * localizedWrapper =
    [[NSFileWrapper alloc] initDirectoryWithFileWrappers:localizedWrappers];
    [resourcesWrappers setObject:localizedWrapper
                          forKey:@"en.lproj"];
    NSFileWrapper * resourcesWrapper =
    [[NSFileWrapper alloc] initDirectoryWithFileWrappers:resourcesWrappers];
    [contentsWrappers setObject:resourcesWrapper
                         forKey:@"Resources"];
    NSFileWrapper * contentsWrapper =
    [[NSFileWrapper alloc] initDirectoryWithFileWrappers:contentsWrappers];
    // …
    for (id item in mumble) {
        NSString * filename = [item filename];
        NSData * data = [item data];
        [localizedWrapper addRegularFileWithContents:data
                                   preferredFilename:filename];
    }
    [contentsWrapper addRegularFileWithContents:[self infoPlistData]
                              preferredFilename:@"Info.plist"];
    [pool drain];
    [bundleWrappers setObject:contentsWrapper
                       forKey:@"Contents"];
    NSFileWrapper * bundleWrapper =
    [[[NSFileWrapper alloc] initDirectoryWithFileWrappers:bundleWrappers] autorelease];
    return bundleWrapper;
}

Now when a package document is edited, the application preserves any files it didn't add to the bundle, including SCM artifacts and "other" localizations!

Kaelin Colclasure