///////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2024, ООО 1С-Софт
// Все права защищены. Эта программа и сопроводительные материалы предоставляются 
// в соответствии с условиями лицензии Attribution 4.0 International (CC BY 4.0)
// Текст лицензии доступен по ссылке:
// https://creativecommons.org/licenses/by/4.0/legalcode
///////////////////////////////////////////////////////////////////////////////////////////////////////

#Если Сервер Или ТолстыйКлиентОбычноеПриложение Или ВнешнееСоединение Тогда

#Область ОписаниеПеременных

Перем СообщениеОбмена Экспорт; // При получении - имя полученного файла во ВременныйКаталог. При отправке - имя файла, который необходимо отправить
Перем ВременныйКаталог Экспорт; // Временный каталог для сообщений обмена.
Перем ИдентификаторКаталога Экспорт;
Перем Корреспондент Экспорт;
Перем ИмяПланаОбмена Экспорт;
Перем ИмяПланаОбменаКорреспондента Экспорт;
Перем СообщениеОбОшибке Экспорт;
Перем СообщениеОбОшибкеЖР Экспорт;

Перем ШаблоныИменДляПолученияСообщения Экспорт;
Перем ИмяСообщенияДляОтправки Экспорт;

// Для FTP
Перем ТаймаутОтправкиПолученияДанных; // Таймаут, используемый для FTP соединения при отправке и получении данных.
Перем ТаймаутПроверкиСоединения; // Таймаут, используемый для проверки соединения с FTP-сервером.
Перем ИмяFTPСервера; // Адрес FTP сервера - имя или ip адрес.
Перем КаталогНаFTPСервере; // Каталог на сервере, для хранения/получения сообщений обмена.

#КонецОбласти

#Область ПрограммныйИнтерфейс

// См. ОбработкаОбъект.ТранспортСообщенийОбменаFILE.ОтправитьДанные
Функция ОтправитьДанные(СообщениеДляСопоставленияДанных = Ложь) Экспорт
	
	Попытка
		Результат = ОтправитьСообщение();
	Исключение
		
		ТранспортСообщенийОбмена.ИнформацияОбОшибкеВСообщения(ЭтотОбъект, ИнформацияОбОшибке());
		ТранспортСообщенийОбмена.ЗаписатьСообщениеВЖурналРегистрации(ЭтотОбъект, "ВыгрузкаДанных");
		
		Результат = Ложь;
		
	КонецПопытки;
	
	Возврат Результат;

КонецФункции

// См. ОбработкаОбъект.ТранспортСообщенийОбменаFILE.ПолучитьДанные
Функция ПолучитьДанные() Экспорт
	
	Попытка
		
		Для Каждого Шаблон Из ШаблоныИменДляПолученияСообщения Цикл
			
			Результат = ПолучитьСообщение(Шаблон);
			
			Если Результат Тогда
				Прервать;
			КонецЕсли;
			
		КонецЦикла;
		
	Исключение
		
		ТранспортСообщенийОбмена.ИнформацияОбОшибкеВСообщения(ЭтотОбъект, ИнформацияОбОшибке());
		ТранспортСообщенийОбмена.ЗаписатьСообщениеВЖурналРегистрации(ЭтотОбъект, "ЗагрузкаДанных");
		
		Результат = Ложь;
		
	КонецПопытки;
	
	Возврат Результат;
	
КонецФункции

// См. ОбработкаОбъект.ТранспортСообщенийОбменаFILE.ПараметрыКорреспондента
Функция ПараметрыКорреспондента(НастройкиПодключения) Экспорт
	
	Результат = ТранспортСообщенийОбмена.СтруктураРезультатаПолученияПараметровКорреспондента();
	Результат.ПодключениеУстановлено = Истина;
	Результат.ПодключениеРазрешено = Истина;
	
	Возврат Результат;
	
КонецФункции

// См. ОбработкаОбъект.ТранспортСообщенийОбменаFILE.ПередВыгрузкойДанных
Функция ПередВыгрузкойДанных(СообщениеДляСопоставленияДанных = Ложь) Экспорт
	
	Возврат Истина;
	
