Сбой NSPersistentDocument при сохранении ранее заблокированного документа

При открытии заблокированного файла с помощью моего подкласса NSPersistentDocument я получаю в консоли следующее сообщение:

Попытка добавить файл только для чтения по пути [URL] для чтения/записи. Вместо этого добавьте его только для чтения. Это будет серьезной ошибкой в ​​будущем; необходимо указать NSReadOnlyPersistentStoreOption.

Заголовок окна документа: «(имя документа) — заблокировано». После того, как пользователь разблокирует его, внесет изменения, а затем попытается сохранить, сохранение завершается с ошибкой

Произошла ошибка при сохранении.

Похоже, что NSPersistentDocument не может распознать, что пользователь разблокировал документ, и не открывает его повторно в режиме чтения/записи. Это ошибка в NSPersistentDocument или я что-то упустил?

Я не переопределяю ни один из методов файлового ввода-вывода в NSPersistentDocument.


person Aderstedt    schedule 25.10.2015    source источник
comment
Мой текущий (очень уродливый) обходной путь - позволить делегату приложения закрыться, а затем снова открыть документ после того, как документ будет разблокирован.   -  person Aderstedt    schedule 26.10.2015
comment
Вам нужно предоставить больше информации. Например, существует несколько способов блокировки файла в OS X, и вы не предоставили достаточно информации для принятия такого решения. Предоставьте подробную информацию о том, как файл заблокирован, и о фактическом исходном коде, который не работает. Кроме того, возможно, укажите атрибуты для файла (файлов). Возможно, ls -lae@ в каталоге, содержащем файлы, также даст некоторое представление. Код, который вы используете для создания основного стека данных, также может дать ценную информацию. Кроме того, как пользователь разблокирует файл?   -  person Jody Hagins    schedule 27.10.2015
comment
Я имею в виду блокировку, которая автоматически применяется OS X после того, как файл не редактировался в течение длительного времени (недели?), и ее можно переключить, выбрав файл в Finder и выбрав «Просмотреть информацию», а затем щелкнув значок Флажок блокировки.   -  person Aderstedt    schedule 27.10.2015
comment
Пользователь может отключить блокировку в любом приложении на основе документов в OS X, щелкнув меню рядом с заголовком окна, где указано «Заблокировано».   -  person Aderstedt    schedule 27.10.2015


Ответы (1)


Ах, хорошо, автоматическая блокировка файлов.

Это происходит с автоматически сохраняемыми документами, к которым давно не обращались.

Типичный подход заключается в том, чтобы заметить блокировку перед созданием основного стека данных и открыть диалоговое окно с просьбой разблокировать файл.

Если они согласятся разблокировать файл, вы просто разблокируете его и запустите как обычно.

Если они не согласны разблокировать его, вы копируете его или открываете только для чтения. Конечно, вы можете просто обойти настройки пользователя и автоматически разблокировать файл в любом случае, но это, вероятно, не очень приятно.

Вот категория, которая должна помочь вам определить, заблокирован ли файл, а также заблокировать/разблокировать файл.

Обратите внимание, что это совершенно не связано с изменением режима файлов на «только для чтения», но вы можете справиться с этим аналогичным образом.

Интерфейс категории

@interface NSFileManager (MyFileLocking)
- (BOOL)isFileLockedAtPath:(NSString *)path;
- (BOOL)unlockFileAtPath:(NSString*)path error:(NSError**)error;
- (BOOL)lockFileAtPath:(NSString*)path error:(NSError**)error;
@end

Реализация категории

@implementation NSFileManager (MyFileLocking)
- (BOOL)isFileLockedAtPath:(NSString *)path {
    return [[[self attributesOfItemAtPath:path error:NULL]
             objectForKey:NSFileImmutable] boolValue];
}

- (BOOL)unlockFileAtPath:(NSString*)path error:(NSError**)error {
    return [self setAttributes:@{NSFileImmutable:@NO}
                  ofItemAtPath:path
                         error:error];
}

