При интеграции 1С с внешними сайтами или файловыми хранилищами по протоколу FTP часто возникает задача поиска и выгрузки файлов из вложенных папок. Однако при вызове метода FTPСоединение.НайтиФайлы с включенным флагом поиска во вложенных подкаталогах процесс может наглухо зависнуть. Разберем, почему возникает эта проблема и какими способами можно эффективно обойти большой объем данных на FTP-сервере без блокировки сеанса 1С.
Проанализируем ситуацию и выясним, почему стандартный рекурсивный поиск дает сбой на боевых базах данных:
FTPСоединение.НайтиФайлы с третьим параметром Истина не возвращает данные порциями. Платформа 1С пытается полностью обойти все дерево каталогов, последовательно отправляя команды серверу, и формирует единый массив объектов FTPФайл в оперативной памяти. Если на сервере тысячи файлов и глубокая структура подпапок, операция занимает огромное количество времени.FTPСоединение явно не задан таймаут, при потере пакетов или срабатывании ограничений FTP-сервера (например, защита от частых запросов) сокет переходит в режим бесконечного ожидания ответа.Рассмотрим основной способ решения проблемы: отказ от автоматической рекурсии платформы в пользу контролируемого последовательного обхода каталогов. Мы передаем в метод НайтиФайлы третий параметр Ложь (поиск только в текущем каталоге) и складываем найденные подкаталоги в массив-очередь.
Посмотрим на пример реализации такого алгоритма:
// Инициализируем FTP-соединение с обязательным указанием таймаута и пассивного режима
Таймаут = 30;
ПассивныйРежим = Истина;
ФТПСоединение = Новый FTPСоединение("ftp.example.com", 21, "Логин", "Пароль", , ПассивныйРежим, Таймаут);
КаталогПоиска = "/public_html/images/";
ОчередьКаталогов = Новый Массив;
ОчередьКаталогов.Добавить(КаталогПоиска);
НайденныеФайлы = Новый Массив;
МаскаПоиска = "*.jpg";
// Обходим очередь каталогов
Пока ОчередьКаталогов.Количество() > 0 Цикл
ТекущийПуть = ОчередьКаталогов[0];
ОчередьКаталогов.Удалить(0);
// Получаем элементы текущего каталога без рекурсии (параметр Ложь)
Попытка
ЭлементыКаталога = ФТПСоединение.НайтиФайлы(ТекущийПуть, "*", Ложь);
Исключение
// Логируем ошибку доступа к конкретной папке и продолжаем работу
ЗаписьЖурналаРегистрации("FTPПоиск", УровеньЖурналаРегистрации.Предупреждение, , ,
"Ошибка чтения каталога: " + ТекущийПуть + ". Описание: " + ОписаниеОшибки());
Продолжить;
КонецПопытки;
Для Каждого Элемент Из ЭлементыКаталога Цикл
Если Элемент.ЭтоКаталог() Тогда
// Исключаем ссылки на текущую и родительскую директории
Если Элемент.Имя <> "." И Элемент.Имя <> ".." Тогда
ОчередьКаталогов.Добавить(Элемент.ПолноеИмя + "/");
КонецЕсли;
Иначе
// Проверяем соответствие расширению или маске
Если СтрЗаканчивается(НРег(Элемент.Имя), ".jpg") Или СтрЗаканчивается(НРег(Элемент.Имя), ".jpeg") Тогда
НайденныеФайлы.Добавить(Элемент.ПолноеИмя);
КонецЕсли;
КонецЕсли;
КонецЦикла;
КонецЦикла;
Этот подход гарантирует, что программа не зависнет в бесконечном цикле, позволяет отслеживать прогресс выполнения и при возникновении ошибки в отдельной папке не терять уже собранные результаты.
Разберем вариант ускорения работы при наличии очень большого дерева каталогов. Мы можем распределить нагрузку между несколькими фоновыми потоками.
ФоновыеЗадания.Выполнить.FTPСоединение не является сериализуемым и не может быть передан в параметры фонового задания. Внутри процедуры фонового задания необходимо инициализировать свой собственный экземпляр FTPСоединение.Если есть доступ к веб-серверу, на котором хранятся файлы, наиболее производительным решением будет отказ от сканирования файловой структуры через протокол FTP со стороны 1С:
HTTPСоединение или скачивает его по FTP методом FTPСоединение.Получить, после чего мгновенно разбирает данные встроенными средствами платформы.Таким образом, для стабильной работы с объемными FTP-хранилищами следует избегать вызова FTPСоединение.НайтиФайлы с флагом рекурсии и использовать пошаговый обход с контролем таймаутов либо выносить индексацию на сторону сервера.