Скрыть NSDocument подкласс от запуска служб - PullRequest
1 голос
/ 09 апреля 2020

У меня есть пара приложений, которые в основном близнецы - одно - клиент, а другое - сервер. Они совместно используют большую часть одного и того же кода и оба используют один и тот же подкласс NSDocument для реализации формата документа, который они совместно используют. Клиентское приложение имеет пользовательский интерфейс и позволяет пользователям визуально работать с документами, но серверное приложение не (хотя оно работает как обычное приложение, а не как демон), оно предназначено для невидимого запуска.

Проблема в том, что приложение сервера запускается, когда на иконку перетаскивается документ. Серверное приложение также запускается, если оно запущено, а клиентское приложение не запущено, и пользователь дважды щелкает документ. В этом случае я хочу, чтобы Launch Services запустил клиентское приложение и открыл документ, вместо этого он пытается открыть документ с помощью серверного приложения. Я настроил метод NSApplicationDelegate application:openFile: так, чтобы приложение сервера отказывалось фактически открывать документ в этой ситуации, но я хочу, чтобы NSApplicationDelegate не вызывал его с событиями открытия документа. Это сбивает с толку пользователей, поскольку они ожидают, что двойной щелчок по документу откроет клиентское приложение, независимо от того, запущено приложение сервера или нет.

Документация Apple Core Foundation Keys, похоже, указывает на то, что способ сделать это - использовать LSHandlerRank свойство.

https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html# // apple_ref / doc / uid / TP40009249-SW1

Для клиентского приложения я установил это в «Владелец».

<key>LSHandlerRank</key>
<string>Owner</string>

Для серверного приложения я установил для него значение «Нет».

<key>LSHandlerRank</key>
<string>None</string>

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

Еще одно свойство, которое, на мой взгляд, может быть многообещающим, - CFBundleTypeRole . В plist для клиентского приложения устанавливается значение «Редактор».

<key>CFBundleTypeRole</key>
<string>Editor</string>

Документация по этому свойству очень скудна, но в ней говорится, что «Нет» - это вариант. Поэтому я попробовал это в списке приложений сервера, и тогда я больше не мог программно открывать свои файлы NSDocument. С другой стороны, перетаскивание документа на значок приложения сервера still привело к тому, что значок загорелся, поэтому службы запуска, по-видимому, все еще считают, что приложение сервера может обрабатывать файлы этого типа.

Подводя итог, мне необходимо внести некоторые изменения в plist, чтобы я мог программно работать с файлами, связанными с моим подклассом NSDocument, но я не хочу, чтобы Launch Services знал, что мое (серверное) приложение может работать с этими документами. Возможно ли это?

---- Продолжение ответа Питера Хоси ----

Спасибо за этот ответ. Я не знал о команде оболочки lsregister, и она выглядит очень удобной. Дамп производит 46 мегабайт данных на моей машине!

Однако я уверен, что это не проблема кэширования. Хотя на моем компьютере имеется несколько копий серверного приложения, эта проблема была обнаружена клиентом. У него есть только одна копия приложения, и у него было только короткое время. Я не менял содержание plist более года, так что в его системе не было бы кешировать ничего.

Вы предлагаете не использовать Launch Services, но я не давал понять, что не принимаю Я не хочу, чтобы Launch Services открывал мои документы (по крайней мере, с серверной версией моего приложения). Фактически, я успешно изменил приложение, так что если Launch Services действительно запрашивает приложение сервера, чтобы открыть документ, он игнорирует этот запрос. Открытие документов выполняется через внутренний сервер TCP / IP, который использует openDocumenWithContentsOfURL, например:

[sharedDocumentController openDocumentWithContentsOfURL:databaseURL display:openWindows error:&err];

Метод openDocumenWithContentsOfURL, похоже, требует, чтобы plist был настроен для типа документа , Моя проблема в том, что это также говорит Finder, что это приложение может обрабатывать документы такого типа, которые я не хочу. Поэтому я ищу способ, которым я могу использовать NSDocument в приложении, но не предоставлять его Finder (так что я думаю, что не предоставлять его Launch Services). Возможно, это невозможно.

Существует ли какой-либо способ открытия документа без URL-адреса, чтобы не имело значения, что такое расширение или как составляется список? Я не вижу такого документированного метода в классе NSDocumentController. Мне кажется, и тестирование, кажется, подтверждает, что класс NSDocument использует plist для связи расширений файлов с подклассами NSDocument.

Если есть способ изменить plist, чтобы сделать это, то я определенно нужно будет использовать lsregister для очистки кэша, чтобы проверить это:)

Ответы [ 2 ]

1 голос
/ 13 апреля 2020