- (BOOL)lockFileAtPath:(NSString*)path error:(NSError**)error {
    return [self setAttributes:@{NSFileImmutable:@YES}
                  ofItemAtPath:path
                         error:error];
}
@end

Затем вы можете вызвать [[NSFileManager defaultManager] isFileLockedAtPath:path], чтобы определить, заблокировано ли оно, и, если это так, вызвать диалоговое окно, спрашивающее пользователя, что с этим делать. Затем вы можете разблокировать его и открыть стек как обычно или оставить его заблокированным и открыть стек только для чтения, что предотвратит изменение хранилища файлов при сохранении.

Обратите внимание, что вы также можете отслеживать файл и знать, когда он меняется с заблокированного/разблокированного, и реагировать соответствующим образом.


Рекомендации Apple по этому поводу см. на странице https://developer.apple.com/library/mac/documentation/DataManagement/Conceptual/DocBasedAppProgrammingGuideForOSX/StandardBehaviors/StandardBehaviors.html

ИЗМЕНИТЬ

В порядке. Мне бы хотелось, чтобы NSPersistentDocument копировал поведение в NSDocument, где запрос на разблокировку появляется только при попытке редактирования. Вы хотите сказать, что в NSPersistentDocument нет такой функции? - Адерштедт

В ПОРЯДКЕ. Я думал, вы хотели попросить пользователя разблокировать его, чтобы его можно было открыть для чтения/записи.

Если вы хотите «плыть по течению» и при необходимости открывать его только для чтения, вам следует добавить небольшую настройку в свой подкласс NSPersistentDocument.

Во-первых, вы хотите добавить небольшое состояние, чтобы отслеживать, указывают ли исходные параметры файл только для чтения.

@implementation MyDocument {
    BOOL explicitReadOnly;
}

Затем вам понадобится несколько служебных методов...

- (NSDictionary*)addReadOnlyOption:(NSDictionary*)options {
    NSMutableDictionary *mutable = options ? [options mutableCopy]
                                           : [NSMutableDictionary dictionary];
    mutable[NSReadOnlyPersistentStoreOption] = @YES;
    return [mutable copy];
}

- (NSDictionary*)removeReadOnlyOption:(NSDictionary*)options {
    NSMutableDictionary *mutable = options ? [options mutableCopy]
                                           : [NSMutableDictionary dictionary];
    [mutable removeObjectForKey:NSReadOnlyPersistentStoreOption];
    return [mutable copy];
}

Затем вы хотите предоставить свой собственный код конфигурации координатора постоянного хранилища. Это позволяет предоставить магазину возможность только для чтения при его создании. Этот метод вызывается автоматически при создании документа, все, что вам нужно сделать, это предоставить реализацию переопределения.

- (BOOL)configurePersistentStoreCoordinatorForURL:(NSURL *)url
                                           ofType:(NSString *)fileType
                               modelConfiguration:(NSString *)configuration
                                     storeOptions:(NSDictionary<NSString *,id> *)storeOptions
                                            error:(NSError * _Nullable __autoreleasing *)error {
    explicitReadOnly = [storeOptions[NSReadOnlyPersistentStoreOption] boolValue];
    if (![[NSFileManager defaultManager] isWritableFileAtPath:url.path]) {
        storeOptions = [self addReadOnlyOption:storeOptions];
    }
    return [super configurePersistentStoreCoordinatorForURL:url
                                                     ofType:fileType
                                         modelConfiguration:configuration
                                               storeOptions:storeOptions
                                                      error:error];
}

Также обратите внимание, что NSPersistentDocument реализует протокол NSFilePresenter. Таким образом, вы можете переопределить метод и получать уведомления при каждом изменении содержимого или атрибутов файла. Это уведомит вас о любых изменениях в файле, включая блокировку/разблокировку из вашего приложения, Finder или любого другого механизма.

- (void)presentedItemDidChange {
    [self ensureReadOnlyConsistency];
    [super presentedItemDidChange];
}

Затем мы хотим убедиться, что наше постоянное хранилище остается совместимым со свойствами файла только для чтения.

