Locating macOS significant directories/ru

From Lazarus wiki
Jump to navigationJump to search
macOSlogo.png

Эта статья относится только к macOS.

См. также: Multiplatform Programming Guide

English (en) русский (ru)

Обзор

Чтобы найти различные важные каталоги в macOS, вы можете использовать встроенную функцию macOS Foundation

NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory directory, NSSearchPathDomainMask domainMask, BOOL expandTilde);

, которая создает список строк путей для указанных каталогов в указанных доменах. Список приведен в том порядке, в котором вы должны выполнять поиск по каталогам.

Примечание: Каталог, возвращаемый этим методом, может не существовать. Этот метод просто указывает вам подходящее местоположение для запрашиваемого каталога. В зависимости от потребностей приложения, разработчик может создать соответствующий каталог и любой промежуточный каталог.

Определение места для хранения файлов

Прежде чем мы продолжим, давайте вспомним, где, согласно рекомендациям Apple, ваше приложение должно хранить свои файлы:

  • Используйте каталог /Applications или /Applications/Utilities для application bundle(пакета приложения). Пакет приложений должен содержать все: библиотеки, зависимости, справку, все файлы, необходимые приложению для запуска, за исключением тех, которые создаются самим приложением. Если пакет приложений скопирован в каталог /Applications или /Applications/Utilities на другом компьютере, он должен быть запущен. Для установки в эти папки требуются права администратора. Резервное копирование данных в этих папках выполняется Time Machine.
  • Используйте каталог ~/Applications, если права администратора недоступны. Это стандартное расположение для однопользовательского приложения. Не следует ожидать, что этот каталог будет существовать. Пакет приложений должен содержать все: библиотеки, зависимости, справку, все файлы, необходимые приложению для запуска, за исключением тех, которые создаются самим приложением. Если пакет приложений скопирован в каталог /Applications или /Applications/Utilities на другом компьютере, он должен быть запущен. Резервная копия этих данных создается Time Machine.
  • Используйте каталог Application Support (эти данные хранятся в резервной копии Time Machine), добавив свой <bundle_ID>(Идентификатор пакета), для:
    • Файлов ресурсов и данных, которые ваше приложение создает для пользователя и управляет ими. Вы можете использовать этот каталог для хранения информации о состоянии приложения, вычисленных или загруженных данных или даже созданных пользователем данных, которыми вы управляете от имени пользователя.
    • Автосохранения файлов.
  • Используйте каталог Caches (эти данные не хранятся в резервной копии Time Machine), добавив свой <bundle_ID> для кэшированных файлов данных или любых файлов, которые ваше приложение может легко воссоздать.
  • Используйте каталог CFPreferences для чтения и записи настроек вашего приложения. Это автоматически запишет настройки в соответствующее расположение и прочитает их из соответствующего местоположения. Резервное копирование этих данных выполняется Time Machine.
  • Используйте каталог Resources (это резервная копия Time Machine) для файлов изображений, звуковых файлов, файлов значков и других неизменяемых файлов данных, необходимых для работы вашего приложения.
  • Используйте функцию NSTemporaryDirectory, которая возвращает путь к временной директории (эти данные не хранятся в резервной копии Time Machine) для хранения временных файлов, которые вы собираетесь немедленно использовать для какой-либо текущей операции, но планируете удалить позже. Удалите временные файлы, как только вы закончите с ними.

Функция получения важных каталогов

{$mode objfpc}{H+}
{$modeswitch objectivec1} 

interface

Uses
  ...
  CocoaUtils,   // for NStringToString
  CocoaAll;     // for NSArray      
...

function GetSignificantDir(DirLocation: qword; DomainMask: qword; count: byte): string;
var
  paths : NSArray;
begin
  paths := NSSearchPathForDirectoriesInDomains(DirLocation, DomainMask, True);
  if(count < paths.count) then
    Result := NSString(paths.objectAtIndex(0)).UTF8String
  else
    Result := '';  
end;

Таблица расположения каталогов