Ооо, значит, вы не используете Launch Services (или Apple Events) для отправки «документа» на сервер, но сервер обрабатывает запросы (полученные другими способами), «открывая» «документ» с помощью NSDocumentController. И вы обнаружите, что NSDocumentController требует типы документов в Info.plist, чтобы знать, какой подкласс NSDocument использовать.

Это то, что вы можете переопределить в своем подклассе NSDocumentController:

  • -typeForContentsOfURL:error:: с учетом URL-адреса вернуть строку, указывающую, к какому типу документа относится этот URL-адрес. Поведение по умолчанию делает это, находя тип, который имеет URL pathExtension среди своих тегов. В зависимости от того, насколько строгим вы хотите быть, вы можете выполнять аналогичные проверки и возвращать имя типа только в том случае, если это документ, правдоподобно отправленный вашим клиентом, или вы можете быть ленивым и просто возвращать постоянную строку (если ваш сервер обрабатывает только один тип) of «document»).
  • -documentClassForType:: учитывая имя типа документа, вернуть подкласс NSDocument, чтобы создать экземпляр для управления документом этого типа. Пока вы обрабатываете только один тип и, следовательно, имеете только один подкласс NSDocument, вы можете вернуть свой подкласс NSDocument безоговорочно; если у вас есть несколько таких подклассов или вы хотите планировать будущее, в котором вы могли бы, сравнить имя типа с каждым известным именем типа и вернуть соответствующий подкласс.
  • -displayNameForType:: учитывая имя типа документа, вернуть локализованная строка, содержащая представляемое пользователем имя этого типа (например, «веб-страница» на английском языке sh вместо «publi c .html»). Может не потребоваться в вашем случае.
  • -documentClassNames: возвращает массив имен подклассов NSDocument, которые могут быть созданы для управления документами, которые может обрабатывать приложение. Может не понадобиться для ваших целей, но реализация по умолчанию обращается к вашему Info.plist.
  • -defaultType: имя типа документа, используемого для новых документов (как в действии newDocument:). Может не иметь значения, если вы не создаете новые документы с сервера, но реализация по умолчанию обращается к вашему Info.plist (он возвращает первый тип, для которого вы объявили себя редактором).

С этими методами, переопределенными в вашем подклассе NSDocumentController, ваш контроллер документов больше не будет обращаться к Info.plist, и вы можете удалить информацию Info.plist о типах документов, о которых вы не хотите, чтобы LS знал.

Если вы еще не используете подкласс NSDocumentController, вам нужно создать его и вызвать [MyDocumentController sharedDocumentController] в начале вашей программы (вам, возможно, даже придется сделать это в main, чтобы сделать это до загрузки пера; это было некоторое время) так как я посмотрел, когда и где создается экземпляр контроллера документов). Отправка этого сообщения вашему подклассу гарантирует, что контроллер документа будет создан из этого подкласса, и, таким образом, будет иметь поведение, которое вы реализовали в этом подклассе.

1 голос
/ 09 апреля 2020

Одной из проблем в работе с LS является аннулирование кэша (известная одна из двух самых сложных проблем в информатике, наряду с именами и ошибками). LS хочет кешировать все по соображениям производительности, поэтому, когда вы что-то меняете, становится важным убедиться, что старая информация удаляется из базы данных LS.

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

Второе, что нужно проверить, это то, какие копии серверного приложения LS считают, что вы есть, и что он думает, что они могут справиться.

Способ сделать оба эти с помощью lsregister -dump. lsregister скрыт в /System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister, и его дамп даст вам обширную информацию о том, какие типы (UTI) зарегистрированы, какие пакеты приложений зарегистрированы (включая разные версии в разных местах с разными возможностями), службы и т. д. c.

lsregister -help расскажет вам о других своих опциях, некоторые из которых могут помочь в удалении устаревших / бесполезных записей из База данных LS.

Я бы, вероятно, не стал использовать LS для этого. Возможно, импортируйте тип в Info.plist серверного приложения, но не перечисляйте его как тип документа и не используйте LS из клиента для открытия документа с помощью серверного приложения.

Вместо этого сделайте ваше собственное событие Open Documents с NSAppleEventDescriptor, затем отправьте его на серверное приложение, используя AESendMessage. Класс события - kCoreEventClass, а идентификатор события - kAEOpenDocuments. Целевым дескриптором должен быть NSAppleEventDescriptor типа typeApplicationBundleID, содержащий идентификатор пакета серверного приложения в виде строки.

Установите для параметра ключевого слова keyDirectObject события значение дескриптора по крайней мере одного дескриптора typeFileURL, содержащего URL-адрес открываемого документа (также представлен в виде строки).

...