Вот одна реализация, которая просто изменяет свойство хранилища readOnly.

- (void)ensureReadOnlyConsistency {
    NSURL *url = [self presentedItemURL];
    BOOL fileIsReadOnly = ![[NSFileManager defaultManager] isWritableFileAtPath:url.path];

    NSPersistentStoreCoordinator *psc = self.managedObjectContext.persistentStoreCoordinator;
    [psc performBlock:^{
        NSPersistentStore *store = [psc persistentStoreForURL:url];
        if (store) {
            if (fileIsReadOnly) {
                if (!store.isReadOnly) {
                    store.readOnly = YES;
                }
            } else if (!explicitReadOnly) {
                if (store.isReadOnly) {
                    store.readOnly = NO;
                }
            }
        }
    }];
}

Это работает, но есть одно небольшое зависание. Если магазин изначально открыт с параметрами только для чтения, то в самый первый раз, когда для атрибута readOnly установлено значение NO, это первое сохранение выдает (фактически, это вызов obtainPermanentIDsForObjects:error:. Основные данные появляются для перехвата исключения, но оно регистрируется в консоль.

Сохранение продолжается, и кажется, что все в порядке. Все объекты сохраняются, а также правильно получаются и записываются идентификаторы объектов.

Итак, нет ничего, что не работает, что я могу сказать.

Впрочем, есть и другой, более драконовский вариант, но он позволяет избежать вышеупомянутой «проблемы». Вы можете заменить магазин.

- (void)ensureReadOnlyConsistency {
    NSURL *url = [self presentedItemURL];
    BOOL fileIsReadOnly = ![[NSFileManager defaultManager] isWritableFileAtPath:url.path];

    NSPersistentStoreCoordinator *psc = self.managedObjectContext.persistentStoreCoordinator;
    [psc performBlock:^{
        NSPersistentStore *store = [psc persistentStoreForURL:url];
        if (store) {
            if (fileIsReadOnly != store.isReadOnly) {
                NSString *type = store.type;
                NSString *configuration = store.configurationName;
                NSDictionary *options = store.options;
                if (fileIsReadOnly) {
                    options = [self addReadOnlyOption:options];
                } else if (!explicitReadOnly) {
                    options = [self removeReadOnlyOption:options];
                }

                NSError *error;
                if (![psc removePersistentStore:store error:&error] ||
                    ![psc addPersistentStoreWithType:type
                                       configuration:configuration
                                                 URL:url
                                             options:options
                                               error:&error]) {
                    // Handle the error
                }
            }
        }
    }];
}

Наконец, обратите внимание, что уведомление появляется, когда операционная система замечает, что файл был изменен. Когда файл заблокирован/разблокирован из вашего приложения, вы можете получить более быстрое уведомление.

Вы можете переопределить эти два метода, чтобы получить более быстрый ответ на изменение...

- (void)lockWithCompletionHandler:(void (^)(NSError * _Nullable))completionHandler {
    [super lockWithCompletionHandler:^(NSError * _Nullable error) {
        if (completionHandler) completionHandler(error);
        if (!error) [self ensureReadOnlyConsistency];
    }];
}

- (void)unlockWithCompletionHandler:(void (^)(NSError * _Nullable))completionHandler {
    [super unlockWithCompletionHandler:^(NSError * _Nullable error) {
        if (completionHandler) completionHandler(error);
        if (!error) [self ensureReadOnlyConsistency];
    }];
}

Надеюсь, это то, что вы ищете.

person Jody Hagins    schedule 27.10.2015
comment
В порядке. Мне бы хотелось, чтобы NSPersistentDocument воспроизвел поведение в NSDocument, где запрос на разблокировку появляется только при попытке редактирования. Вы хотите сказать, что в NSPersistentDocument такой функции нет? - person Aderstedt; 28.10.2015
comment
@Aderstedt - Хорошо, проверьте недавнее редактирование и посмотрите, не этого ли вы хотите достичь. - person Jody Hagins; 29.10.2015