Directory Locations
Имя каталога Описание каталога
NSApplicationDirectory Поддерживаемые приложения (/Applications)
NSDemoApplicationDirectory Неподдерживаемые приложения и демонстрационные версии
NSDeveloperApplicationDirectory Приложения для разработчиков (/Developer/Applications)
NSAdminApplicationDirectory Приложения для системного и сетевого администрирования
NSLibraryDirectory Различная доступная пользователю документация, поддержка и конфигурационные файлы (/Library)
NSDeveloperDirectory Ресурсы разработчика (/Developer)
NSUserDirectory Домашние каталоги пользователей (/Users)
NSDocumentationDirectory Документация
NSDocumentDirectory Каталог документов
NSCoreServiceDirectory Службы ядра (System/Library/CoreServices)
NSAutosavedInformationDirectory Автоматически сохраненные документы пользователя (Library/Autosave Information)
NSDesktopDirectory Каталог рабочего стола пользователя
NSCachesDirectory Удаляемые файлы кэша (Library/Caches)
NSApplicationSupportDirectory Файлы поддержки приложений (Library/Application Support)
NSDownloadsDirectory Каталог загрузок пользователя
NSInputMethodsDirectory Методы ввода (Library/Input Methods)
NSMoviesDirectory Каталог фильмов пользователя (~/Movies)
NSMusicDirectory Каталог музыки пользователя (~/Music)
NSPicturesDirectory Каталог изображений пользователя (~/Pictures)
NSPrinterDescriptionDirectory Системный каталог PostScript Printer Description (Library/Printers/PPDs)
NSSharedPublicDirectory Общедоступный каталог общего доступа пользователя (~/Public)
NSPreferencePanesDirectory Каталог панели настроек (preference panes) для системных настроек (Library/PreferencePanes)
NSApplicationScriptsDirectory Папка пользовательских сценариев для вызывающего приложения (~/Library/Application Scripts/<code-signing-id>
NSItemReplacementDirectory Константа, используемая для создания временного каталога
NSAllApplicationsDirectory Все каталоги, в которых могут храниться приложения
NSAllLibrariesDirectory Все каталоги, в которых могут храниться ресурсы
NSTrashDirectory Каталог корзины пользователя

Table of Path Domains

Path Domains
Domain name Domain Description
NSUserDomainMask The user’s home directory—the place to install user’s personal items (~).
NSLocalDomainMask The place to install items available to everyone on this machine
NSNetworkDomainMask The place to install items available on the network (/Network)
NSSystemDomainMask A directory for system files provided by Apple (/System)
NSAllDomainsMask All domains


Примеры кода

Теперь вы можете находить различные важные каталоги и отображать их, как показано в примерах кода ниже.

Каталог пользовательских кэшей

procedure TForm1.MenuItem5Click(Sender: TObject);
begin
  ShowMessage('User caches dir: ' + GetSignificantDir(NSCachesDirectory,NSUserDomainMask,0));
end;

Пользовательский каталог корзины

procedure TForm1.MenuItem16Click(Sender: TObject);
begin
  ShowMessage('User trash dir: '
          + GetSignificantDir(NSTrashDirectory,NSUserDomainMask,0));
end;

Каталог пользователей

Возможно, это не то, что вы ожидаете... это каталог "/Users" :-)

procedure TForm1.MenuItem10Click(Sender: TObject);
begin
  ShowMessage('Users dir: ' +  GetSignificantDir(NSUserDirectory,NSLocalDomainMask,0));
end;

Чтобы получить домашний каталог текущего пользователя, вам нужно вместо этого использовать функцию NSHomeDirectory():

procedure TForm1.MenuItem11Click(Sender: TObject);
begin
  ShowMessage('User''s dir: ' + NSStringToString(NSHomeDirectory));
end;

НО обратите внимание, что если приложение находится в изолированной среде, это вернет каталог приложения sandbox, а не домашний каталог текущего пользователя.

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

function NSUserName: CFStringRef external name '_NSUserName';
...

procedure TForm1.MenuItem22Click(Sender: TObject);
var
  usernameStr: ShortString;
  status: Boolean = false;
begin
   status := CFStringGetPascalString(CFStringRef(NSusername),@usernameStr,255,CFStringGetSystemEncoding);
  if(status = true) then
     ShowMessage('Current user''s dir: ' + NSStringToString(NShomeDirectoryForUser(NSStr(usernameStr))))
  else
     ShowMessage('Error retrieving username'); 
end;

Все каталоги приложений

procedure TForm1.MenuItem7Click(Sender: TObject);
var
  count: byte;
begin
  for count := 0 to 12 do
   if GetSignificantDir(NSAllApplicationsDirectory, NSAllDomainsMask, count) <> '' then
      ShowMessage('Application dir ' + IntToStr(count) + ': '
           + GetSignificantDir(NSAllApplicationsDirectory,NSAllDomainsMask,count))
   else
      exit; 
end;

Все каталоги пользовательских приложений

procedure TForm1.MenuItem8Click(Sender: TObject);
var
  count: byte;
begin
  for count := 0 to 12 do
   if GetSignificantDir(NSAllApplicationsDirectory, NSUserDomainMask, count) <> '' then
      ShowMessage('User application dir ' + IntToStr(count) + ': '
           + GetSignificantDir(NSAllApplicationsDirectory,NSUserDomainMask,count))
   else
      exit; 
end;

Все системные каталоги приложений

procedure TForm1.MenuItem9Click(Sender: TObject);
var
  count: byte;