КонецФункции

// См. ОбработкаОбъект.ТранспортСообщенийОбменаFILE.СохранитьНастройкиВКорреспонденте
Функция СохранитьНастройкиВКорреспонденте(НастройкиПодключения) Экспорт
		
	Возврат Истина;
	
КонецФункции

// См. ОбработкаОбъект.ТранспортСообщенийОбменаFILE.ТребуетсяАутентификация
Функция ТребуетсяАутентификация() Экспорт
	
	Возврат Ложь;
	
КонецФункции

#КонецОбласти

#Область СлужебныеПроцедурыИФункции

Функция ПодключениеУстановлено() Экспорт
	
	// Возвращаемое значение функции.
	Результат = Истина;
	
	Если ОбщегоНазначения.РазделениеВключено() Тогда
		Возврат Результат;
	КонецЕсли;
	
	Если ПустаяСтрока(Путь) Тогда
		СообщениеОбОшибке = НСтр("ru = 'Не задан путь на сервере.'");
		ТранспортСообщенийОбмена.ЗаписатьСообщениеВЖурналРегистрации(ЭтотОбъект);
		Возврат Ложь;
	КонецЕсли;
	
	// Создаем файл во временном каталоге.
	ИмяВременногоФайлаПроверкиПодключения = ПолучитьИмяВременногоФайла("tmp");
	ИмяФайлаНаСторонеПриемника = ОбменДаннымиСервер.ИмяВременногоФайлаПроверкиПодключения();
	
	ЗаписьТекста = Новый ЗаписьТекста(ИмяВременногоФайлаПроверкиПодключения);
	ЗаписьТекста.ЗаписатьСтроку(ИмяФайлаНаСторонеПриемника);
	ЗаписьТекста.Закрыть();
	
	// Копируем файл на внешний ресурс из временного каталога.
	Результат = ВыполнитьКопированиеФайлаНаFTPСервер(ИмяВременногоФайлаПроверкиПодключения, ИмяФайлаНаСторонеПриемника, ТаймаутПроверкиСоединения);
	
	// Удаляем файл на внешнем ресурсе.
	Если Результат Тогда
		Результат = ВыполнитьУдалениеФайлаНаFTPСервере(ИмяФайлаНаСторонеПриемника, Истина);
	КонецЕсли;
	
	// Удаляем файл из временного каталога.
	УдалитьФайлы(ИмяВременногоФайлаПроверкиПодключения);
	
	Возврат Результат;
	
КонецФункции

Функция ОтправитьСообщение()
	
	Результат = Истина;
	
	Если СжиматьФайлИсходящегоСообщения Тогда
		
		Если Не ТранспортСообщенийОбмена.ЗапаковатьСообщениеОбменаВZipФайл(ЭтотОбъект, ПарольАрхиваСообщенияОбмена) Тогда
			Результат = Ложь;
		КонецЕсли;

		Файл = Новый Файл(СообщениеОбмена);
		ИмяФайлаПриемника = Файл.Имя;
		
	Иначе
		
		ИмяФайлаПриемника = ИмяСообщенияДляОтправки;

	КонецЕсли;
	
	Если Результат Тогда
		
		// Выполняем проверку на максимально допустимый размер сообщения обмена.
		Если ОбменДаннымиСервер.РазмерСообщенияОбменаПревышаетДопустимый(СообщениеОбмена, МаксимальныйДопустимыйРазмерСообщения) Тогда
			
			СообщениеОбОшибке = НСтр("ru = 'Превышен допустимый размер сообщения обмена.'");
			ТранспортСообщенийОбмена.ЗаписатьСообщениеВЖурналРегистрации(ЭтотОбъект, "ВыгрузкаДанных");
			
			Результат = Ложь;
			
		КонецЕсли;
		
	КонецЕсли;
		
	Если Результат Тогда
		
		// Копируем файл архива на FTP сервер в каталог обмена информацией.
		Если Не ВыполнитьКопированиеФайлаНаFTPСервер(СообщениеОбмена, ИмяФайлаПриемника, ТаймаутОтправкиПолученияДанных) Тогда
			Результат = Ложь;
		КонецЕсли;
		
	КонецЕсли;
	
	Возврат Результат;
	
