Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Prevent warning when NSDocument file is programmatically renamed

My application allows the user to rename documents that are currently open. This is trivial, and works fine, with one really annoying bug I can't figure out. When a file is renamed, AppKit (kindly) warns the user the next time they try to save the document. The user says "OK" and everything continues as normal. This makes sense when something external to the application changed the document, but not when it was actually done by the document itself.

The code goes something like this:

-(void)renameDocumentTo:(NSString *)newName {
  NSURL *newURL = [[[self fileURL] URLByDeletingLastPathComponent]
                                   URLByAppendingPathComponent:newName];

  NSFileManager *fileManager = [NSFileManager defaultManager];
  [fileManager moveItemAtURL:[self fileURL] toURL:newURL];
  NSDictionary *attrs = [fileManager attributesForItemAtPath:[newURL path] error:NULL];

  [self setFileURL:newURL];
  [self setFileModificationDate:[attrs fileModificationDate]];
}

One would think that expressly setting the new URL and modification date on the document would be enough, but sadly it's not. Cocoa still generates the warning.

I've tried changing the order (setting the new URL on the document, THEN renaming the file) but this doesn't help.

I've also tried a fix suggested by a user on an old post over at CocoaDev:

[self performSelector:@selector(_resetMoveAndRenameSensing)];

Even this does not stop the warning however, and I'm guessing there has to be a proper way to do this using the documented API. How does Xcode handle things when a user clicks a file on the project tree and renames it to something else. It doesn't warn the user about the rename, since the user actually performed the rename.

What do I need to do?

like image 249
d11wtq Avatar asked Dec 03 '10 09:12

d11wtq


2 Answers

There isn't much on this in the main docs. Instead, have a look at the 10.5 release notes: http://developer.apple.com/library/mac/#releasenotes/Cocoa/AppKitOlderNotes.html%23X10_5Notes under the heading "NSDocument Checking for Modified Files At Saving Time"

(In the case of Xcode, it has a long history and I wouldn't be surprised if if doesn't use NSDocument for files within the project)

It is worth noting that moving a file does not change its modification date, so calling -setFileModificationDate: is unlikely to have any effect.

So one possibility could be to bypass NSDocument's usual warning like so:

- (void)saveDocument:(id)sender;
{
    if (wasRenamed)
    {
        [self saveToURL:[self fileURL] ofType:[self fileType] forSaveOperation:NSSaveOperation delegate:nil didSaveSelector:nil contextInfo:NULL];
        wasRenamed = NO;
    }
    else
    {
        [super saveDocument:sender];
    }
}

Ideally you also need to check for the possibility of:

  1. Ask app to rename the doc
  2. Renamed file is then modified/moved by another app
  3. User goes to save the doc

At that point you want the usual warning sheet to come up. Could probably be accomplished by something like:

- (void)renameDocumentTo:(NSString *)newName
{
    // Do the rename

    [self setFileURL:newURL];
    wasRenamed = YES; // MUST happen after -setFileURL:
}

- (void)setFileURL:(NSURL *)absoluteURL;
{
    if (![absoluteURL isEqual:[self fileURL]]) wasRenamed = NO;
    [super setFileURL:absoluteURL];
}

- (void)setFileModificationDate:(NSDate *)modificationDate;
{
    if (![modificationDate isEqualToDate:[self fileModificationDate]]) wasRenamed = NO;
    [super setFileModificationDate:modificationDate];
}

Otherwise, your only other choice I can see is to call one of the standard save/write methods with some custom parameters that prompt your document subclass to move the current doc rather than actually save it. Would be trickier I think. Perhaps define your own NSSaveOperationType?

With this technique the doc system should understand that the rename was part of a save-like operation, but it would need quite a bit of experimentation to be sure.

like image 54
Mike Abdullah Avatar answered Sep 28 '22 03:09

Mike Abdullah


Much inspired from @Mike's answer, I got the "moved to" message not to show up anymore by re-routing NSSaveOperation to NSSaveAsOperation. In my NSDocument subclass:

  • I overload saveDocumentWithDelegate:didSaveSelector:contextInfo: to determine the save URL and document type (assigning those to self); if the old fileURL exists, I move that to the new location
  • Inside saveDocumentWithDelegate:didSaveSelector:contextInfo: I redirect the call to [self saveToURL:self.fileURL ofType:self.fileType forSaveOperation:NSSaveAsOperation completionHandler: ...] instead of [super saveDocumentWithDelegate:didSaveSelector:contextInfo:]

This works for me.

like image 27
Alessandro Avatar answered Sep 28 '22 03:09

Alessandro