begin
  for count := 0 to 12 do
   if GetSignificantDir(NSAllApplicationsDirectory, NSSystemDomainMask, count) <> '' then
      ShowMessage('System application dir ' + IntToStr(count) + ': '
           + GetSignificantDir(NSAllApplicationsDirectory,NSSystemDomainMask,count))
   else
      exit; 
end;

Замена GetAppConfigDir и GetAppConfigFile в FPC

Существующие функции FPC GetAppConfigDir и GetAppConfigFile не подходят для Mac OS, поскольку они используют соглашение UNIX и не соответствуют соглашению Apple macOS. Например, выходные данные этих функций:

writeLn(GetAppConfigDir(true));
writeLn(GetAppConfigDir(false));

writeLn(GetAppConfigFile(true));
writeLn(GetAppConfigFile(false));

writeLn(GetAppConfigFile(true,true));
writeLn(GetAppConfigFile(true,false));

writeLn(GetAppConfigFile(false,false));
writeLn(GetAppConfigFile(false,true));

это:

/etc/
/Users/<username>/.config/

/etc/project1.cfg
/Users/<username>/.config/project1.cfg

/etc/project1/project1.cfg
/etc/project1.cfg

/Users/<username>/.config/project1.cfg
/Users/<username>/.config/project1/project1.cfg

принимая во внимание, что правильный вывод для приложения командной строки macOS (т.е. без Пакета приложений) выглядит следующим образом:

/Library/Application Support/project1/
/Users/<username>/Library/Application Support/project1/

/Library/Application Support/project1/project1.plist
/Users/<username>/Library/Application Support/project1/project1.plist

/Library/Application Support/project1/project1.plist
/Library/Application Support/project1/project1.plist

/Users/<username>/Library/Application Support/project1/project1.plist
/Users/<username>/Library/Application Support/project1/project1.plist

и правильный вывод для приложения с графическим интерфейсом Mac OS (например, с пакетом приложений):

/Library/Application Support/com.company.project1/
/Users/<username>/Library/Application Support/com.company.project1/

/Library/Application Support/com.company.project1/project1.plist
/Users/<username>/Library/Application Support/com.company.project1/project1.plist

/Library/Application Support/com.company.project1/project1.plist
/Library/Application Support/com.company.project1/project1.plist

/Users/<username>/Library/Application Support/com.company.project1/project1.plist
/Users/<username>/Library/Application Support/com.company.project1/project1.plist

Вот заменяющие функции, названные с добавлением 2 к существующим именам функций, и с ${IFDEF}s, чтобы их можно было использовать в мультиплатформенном коде:

{$mode objfpc}{H+}
{$modeswitch objectivec1}

...

Uses
  ...
  SysUtils,
  CocoaAll,
  Cocoautils;
...

//
// Замена GetAppConfigDir
//

function GetAppConfigDir2(Global: Boolean): String;

{$IFDEF DARWIN}
var
  bundleIdStr: String;
  pathsArr : NSArray;
{$ENDIF}
begin
  {$IFDEF DARWIN}
  bundleIdStr := NSStringToString(NSBundle.mainBundle.bundleIdentifier);
  if(bundleIdStr = '') then
     bundleIdStr := ExtractFileName(paramstr(0)); // Работа с приложениями, не входящими в пакет приложения, например, с командной строкой

  if(Global) then
    pathsArr := NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSLocalDomainMask, True)
  else
    pathsArr := NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, True);

  Result := NSString(pathsArr.objectAtIndex(0)).Utf8String + '/' + bundleIdStr + '/';
  {$ELSE}
    GetAppConfigDir(global);
  {$ENDIF}
end;  

//    
// Замена GetAppConfigFile
//

Function GetAppConfigFile2(Global : Boolean; SubDir : Boolean) : String;

{$IFDEF DARWIN}
var
  bundleIdStr: ShortString;
  appNameStr: ShortString;
  pathBufferStr: String;
  isAppBundle : Boolean = True;
{$ENDIF}
begin
  {$IFDEF DARWIN}
  bundleIdStr := NSStringToString(NSBundle.mainBundle.bundleIdentifier);
  if(bundleIdStr = '') then
    begin
     bundleIdStr := ExtractFileName(paramstr(0)); // Работа с приложениями, не входящими в пакет приложения, например, с командной строкой
     isAppBundle := False;
    end;

  pathBufferStr := GetAppConfigDir2(Global);

  if(isAppBundle) then
    appNameStr := StringReplace(ExtractFileExt(bundleIdStr), '.', '', [rfReplaceAll])
  else
    appNameStr := extractFileName(ExcludeTrailingPathDelimiter(pathBufferStr));

  if(SubDir) then
    // Игнорируем, потому что Apple уже задает subdir как часть GetAppConfigDir()
    ;

  Result := pathBufferStr + appNameStr + '.plist';

  {$ELSE}
  Result := GetAppConfigFile(Global, SubDir);
  {$ENDIF}
end;

см.также