Получение свойств файла

В процессе работы с файлами помимо непосредственно записи и чтения данных часто возникает необходимость проанализировать их свойства. Одно из основных свойств — размер файла — можно получить с помощью функции FileSize. Но есть и еще несколько характеристик, которые можно запросить с помощью FileGetInteger.

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

ulong FileSize(int handle)

Функция возвращает размер открытого файла по его дескриптору. В случае ошибки результат равен 0, что является допустимым размером и при штатном выполнении функции, поэтому анализ потенциальных ошибок требуется всегда проводить с помощью _LastError (или GetLastError).

Размер файла можно также получить путем перемещения указателя в конец файла FileSeek(handle, 0, SEEK_END) и вызова FileTell(handle) — обе функции описаны в предыдущем разделе.

long FileGetInteger(int handle, ENUM_FILE_PROPERTY_INTEGER property)

long FileGetInteger(const string filename, ENUM_FILE_PROPERTY_INTEGER property, bool common = false)

Функция имеет 2 варианта: для работы через дескриптор открытого файла и по имени файла (в том числе закрытого).

Функция возвращает одно из свойств файла, указанное в параметре property, причем перечень допустимых свойств отличается для каждого из вариантов (см. ниже). Несмотря на то, что тип значения — long, он может содержать, в зависимости от запрошенного свойства, не только целое число, но и datetime или bool: выполните необходимое приведение типа явным образом.

При запросе свойства по имени файла можно дополнительно уточнить с помощью параметра common, в какой папке следует искать файл: текущего терминала MQL5/Files (false, по умолчанию) или общей Users/<пользователь>...­MetaQuotes/Terminal/Common/Files (true). Если MQL-программа выполняется в тестере, рабочий каталог расположен внутри папки агента тестирования (Tester/<агент>/MQL5/Files), см. вводную часть главы Работа с файлами.

В следующей таблице перечислены все элементы ENUM_FILE_PROPERTY_INTEGER.

Свойство

Описание

FILE_EXISTS *

Проверка на существование (аналог FileIsExist)

FILE_CREATE_DATE *

Дата создания

FILE_MODIFY_DATE *

Дата последнего изменения

FILE_ACCESS_DATE *

Дата последнего доступа

FILE_SIZE *

Размер файла в байтах (аналог FileSize)

FILE_POSITION

Позиция указателя в файле (аналог FileTell)

FILE_END

Признак позиции в конце файла (аналог FileIsEnding)

FILE_LINE_END

Признак позиции в конце строки (аналог FileIsLineEnding)

FILE_IS_COMMON

Файл открыт в общей папке терминалов (FILE_COMMON)

FILE_IS_TEXT

Файл открыт как текстовый (FILE_TXT)

FILE_IS_BINARY

Файл открыт как бинарный (FILE_BIN)

FILE_IS_CSV

Файл открыт как CSV (FILE_CSV)

FILE_IS_ANSI

Файл открыт как ANSI (FILE_ANSI)

FILE_IS_READABLE

Файл открыт на чтение (FILE_READ)

FILE_IS_WRITABLE

Файл открыт на запись (FILE_WRITE)

Свойства, разрешенные для использования по имени файла, помечены звездочкой. При попытке получения других свойств второй вариант функции вернет ошибку 4003 (INVALID_PARAMETER).

Часть свойств может меняться в процессе работы с открытым файлом: FILE_MODIFY_DATE, FILE_ACCESS_DATE, FILE_SIZE, FILE_POSITION, FILE_END, FILE_LINE_END (только для текстовых файлов).

В случае ошибки результат вызова равен -1.

Второй вариант функции позволяет проверить, является ли указанное имя именем файла или каталога. Если при получении свойств по имени будет указан каталог, то функция выставит специальный код внутренней ошибки 5018 (ERR_MQL_FILE_IS_DIRECTORY), при этом возвращаемое значение будет корректным.

Функции данного раздела протестируем в скрипте FileProperties.mq5. Он будет работать с файлом с предопределенным именем.

const string fileprop = "MQL5Book/fileprop";

В начале OnStart попробуем запросить размер по ошибочному дескриптору (он не был получен через вызов FileOpen). После FileSize потребуется проверка переменной _LastError, а FileGetInteger сразу возвращает специальное значение — индикатор ошибки (-1).

void OnStart()
{
   int handle = 0;
   ulong size = FileSize(handle);
   if(_LastError)
   {
      Print("FileSize error="E2S(_LastError) + "(" + (string)_LastError + ")");
      // Получим: FileSize 0, error=WRONG_FILEHANDLE(5008)
   }
   
   PRTF(FileGetInteger(handleFILE_SIZE)); // -1 / WRONG_FILEHANDLE(5008)

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

   handle = PRTF(FileOpen(filepropFILE_TXT | FILE_WRITE | FILE_ANSI)); // 1
   PRTF(FileWriteString(handle"Test Text\n")); // 11

Выборочно запрашиваем некоторые из свойств.

   PRTF(FileGetInteger(filepropFILE_SIZE)); // 0, еще не записан на диск
   PRTF(FileGetInteger(handleFILE_SIZE)); // 11
   PRTF(FileSize(handle)); // 11
   PRTF(FileGetInteger(handleFILE_MODIFY_DATE)); // 1629730884, кол-во секунд с 1970
   PRTF(FileGetInteger(handleFILE_IS_TEXT)); // 1, bool true
   PRTF(FileGetInteger(handleFILE_IS_BINARY)); // 0, bool false

Информация о длине файла по его дескриптору учитывает текущий буфер кэширования, а по имени файла актуальная длина станет доступна только после закрытия файла или если вызвать функцию FileFlush (см. раздел Принудительная запись кэша на диск).

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

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

   PRTF(FileGetInteger(filepropFILE_IS_TEXT)); // -1 / INVALID_PARAMETER(4003)

Выждем 1 секунду, закроем файл и снова проверим дату модификации (на этот раз по имени, т.к. дескриптор уже не действует).

   Sleep(1000);
   FileClose(handle);
   PRTF(FileGetInteger(filepropFILE_MODIFY_DATE)); // 1629730885 / ok

Здесь наглядно видно, что время увеличилось на 1.

Наконец, убедимся, что свойства доступны и для директорий (каталогов).

   PRTF((datetime)FileGetInteger("MQL5Book"FILE_CREATE_DATE));
   // Получим: 2021.08.09 22:38:00 / FILE_IS_DIRECTORY(5018)

Поскольку все примеры книги расположены в папке "MQL5Book", она должна уже существовать. Однако актуальное время создания у вас будет отличаться. Код ошибки FILE_IS_DIRECTORY в данном случае для нас выводит макрос PRTF. В рабочей программе вызов функции следует делать без макроса, а код затем прочитать в _LastError.