КонецФункции

Функция ПолучитьСообщение(ШаблонИмениСообщения)
	
	ТаблицаФайловСообщенийОбмена = Новый ТаблицаЗначений;
	ТаблицаФайловСообщенийОбмена.Колонки.Добавить("Файл");
	ТаблицаФайловСообщенийОбмена.Колонки.Добавить("ВремяИзменения");
	
	Попытка
		FTPСоединение = ПолучитьFTPСоединение(ТаймаутОтправкиПолученияДанных);
	Исключение
		
		СообщениеОбОшибке = НСтр("ru = 'Ошибка инициализации подключения к FTP-серверу.'");
		СообщениеОбОшибкеЖР = СообщениеОбОшибке;
		
		ТранспортСообщенийОбмена.ИнформацияОбОшибкеВСообщения(ЭтотОбъект, ИнформацияОбОшибке(), Истина);
		ТранспортСообщенийОбмена.ЗаписатьСообщениеВЖурналРегистрации(ЭтотОбъект, "ЗагрузкаДанных");
		
		Возврат Ложь;
		
	КонецПопытки;
	
	Попытка
		МассивНайденныхФайлов = FTPСоединение.НайтиФайлы(КаталогНаFTPСервере, ШаблонИмениСообщения, Ложь);
	Исключение
		
		СообщениеОбОшибке = НСтр("ru = 'Ошибка инициализации подключения к FTP-серверу.'");
		СообщениеОбОшибкеЖР = СообщениеОбОшибке;
		
		ТранспортСообщенийОбмена.ИнформацияОбОшибкеВСообщения(ЭтотОбъект, ИнформацияОбОшибке(), Истина);
		ТранспортСообщенийОбмена.ЗаписатьСообщениеВЖурналРегистрации(ЭтотОбъект, "ЗагрузкаДанных");
		
		Возврат Ложь;
		
	КонецПопытки;
	
	Для Каждого ТекущийФайл Из МассивНайденныхФайлов Цикл
		
		Если НЕ ТекущийФайл.ЭтоФайл() Тогда
			
			Продолжить;
			
		// Проверяем ненулевой размер файла.
		ИначеЕсли (ТекущийФайл.Размер() = 0) Тогда
			
			Продолжить;
			
		КонецЕсли;
		
		// Файл является требуемым сообщением обмена; добавляем его в таблицу.
		СтрокаТаблицы = ТаблицаФайловСообщенийОбмена.Добавить();
		СтрокаТаблицы.Файл           = ТекущийФайл;
		СтрокаТаблицы.ВремяИзменения = ТекущийФайл.ПолучитьВремяИзменения();
		
	КонецЦикла;
	
	Если ТаблицаФайловСообщенийОбмена.Количество() = 0 Тогда
		
		СообщениеОбОшибке = НСтр("ru = 'В каталоге обмена информацией не был обнаружен файл сообщения с данными.
                                  |Каталог обмена информацией на сервере: ""%1""
                                  |Имя файла сообщения обмена: ""%2""'");
		
		СообщениеОбОшибке = СтрШаблон(СообщениеОбОшибке, КаталогНаFTPСервере, ШаблонИмениСообщения);
		ТранспортСообщенийОбмена.ЗаписатьСообщениеВЖурналРегистрации(ЭтотОбъект, "ЗагрузкаДанных");
		
		Возврат Ложь;
		
	Иначе
		
		ТаблицаФайловСообщенийОбмена.Сортировать("ВремяИзменения Убыв");
		
		// Получаем из таблицы самый "свежий" файл сообщения обмена.
		Файл = ТаблицаФайловСообщенийОбмена[0].Файл;
		ФайлЗапакован = (ВРег(Файл.Расширение) = ".ZIP");
			
		Если ФайлЗапакован Тогда
			
			ИмяВременногоФайлаАрхива = ОбщегоНазначенияКлиентСервер.ПолучитьПолноеИмяФайла(
				ВременныйКаталог, Строка(Новый УникальныйИдентификатор) + ".zip");
				
			Попытка
				FTPСоединение.Получить(Файл.ПолноеИмя, ИмяВременногоФайлаАрхива);
			Исключение

				СообщениеОбОшибке = НСтр("ru = 'Ошибка при получении файла с FTP-сервера.'");
				СообщениеОбОшибкеЖР = СообщениеОбОшибке;
					
				ТранспортСообщенийОбмена.ИнформацияОбОшибкеВСообщения(ЭтотОбъект, ИнформацияОбОшибке(), Истина);
				ТранспортСообщенийОбмена.ЗаписатьСообщениеВЖурналРегистрации(ЭтотОбъект, "ЗагрузкаДанных");
				
				Возврат Ложь;
				
			КонецПопытки;
		
			Если Не ТранспортСообщенийОбмена.РаспаковатьСообщениеОбменаИзZipФайла(
				ЭтотОбъект, ИмяВременногоФайлаАрхива, ПарольАрхиваСообщенияОбмена) Тогда
				
				Возврат Ложь;
				
			КонецЕсли;
			
		Иначе
	
			Попытка
				
				FTPСоединение.Получить(Файл.ПолноеИмя, СообщениеОбмена);
				
			Исключение
				
				СообщениеОбОшибке = НСтр("ru = 'Ошибка при получении файла с FTP-сервера.'");
				СообщениеОбОшибкеЖР = СообщениеОбОшибке;
		
				ТранспортСообщенийОбмена.ИнформацияОбОшибкеВСообщения(ЭтотОбъект, ИнформацияОбОшибке(), Истина);
				ТранспортСообщенийОбмена.ЗаписатьСообщениеВЖурналРегистрации(ЭтотОбъект, "ВыгрузкаДанных"); 
				
				Возврат Ложь;
				
			КонецПопытки;
			
		КонецЕсли;
		
	КонецЕсли;
	
	Возврат Истина;
	
КонецФункции

///////////////////////////////////////////////////////////////////////////////
// Работа с FTP

Функция ПолучитьFTPСоединение(Таймаут)
	
	ИмяСервераИКаталогНаСервере = РазделитьFTPРесурсНаСерверИКаталог(СокрЛП(Путь));
	ИмяFTPСервера = ИмяСервераИКаталогНаСервере.ИмяСервера;
	КаталогНаFTPСервере = ИмяСервераИКаталогНаСервере.ИмяКаталога;
	
	НастройкиFTP = ТранспортСообщенийОбмена.FTPНастройкиСоединения(Таймаут);
	НастройкиFTP.Сервер               = ИмяFTPСервера;
	НастройкиFTP.Порт                 = Порт;
	НастройкиFTP.ИмяПользователя      = Пользователь;
	НастройкиFTP.ПарольПользователя   = Пароль;
	НастройкиFTP.ПассивноеСоединение  = ПассивноеСоединение;
	НастройкиFTP.ЗащищенноеСоединение = ОбменДаннымиСервер.ЗащищенноеСоединение(Путь);
	
	Возврат ТранспортСообщенийОбмена.FTPСоединение(НастройкиFTP);
	
КонецФункции

Функция ВыполнитьКопированиеФайлаНаFTPСервер(Знач ИмяФайлаИсточника, ИмяФайлаПриемника, Знач Таймаут)
	
	Перем КаталогНаСервере;
	
	СерверИКаталогНаСервере = РазделитьFTPРесурсНаСерверИКаталог(СокрЛП(Путь));
	КаталогНаСервере = СерверИКаталогНаСервере.ИмяКаталога;
	
	Попытка
		FTPСоединение = ПолучитьFTPСоединение(Таймаут);
	Исключение
		
		СообщениеОбОшибке = НСтр("ru = 'Ошибка инициализации подключения к FTP-серверу.'");
		СообщениеОбОшибкеЖР = СообщениеОбОшибке;
		
		ТранспортСообщенийОбмена.ИнформацияОбОшибкеВСообщения(ЭтотОбъект, ИнформацияОбОшибке(), Истина);
		ТранспортСообщенийОбмена.ЗаписатьСообщениеВЖурналРегистрации(ЭтотОбъект, "ВыгрузкаДанных");
		
		Возврат Ложь;
		
	КонецПопытки;
	
	Если Таймаут = ТаймаутПроверкиСоединения 
		И FTPСоединение.ПассивныйРежим 
		И Не ПассивноеСоединение Тогда
		
		СообщениеОбОшибке = НСтр("ru = 'Ошибка при попытке установить активное соединение с FTP-сервером. Попробуйте использовать пассивное соединение.'");
		СообщениеОбОшибкеЖР = СообщениеОбОшибке;
		
		ТранспортСообщенийОбмена.ИнформацияОбОшибкеВСообщения(ЭтотОбъект, ИнформацияОбОшибке(), Истина);
		ТранспортСообщенийОбмена.ЗаписатьСообщениеВЖурналРегистрации(ЭтотОбъект, "ВыгрузкаДанных");
		
		Возврат Ложь;
		
	КонецЕсли;
	
	СоздатьКаталогПриНеобходимости(FTPСоединение, КаталогНаСервере);
	
	Попытка
		FTPСоединение.Записать(ИмяФайлаИсточника, КаталогНаСервере + ИмяФайлаПриемника);
	Исключение
		
		СообщениеОбОшибке = НСтр("ru = 'Ошибка подключения к FTP-серверу, проверьте правильность задания пути и права доступа к ресурсу.'");
		СообщениеОбОшибкеЖР = СообщениеОбОшибке;
		
		ТранспортСообщенийОбмена.ИнформацияОбОшибкеВСообщения(ЭтотОбъект, ИнформацияОбОшибке(), Истина);
		ТранспортСообщенийОбмена.ЗаписатьСообщениеВЖурналРегистрации(ЭтотОбъект, "ВыгрузкаДанных");

		Возврат Ложь;
	КонецПопытки;
	
	Если ОбщегоНазначения.РазделениеВключено() Тогда
		Возврат Истина;
	КонецЕсли;
	
	Попытка
		МассивФайлов = FTPСоединение.НайтиФайлы(КаталогНаСервере, ИмяФайлаПриемника, Ложь);
	Исключение
		
		СообщениеОбОшибке = НСтр("ru = 'Ошибка при поиске файлов на FTP-сервере.'");
		СообщениеОбОшибкеЖР = СообщениеОбОшибке;
		
		ТранспортСообщенийОбмена.ИнформацияОбОшибкеВСообщения(ЭтотОбъект, ИнформацияОбОшибке(), Истина);
		ТранспортСообщенийОбмена.ЗаписатьСообщениеВЖурналРегистрации(ЭтотОбъект, "ВыгрузкаДанных");
		
		Возврат Ложь;
		
	КонецПопытки;
	
	Возврат МассивФайлов.Количество() > 0;
	
КонецФункции

Процедура СоздатьКаталогПриНеобходимости(FTPСоединение, КаталогНаСервере)
	
	Если КаталогНаСервере = "/" Тогда
		Возврат;
	КонецЕсли;
	
	Если ОбщегоНазначения.РазделениеВключено() Тогда
		
		// При работе в сервисе, проверка наличия каталога - очень затратная операция.
		// Поэтому каждый раз используется СоздатьКаталог. 
		// Если каталог уже существует, то переходим в Исключение и ничего не делаем
		Попытка
			FTPСоединение.СоздатьКаталог(КаталогНаСервере);
		Исключение
			// Действий не требуется
		КонецПопытки;
		
	Иначе	
		
		МассивИмен = СтрРазделить(КаталогНаСервере, "/", Ложь);
		ИмяКаталога = "";
		
		Для Каждого Имя Из МассивИмен Цикл
		
			ИмяКаталога = ИмяКаталога + "/" + Имя;
		
			Если FTPСоединение.НайтиФайлы(ИмяКаталога).Количество() = 0 Тогда
				FTPСоединение.СоздатьКаталог(ИмяКаталога);
			КонецЕсли;
		
		КонецЦикла;
	
	КонецЕсли;
	
КонецПроцедуры

Функция ВыполнитьУдалениеФайлаНаFTPСервере(Знач ИмяФайла, ПроверкаПодключения = Ложь)
	
	Перем КаталогНаСервере;
	
	СерверИКаталогНаСервере = РазделитьFTPРесурсНаСерверИКаталог(СокрЛП(Путь));
	КаталогНаСервере = СерверИКаталогНаСервере.ИмяКаталога;
	
	Попытка
		FTPСоединение = ПолучитьFTPСоединение(ТаймаутПроверкиСоединения);
	Исключение
		
		СообщениеОбОшибке = НСтр("ru = 'Ошибка инициализации подключения к FTP-серверу.'");
		СообщениеОбОшибкеЖР = СообщениеОбОшибке;
		
		ТранспортСообщенийОбмена.ИнформацияОбОшибкеВСообщения(ЭтотОбъект, ИнформацияОбОшибке(), Истина); 
		ТранспортСообщенийОбмена.ЗаписатьСообщениеВЖурналРегистрации(ЭтотОбъект);
		
		Возврат Ложь;
		
	КонецПопытки;
	
	Попытка
		FTPСоединение.Удалить(КаталогНаСервере + ИмяФайла);
	Исключение
		
		СообщениеОбОшибке = НСтр("ru = 'Ошибка удаления файла на FTP-сервере, проверьте права доступа к ресурсу.'");
		СообщениеОбОшибкеЖР = СообщениеОбОшибке;
		
		ТранспортСообщенийОбмена.ИнформацияОбОшибкеВСообщения(ЭтотОбъект, ИнформацияОбОшибке(), Истина);
		ТранспортСообщенийОбмена.ЗаписатьСообщениеВЖурналРегистрации(ЭтотОбъект);
		
		Если ПроверкаПодключения Тогда
			
			СообщениеОбОшибке = НСтр("ru = 'Не удалось проверить подключение с помощью тестового файла ""%1"".
				|Возможно, заданный каталог не существует или не доступен.
				|Рекомендуется также обратиться к документации по FTP-серверу для настройки поддержки имен файлов с кириллицей.'");
			СообщениеОбОшибке = СтрШаблон(СообщениеОбОшибке, ИмяФайла);
			СообщениеОбОшибкеЖР = СообщениеОбОшибке;
			
			ТранспортСообщенийОбмена.ЗаписатьСообщениеВЖурналРегистрации(ЭтотОбъект);
			
		КонецЕсли;
		
		Возврат Ложь;
		
	КонецПопытки;
	
	Возврат Истина;
	
КонецФункции

Функция РазделитьFTPРесурсНаСерверИКаталог(Знач ПолныйПуть)
	
	Результат = Новый Структура("ИмяСервера, ИмяКаталога");
	
	ПараметрыFTP = ТранспортСообщенийОбмена.FTPИмяСервераИПуть(ПолныйПуть);
	
	Результат.ИмяСервера  = ПараметрыFTP.Сервер;
	Результат.ИмяКаталога = ПараметрыFTP.Путь;
	
	Возврат Результат;
	
КонецФункции

#КонецОбласти

#Область Инициализация

ВременныйКаталог = Неопределено;
СообщениеОбмена = Неопределено;

ИмяFTPСервера = Неопределено;
КаталогНаFTPСервере = Неопределено;

ТаймаутОтправкиПолученияДанных = 12*60*60;
ТаймаутПроверкиСоединения = 10;

#КонецОбласти

#Иначе
ВызватьИсключение НСтр("ru = 'Недопустимый вызов объекта на клиенте.'");
#КонецЕсли