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

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

// Возвращает настройки смешанного хранения файлов в томах и информационной базе.
//
// Возвращаемое значение:
//   Структура::
//    * РасширенияФайлов   - Строка - расширения файлов, которые хранятся в ИБ.
//                                    Разделены пробелом.
//    * МаксимальныйРазмер - Число - максимальный размер файла, сохраняемого в ИБ, в байтах.
//
Функция ПараметрыХраненияФайловВИнформационнойБазе() Экспорт
	
	УстановитьПривилегированныйРежим(Истина);
	Возврат Константы.ПараметрыХраненияФайловВИБ.Получить().Получить();
	
КонецФункции

// Устанавливает настройки смешанного хранения файлов в томах и информационной базе.
//
// Параметры:
//  ПараметрыХранения - Структура - настройки хранения файлов в ИБ. Свойства:
//    * РасширенияФайлов   - Строка - расширения файлов, которые хранятся в ИБ.
//                           Разделены пробелом.
//    * МаксимальныйРазмер - Число - максимальный размер файла, сохраняемого
//                           в ИБ, в байтах.
//
Процедура УстановитьПараметрыХраненияФайловВИнформационнойБазе(ПараметрыХранения) Экспорт
	
	ХранилищеПараметров = Новый ХранилищеЗначения(ПараметрыХранения, Новый СжатиеДанных(9));
	Константы.ПараметрыХраненияФайловВИБ.Установить(ХранилищеПараметров);
	
КонецПроцедуры

// Возвращает Истина, если настроен хотя бы один том хранения файлов.
//
// Возвращаемое значение:
//   Булево
//
Функция ЕстьТомаХраненияФайлов() Экспорт
	
	Запрос = Новый Запрос;
	Запрос.Текст =
	"ВЫБРАТЬ ПЕРВЫЕ 1
	|	ИСТИНА КАК ЗначениеИстина
	|ИЗ
	|	Справочник.ТомаХраненияФайлов КАК ТомаХраненияФайлов
	|ГДЕ
	|	ТомаХраненияФайлов.ПометкаУдаления = ЛОЖЬ";
	
	УстановитьОтключениеБезопасногоРежима(Истина); 
	УстановитьПривилегированныйРежим(Истина);
	Возврат НЕ Запрос.Выполнить().Пустой();
	
КонецФункции

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

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

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

// Конструктор структуры данных присоединенного файла. Подробнее см. ДобавитьФайл.
// 
// Возвращаемое значение:
//   Структура:
//     Перед добавлением файла должны быть заполнены свойства:
//       * Ссылка                       - ОпределяемыйТип.ПрисоединенныйФайл - ссылка на элемент справочника с файлами.
//       * Наименование                 - Строка - наименование добавляемого файла.
//       * Размер                       - Число - размер файла.
//       * Расширение                   - Строка - расширение добавляемого файла.
//       * ВладелецФайла                - ОпределяемыйТип.ВладелецПрисоединенныхФайлов - ссылка на владельца файла.
//       * ДатаМодификацииУниверсальная - Дата - дата изменения файла.
//       * ДополнительныеСвойства - Структура
//    После добавления можно анализировать свойства:
//       * ТипХраненияФайла - ПеречислениеСсылка.ТипыХраненияФайлов - тип хранения данных файла.
//       * Том              - СправочникСсылка.ТомаХраненияФайлов - том, в который был добавлен файл.
//       * ПутьКФайлу       - Строка - путь в томе, по которому был размещен файл.
//       * ХранимыйФайл     - ХранилищеЗначения - данные добавленного файла.
//
Функция ПараметрыДобавленияФайла() Экспорт
	
	ПараметрыФайла = Новый Структура;
	ПараметрыФайла.Вставить("Ссылка", Неопределено);
	ПараметрыФайла.Вставить("Том", Справочники.ТомаХраненияФайлов.ПустаяСсылка());
	ПараметрыФайла.Вставить("ПутьКФайлу", "");
	ПараметрыФайла.Вставить("Расширение", Неопределено);
	ПараметрыФайла.Вставить("Размер", 0);
	ПараметрыФайла.Вставить("Наименование", "");
	ПараметрыФайла.Вставить("ХранимыйФайл", Неопределено);
	ПараметрыФайла.Вставить("ВладелецФайла", Неопределено);
	ПараметрыФайла.Вставить("ТипХраненияФайла", Неопределено);
	ПараметрыФайла.Вставить("ДатаМодификацииУниверсальная", Неопределено);
	ПараметрыФайла.Вставить("ДополнительныеСвойства", Новый Структура);
	
	Возврат ПараметрыФайла;
	
КонецФункции

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

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

	Если ТипХраненияФайла = Перечисления.ТипыХраненияФайлов.ВИнформационнойБазе Тогда
		
		СсылкаНаФайл = ПрисоединенныйФайл.Ссылка;
		Если Не ЗначениеЗаполнено(СсылкаНаФайл) Тогда
			МетаданныеПрисоединенныйФайл = ПрисоединенныйФайл.Метаданные(); // ОбъектМетаданных
			СсылкаНаФайл = Справочники[МетаданныеПрисоединенныйФайл.Имя].ПолучитьСсылку();
			ПрисоединенныйФайл.УстановитьСсылкуНового(СсылкаНаФайл);
		КонецЕсли;
		
		ДанныеФайла = ?(ТипЗнч(ДвоичныеДанныеИлиПуть) = Тип("Строка"),
			Новый ДвоичныеДанные(ДвоичныеДанныеИлиПуть), ДвоичныеДанныеИлиПуть);
		РаботаСФайламиСлужебный.ЗаписатьФайлВИнформационнуюБазу(СсылкаНаФайл, ДанныеФайла);
		
		ПрисоединенныйФайл.ТипХраненияФайла = Перечисления.ТипыХраненияФайлов.ВИнформационнойБазе;
		ПрисоединенныйФайл.Том = Неопределено;
		ПрисоединенныйФайл.ПутьКФайлу = "";
		Если ЗаполнятьСлужебныйРеквизитХранилище Тогда
			ПрисоединенныйФайл.ФайлХранилище = Новый ХранилищеЗначения(ДанныеФайла, Новый СжатиеДанных(9));
		КонецЕсли;

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

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

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

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

Функция СобытиеЖурналаРегистрации()
	
	Возврат НСтр("ru = 'Файлы.Удаление файлов в томе'", ОбщегоНазначения.КодОсновногоЯзыка());
	
КонецФункции

Функция СообщениеЖурналаРегистрации(ТекстСообщения)
	
	Если Не ОбщегоНазначения.РежимОтладки() Тогда
		Возврат ТекстСообщения;
	КонецЕсли;
	
	Попытка
		ВызватьИсключение ТекстСообщения;
	Исключение
		ИнформацияОбОшибке = ИнформацияОбОшибке();
	КонецПопытки;
	Возврат ОбработкаОшибок.ПодробноеПредставлениеОшибки(ИнформацияОбОшибке);
		
КонецФункции

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

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

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

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

// Возвращает полный путь к корневому каталогу тома хранения файлов.
//
// Параметры:
//   Том - СправочникСсылка.ТомаХраненияФайлов - том, путь к корневому каталогу которого необходимо получить.
//
// Возвращаемое значение:
//   Строка - полный путь к корневому каталогу тома.
//
Функция ПолныйПутьТома(Том) Экспорт

	УстановитьОтключениеБезопасногоРежима(Истина);
	УстановитьПривилегированныйРежим(Истина);
	
	КорневойКаталог = ОбщегоНазначения.ЗначениеРеквизитаОбъекта(Том,
		?(ОбщегоНазначения.ЭтоWindowsСервер(), "ПолныйПутьWindows", "ПолныйПутьLinux"));
	
	Если ОбщегоНазначения.РазделениеВключено() Тогда
		МодульРаботаВМоделиСервиса = ОбщегоНазначения.ОбщийМодуль("РаботаВМоделиСервиса");
		ЗначениеРазделителя = "";
		Если ПутьКТомуБезУчетаРегиональныхНастроек() Тогда
			ЗначениеРазделителя = ?(МодульРаботаВМоделиСервиса.ИспользованиеРазделителяСеанса(),
									Формат(МодульРаботаВМоделиСервиса.ЗначениеРазделителяСеанса(), "ЧГ=;"),
									"");
		Иначе
			ЗначениеРазделителя = ?(МодульРаботаВМоделиСервиса.ИспользованиеРазделителяСеанса(),
									МодульРаботаВМоделиСервиса.ЗначениеРазделителяСеанса(),
									"");
		КонецЕсли;
	Иначе
		ЗначениеРазделителя = "";
	КонецЕсли;
	
	Возврат СтрЗаменить(КорневойКаталог, "%z",ЗначениеРазделителя);
	
КонецФункции

// Возвращает суммарный размер всех файлов в томе в байтах.
//
// Параметры:
//   Том - СправочникСсылка.ТомаХраненияФайлов - том, размер которого необходимо посчитать.
//
// Возвращаемое значение:
//   Число - суммарный размер файлов в томе.
//
Функция ОбъемТома(Том) Экспорт
	
	ОбъемТома = 0;
	Если Не ОбщегоНазначения.ДоступноИспользованиеРазделенныхДанных() Тогда
		Возврат ОбъемТома;
	КонецЕсли;
	
	Запрос = Новый Запрос;
	ТипыПрисоединенныхФайлов = Метаданные.ОпределяемыеТипы.ПрисоединенныйФайл.Тип.Типы();
	
	Запрос = Новый Запрос;
	Запрос.Текст = 
	"ВЫБРАТЬ
	|	ЕСТЬNULL(СУММА(Версии.Размер), 0) КАК РазмерФайлов
	|ИЗ
	|	Справочник.ВерсииФайлов КАК Версии
	|ГДЕ
	|	Версии.Том = &Том";
	
	Для Каждого Тип Из ТипыПрисоединенныхФайлов Цикл
		
		Если Тип = Тип("СправочникСсылка.ВерсииФайлов")
			Или Тип = Тип("СправочникСсылка.ИдентификаторыОбъектовМетаданных") Тогда
			
			Продолжить;
		КонецЕсли;
		
		МетаданныеСправочника = Метаданные.НайтиПоТипу(Тип);
		Если МетаданныеСправочника.Реквизиты.Найти("ТекущаяВерсия") <> Неопределено Тогда
			Продолжить;
		КонецЕсли;
		
		ТекстЗапросаПоСправочнику = "ВЫБРАТЬ
		|	ЕСТЬNULL(СУММА(ПрисоединенныеФайлы.Размер), 0)
		|ИЗ
		|	&ИмяСправочника КАК ПрисоединенныеФайлы
		|ГДЕ
		|	ПрисоединенныеФайлы.Том = &Том";
		
		Запрос.Текст = Запрос.Текст + "
		|
		|ОБЪЕДИНИТЬ ВСЕ
		|
		|" + СтрЗаменить(ТекстЗапросаПоСправочнику, "&ИмяСправочника",
			Метаданные.НайтиПоТипу(Тип).ПолноеИмя());
		
	КонецЦикла;
	
	Запрос.Параметры.Вставить("Том", Том);
	Выборка = Запрос.Выполнить().Выбрать();
	Пока Выборка.Следующий() Цикл
		ОбъемТома = ОбъемТома + Выборка.РазмерФайлов;
	КонецЦикла;
			
	Возврат ОбъемТома;
	
КонецФункции

// Проверяет наличие файла в томе.
//
// Параметры:
//   ПрисоединенныйФайл - ОпределяемыйТип.ПрисоединенныйФайл - элемент справочника с файлом.
//
// Возвращаемое значение:
//   Булево
//
Функция ПрисоединенныйФайлНаходитсяВТоме(ПрисоединенныйФайл) Экспорт
	
	СвойстваФайла = СвойстваФайлаВТоме(ПрисоединенныйФайл);
	ПутьКФайлу = ПолноеИмяФайлаВТоме(СвойстваФайла);
	Если Не ЗначениеЗаполнено(ПутьКФайлу) Тогда
		Возврат Ложь;
	КонецЕсли;
			
	ПроверяемыйФайл = Новый Файл(ПутьКФайлу);
	Если ПроверяемыйФайл.Существует() Тогда
		Возврат Истина;
	КонецЕсли;
	
	Возврат Ложь;
КонецФункции

#Область КонтрольВеденияУчета

// См. КонтрольВеденияУчетаПереопределяемый.ПриОпределенииПроверок
Процедура ПриОпределенииПроверок(ГруппыПроверок, Проверки) Экспорт
	
	Проверка = Проверки.Добавить();
	Проверка.ИдентификаторГруппы          = "СистемныеПроверки";
	Проверка.Наименование                 = НСтр("ru = 'Поиск ссылок на несуществующие файлы в томах хранения'");
	Проверка.Причины                      = НСтр("ru = 'Файл был физически удален или перемещен на компьютере вследствие работы антивируса,
		|непреднамеренных действий администратора и.т.д.'");
	Проверка.Рекомендация                 = НСтр("ru = '• Пометить файл в приложении на удаление;
		|• Или восстановить файл в томе из резервной копии.'");
	Проверка.Идентификатор                = "СтандартныеПодсистемы.ПроверкаСсылокНаНесуществующиеФайлыВТоме";
	Проверка.ОбработчикПроверки           = "РаботаСФайламиВТомахСлужебный.ПроверкаСсылокНаНесуществующиеФайлыВТоме";
	Проверка.КонтекстПроверокВеденияУчета = "СистемныеПроверки";
	Проверка.Отключена                    = Истина;
	
КонецПроцедуры

Процедура ПроверкаСсылокНаНесуществующиеФайлыВТоме(Проверка, ПараметрыПроверки) Экспорт
	
	Если ОбщегоНазначения.РазделениеВключено()
		Или Не ХранитьФайлыВТомах() Тогда
		Возврат;
	КонецЕсли;
	
	ДоступныеТома = ДоступныеТома(ПараметрыПроверки);
	Если ДоступныеТома.Количество() = 0 Тогда
		Возврат;
	КонецЕсли;
	
	МодульРаботаВМоделиСервиса = Неопределено;
	Если ОбщегоНазначения.ПодсистемаСуществует("СтандартныеПодсистемы.РаботаВМоделиСервиса") Тогда
		МодульРаботаВМоделиСервиса = ОбщегоНазначения.ОбщийМодуль("РаботаВМоделиСервиса");
	КонецЕсли;
	
	ВидыОбъектовМетаданных = Новый Массив;
	ВидыОбъектовМетаданных.Добавить(Метаданные.Справочники);
	ВидыОбъектовМетаданных.Добавить(Метаданные.Документы);
	ВидыОбъектовМетаданных.Добавить(Метаданные.ПланыСчетов);
	ВидыОбъектовМетаданных.Добавить(Метаданные.ПланыВидовХарактеристик);
	ВидыОбъектовМетаданных.Добавить(Метаданные.Задачи);
	
	Для Каждого ВидОбъектаМетаданных Из ВидыОбъектовМетаданных Цикл
		Для Каждого ОбъектМетаданных Из ВидОбъектаМетаданных Цикл
			Если МодульРаботаВМоделиСервиса <> Неопределено 
				И Не МодульРаботаВМоделиСервиса.ЭтоРазделенныйОбъектМетаданных(ОбъектМетаданных.ПолноеИмя()) Тогда
				Продолжить;
			КонецЕсли;
			Если Не ПроверитьОбъектПрисоединенныхФайлов(ОбъектМетаданных) Тогда
				Продолжить;
			КонецЕсли;
			// @skip-check query-in-loop - Порционная обработка большого объема данных.
			ПоискСсылокНаНесуществующиеФайлыВТомах(ОбъектМетаданных, ПараметрыПроверки, ДоступныеТома);
		КонецЦикла;
	КонецЦикла;
	
КонецПроцедуры

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

// Возвращает признак того, что файлы могут храниться в томах.
//
// Возвращаемое значение:
//  Булево
//
Функция ХранитьФайлыВТомах() Экспорт
	
	УстановитьПривилегированныйРежим(Истина);
	СпособХраненияФайлов = Константы.СпособХраненияФайлов.Получить();
	Возврат СпособХраненияФайлов = "ВТомахНаДиске"
		Или СпособХраненияФайлов = "ВИнформационнойБазеИТомахНаДиске";
	
КонецФункции

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

// Возвращает размеры всех файлов в каждом указанном томе в байтах.
//
// Параметры:
//   Тома - Массив из СправочникСсылка.ТомаХраненияФайлов
//
// Возвращаемое значение:
//   Соответствие из КлючИЗначение:
//     * Ключ - СправочникСсылка.ТомаХраненияФайлов
//     * Значение - Число
//
Функция ОбъемыТомов(Тома)
	
	Результат = Новый Соответствие;
	Для Каждого Том Из Тома Цикл
		Результат[Том] = 0;
	КонецЦикла; 
	
	Если Не ОбщегоНазначения.ДоступноИспользованиеРазделенныхДанных() Тогда
		Возврат Результат;
	КонецЕсли;
	
	ТипыПрисоединенныхФайлов = Метаданные.ОпределяемыеТипы.ПрисоединенныйФайл.Тип.Типы();
	ТекстыЗапросов = Новый Массив;
	
	ТекстЗапроса = 
	"ВЫБРАТЬ
	|	Версии.Том КАК Том,
	|	ЕСТЬNULL(Версии.Размер, 0) КАК РазмерФайлов
	|ИЗ
	|	Справочник.ВерсииФайлов КАК Версии
	|ГДЕ
	|	Версии.Том В (&Тома)";
	ТекстыЗапросов.Добавить(ТекстЗапроса);
	
	ШаблонТекстаЗапроса = "ВЫБРАТЬ
	|	ПрисоединенныеФайлы.Том,
	|	ЕСТЬNULL(ПрисоединенныеФайлы.Размер, 0)
	|ИЗ
	|	&ИмяСправочника КАК ПрисоединенныеФайлы
	|ГДЕ
	|	ПрисоединенныеФайлы.Том В (&Тома)";
	
	Для Каждого Тип Из ТипыПрисоединенныхФайлов Цикл
		
		Если Тип = Тип("СправочникСсылка.ВерсииФайлов")
			Или Тип = Тип("СправочникСсылка.ИдентификаторыОбъектовМетаданных") Тогда
			
			Продолжить;
		КонецЕсли;
		
		МетаданныеСправочника = Метаданные.НайтиПоТипу(Тип);
		Если МетаданныеСправочника.Реквизиты.Найти("ТекущаяВерсия") <> Неопределено Тогда
			Продолжить;
		КонецЕсли;
		
		ТекстыЗапросов.Добавить(СтрЗаменить(ШаблонТекстаЗапроса, "&ИмяСправочника", 
			Метаданные.НайтиПоТипу(Тип).ПолноеИмя()));
	КонецЦикла;
	
	ТекстЗапроса = 
		"ВЫБРАТЬ
		|	ВложенныйЗапрос.Том,
		|	СУММА(ВложенныйЗапрос.РазмерФайлов) КАК РазмерФайлов
		|ИЗ
		|	&ТекстЗапросаПрисоединенныхФайлов КАК ВложенныйЗапрос
		|СГРУППИРОВАТЬ ПО
		|	ВложенныйЗапрос.Том";
	ТекстЗапроса = СтрЗаменить(ТекстЗапроса, "&ТекстЗапросаПрисоединенныхФайлов", 
		"(" + СтрСоединить(ТекстыЗапросов, Символы.ПС + "ОБЪЕДИНИТЬ ВСЕ" + Символы.ПС) + ")"); // @query-part-3
	
	Запрос = Новый Запрос(ТекстЗапроса); 
	Запрос.Параметры.Вставить("Тома", Тома);
	Выборка = Запрос.Выполнить().Выбрать();
	Пока Выборка.Следующий() Цикл
		Результат[Выборка.Том] = Выборка.РазмерФайлов;
	КонецЦикла;
			
	Возврат Результат;
	
КонецФункции

// Для функции см. ПолныйПутьТома.
// 
// Возвращаемое значение:
//  Булево
//
Функция ПутьКТомуБезУчетаРегиональныхНастроек() Экспорт
	Возврат РаботаСФайламиСлужебныйПовтИсп.ПутьКТомуБезУчетаРегиональныхНастроек();
КонецФункции

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

Процедура ОчиститьУдаленныеФайлы() Экспорт
	Если НЕ ХранитьФайлыВТомах() Тогда
		Возврат;
	КонецЕсли;
	
	ОбрабатываемыеТома = ДоступныеТома();
	ОписаниеТомов = ОбщегоНазначения.ЗначенияРеквизитовОбъектов(ОбрабатываемыеТома, 
		"Ссылка, ВремяПоследнейОчисткиФайлов, ПометкаУдаления, ПолныйПутьLinux, ПолныйПутьWindows");
		
	ОбрабатываемыеКаталоги = Новый Соответствие; // несколько томов могут хранить файлы в одном каталоге
	Для Каждого ОписаниеТома Из ОписаниеТомов Цикл
		КорневойКаталог = ПолныйПутьТома(ОписаниеТома.Ключ);
		
		КаталогПоискаУдаляемыхФайлов = ОбрабатываемыеКаталоги[КорневойКаталог];
		Если КаталогПоискаУдаляемыхФайлов = Неопределено Тогда
			КаталогПоискаУдаляемыхФайлов = КаталогПоискаУдаляемыхФайлов(КорневойКаталог);
			ОбрабатываемыеКаталоги.Вставить(КорневойКаталог, КаталогПоискаУдаляемыхФайлов);
		КонецЕсли;
		
		ДобавитьТом(КаталогПоискаУдаляемыхФайлов, ОписаниеТома.Значение);
	КонецЦикла;
	
	Для Каждого Том Из ОбрабатываемыеКаталоги Цикл
		// @skip-check query-in-loop - Незначительное количество вызовов.
		ОчиститьУдаленныеФайлыВТоме(Том.Значение);
	КонецЦикла;
КонецПроцедуры

// Параметры:
//  Путь - Строка - путь
// 
// Возвращаемое значение:
//  Структура - каталог поиска удаляемых файлов:
//   * Путь - Строка
//   * Тома - ТаблицаЗначений:
//   ** Ссылка - СправочникСсылка.ТомаХраненияФайлов
//   ** ВремяПоследнейОчисткиФайлов - Дата
//   ** ПометкаУдаления - Булево
//   * ВремяПоследнейОчисткиФайлов - Дата
//   * Обрабатывать - Булево
//
Функция КаталогПоискаУдаляемыхФайлов(Путь)
	Каталог = Новый Структура;
	Каталог.Вставить("Путь", Путь);
	
	ТаблицаТомов = Новый ТаблицаЗначений;
	ТаблицаТомов.Колонки.Добавить("Ссылка", Новый ОписаниеТипов("СправочникСсылка.ТомаХраненияФайлов"));
	ТаблицаТомов.Колонки.Добавить("ВремяПоследнейОчисткиФайлов", Новый ОписаниеТипов("Дата"));
	ТаблицаТомов.Колонки.Добавить("ПометкаУдаления", Новый ОписаниеТипов("Булево"));
	
	Каталог.Вставить("Тома", ТаблицаТомов);
	Каталог.Вставить("ВремяПоследнейОчисткиФайлов", Дата(1,1,1));
	Каталог.Вставить("Обрабатывать", Истина);
	Возврат Каталог;
КонецФункции

// Параметры:
//  КаталогПоискаУдаляемыхФайлов - см. КаталогПоискаУдаляемыхФайлов
//
Процедура ДобавитьТом(КаталогПоискаУдаляемыхФайлов, Том)
	// Не обрабатываем тома помеченные на удаление.
	Если Том.ПометкаУдаления <> Неопределено И Том.ПометкаУдаления Тогда
		КаталогПоискаУдаляемыхФайлов.Обрабатывать = Ложь;
		Возврат;
	КонецЕсли;
	
	// При использовании разделения и общих томов (нет разделения на области в томах) нет возможности 
	// отделить удаленные файлы и файлы других областей.
	Если ОбщегоНазначения.РазделениеВключено() 
			И (ЗначениеЗаполнено(Том.ПолныйПутьLinux) И СтрНайти(Том.ПолныйПутьLinux, "%z") = 0
				ИЛИ ЗначениеЗаполнено(Том.ПолныйПутьWindows) И СтрНайти(Том.ПолныйПутьWindows, "%z") = 0) Тогда
		
		КаталогПоискаУдаляемыхФайлов.Обрабатывать = Ложь;	
		Возврат;
	КонецЕсли;
	
	ВремяПоследнейОчисткиФайлов = ?(КаталогПоискаУдаляемыхФайлов.ВремяПоследнейОчисткиФайлов < Том.ВремяПоследнейОчисткиФайлов,
									Том.ВремяПоследнейОчисткиФайлов,
									КаталогПоискаУдаляемыхФайлов.ВремяПоследнейОчисткиФайлов);
	КаталогПоискаУдаляемыхФайлов.ВремяПоследнейОчисткиФайлов = ВремяПоследнейОчисткиФайлов;
	
	СтрокаТома = КаталогПоискаУдаляемыхФайлов.Тома.Добавить();
	ЗаполнитьЗначенияСвойств(СтрокаТома, Том);
КонецПроцедуры

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

КонецПроцедуры

// Возвращает удаленные файлы.
// 
// Параметры:
//  Тома - Массив из СправочникСсылка.ТомаХраненияФайлов
//  КорневойКаталог - Строка
//  ФайлыДляУдаления - Массив из Файл
// 
// Возвращаемое значение:
//   Массив из Файл
//  
Функция УдаленныеФайлы(Тома, КорневойКаталог, ФайлыДляУдаления)
	Результат = Новый Массив;
	
	СвойстваФайловойСистемы = СвойстваФайловойСистемы(КорневойКаталог);
	
	ИсправлятьПутиФайлов = Не СвойстваФайловойСистемы.ПробелВНачалеИмениФайла
		Или Не СвойстваФайловойСистемы.ТочкаВКонцеИмениФайла;
	
	ТаблицаФайлы = Новый ТаблицаЗначений();
	ДлинаСтроки = Метаданные.Справочники.Файлы.Реквизиты.ПутьКФайлу.Тип;
	ТаблицаФайлы.Колонки.Добавить("Путь", ДлинаСтроки);
	
	Если ИсправлятьПутиФайлов Тогда
		ТаблицаФайлы.Колонки.Добавить("АльтернативныйПуть", ДлинаСтроки);
		ТаблицаФайлы.Колонки.Добавить("ИспользоватьАльтернативныйПуть", Новый ОписаниеТипов("Булево"));
	КонецЕсли;
	
	Для Каждого Файл Из ФайлыДляУдаления Цикл
		ОтносительныйПуть = СтрЗаменить(Файл, КорневойКаталог, "");
		СтрокаФайла = ТаблицаФайлы.Добавить();
		СтрокаФайла.Путь = ОтносительныйПуть;
		
		Если ИсправлятьПутиФайлов Тогда
			
			СвойстваФайла = Новый Файл(ОтносительныйПуть);
			
			АльтернативныйПуть = ОтносительныйПуть;
			ДобавитьСтрокуСПробелом = Ложь;
			Если Не СвойстваФайловойСистемы.ПробелВНачалеИмениФайла
				И Лев(СвойстваФайла.Имя, 1) <> " " Тогда
				АльтернативныйПуть = СвойстваФайла.Путь + " " + СвойстваФайла.Имя;
				ДобавитьСтрокуСПробелом = Истина;
			КонецЕсли;
			
			ДобавитьСтрокуСТочкой = Ложь;
			Если Не СвойстваФайловойСистемы.ТочкаВКонцеИмениФайла 
				И СвойстваФайла.Расширение = "" Тогда
				АльтернативныйПуть = АльтернативныйПуть + ".";
				ДобавитьСтрокуСТочкой = Истина;
			КонецЕсли;
			
			Если ДобавитьСтрокуСПробелом Или ДобавитьСтрокуСТочкой Тогда
				СтрокаФайла = ТаблицаФайлы.Добавить();
				СтрокаФайла.Путь = ОтносительныйПуть;
				СтрокаФайла.ИспользоватьАльтернативныйПуть = Истина;
				СтрокаФайла.АльтернативныйПуть = АльтернативныйПуть;
			КонецЕсли;
			
			Если ДобавитьСтрокуСПробелом И ДобавитьСтрокуСТочкой Тогда
				СтрокаФайла = ТаблицаФайлы.Добавить();
				СтрокаФайла.Путь = ОтносительныйПуть;
				СтрокаФайла.ИспользоватьАльтернативныйПуть = Истина;
				СтрокаФайла.АльтернативныйПуть = СвойстваФайла.Путь + " " + СвойстваФайла.Имя;

				СтрокаФайла = ТаблицаФайлы.Добавить();
				СтрокаФайла.Путь = ОтносительныйПуть;
				СтрокаФайла.ИспользоватьАльтернативныйПуть = Истина;
				СтрокаФайла.АльтернативныйПуть = ОтносительныйПуть + ".";
			КонецЕсли;
		КонецЕсли;
	КонецЦикла;
	
	Если ИсправлятьПутиФайлов Тогда
		Запрос = ЗапросПроверкиСуществованияФайловСВосстановленнымиСвязями(ТаблицаФайлы);
		Запрос.УстановитьПараметр("Тома", Тома);
		РезультатыЗапроса = Запрос.ВыполнитьПакет();
		ЛишниеФайлы = РезультатыЗапроса[РезультатыЗапроса.ВГраница() - 1];
		
		ФайлыДляИсправления = РезультатыЗапроса[РезультатыЗапроса.ВГраница()].Выгрузить();
		УстановитьПутиХраненияФайлов(ФайлыДляИсправления, "");
	Иначе
		Запрос = ЗапросПроверкиСуществованияФайлов(ТаблицаФайлы);
		Запрос.УстановитьПараметр("Тома", Тома);
		ЛишниеФайлы = Запрос.Выполнить();
	КонецЕсли;
	
	Выборка = ЛишниеФайлы.Выбрать();
	Пока Выборка.Следующий() Цикл
		Результат.Добавить(КорневойКаталог + СокрЛП(Выборка.Путь));
	КонецЦикла;
	
	Возврат Результат;
КонецФункции

// Длина файлов в запросе ограничена:
// Имя метаданного (80) + дата (6) + подкаталог для исключения повторения (6 символов) + имя файла (150).
// Итого: 242.
//
Функция ЗапросПроверкиСуществованияФайлов(Знач ТаблицаФайлы)
	МенеджерТаблиц = Новый МенеджерВременныхТаблиц();
	Запрос = Новый Запрос("ВЫБРАТЬ
	|	ВЫРАЗИТЬ(Файлы.Путь КАК Строка(1024)) КАК Путь
	|ПОМЕСТИТЬ Файлы
	|ИЗ
	|	&Файлы КАК Файлы");  
	
	Запрос.МенеджерВременныхТаблиц = МенеджерТаблиц;
	Запрос.УстановитьПараметр("Файлы", ТаблицаФайлы);
	Запрос.Выполнить();
	
	ЧастиЗапросаНайденныхФайлов = Новый Массив;
	
	ШапкаЗапросаНайденныхФайлов = "ВЫБРАТЬ
	|	"""" КАК Путь
	|ПОМЕСТИТЬ СуществующиеФайлы
	|ГДЕ 
	|	ЛОЖЬ";
	ЧастиЗапросаНайденныхФайлов.Добавить(ШапкаЗапросаНайденныхФайлов);
	
	ШаблонЗапросаНайденныхФайлов = "
	|ВЫБРАТЬ
	|	Файлы.Путь КАК Путь
	|ИЗ
	|	Файлы КАК Файлы
	|	ВНУТРЕННЕЕ СОЕДИНЕНИЕ &СправочникПрисоединенныхФайлов КАК ПрисоединенныеФайлы
	|		ПО ПрисоединенныеФайлы.Том В (&Тома)
	|		И ВЫРАЗИТЬ(ПрисоединенныеФайлы.ПутьКФайлу КАК Строка(1024)) = Файлы.Путь";
	
	ТипыПрисоединенныхФайлов = Метаданные.ОпределяемыеТипы.ПрисоединенныйФайл.Тип.Типы();
	Для Каждого Тип Из ТипыПрисоединенныхФайлов Цикл
		МетаданныеПрисоединенныеФайлы = Метаданные.НайтиПоТипу(Тип);
		ТекстЗапроса = СтрЗаменить(ШаблонЗапросаНайденныхФайлов, "&СправочникПрисоединенныхФайлов", МетаданныеПрисоединенныеФайлы.ПолноеИмя());
		ЧастиЗапросаНайденныхФайлов.Добавить(ТекстЗапроса);
	КонецЦикла;
	
	Разделитель = "
	|ОБЪЕДИНИТЬ ВСЕ
	|";
	
	ТекстЗапроса = СтрСоединить(ЧастиЗапросаНайденныхФайлов, Разделитель);
	
	ТекстЗапроса = ТекстЗапроса + ОбщегоНазначения.РазделительПакетаЗапросов() + "
	|ВЫБРАТЬ 
	|	Файлы.Путь КАК Путь
	|ИЗ
	|	Файлы КАК Файлы
	|ГДЕ
	|	НЕ Файлы.Путь В (
	|		ВЫБРАТЬ
	|			СуществующиеФайлы.Путь КАК Путь
	|		ИЗ
	|			СуществующиеФайлы КАК СуществующиеФайлы)";

	Запрос = Новый Запрос(ТекстЗапроса);
	Запрос.МенеджерВременныхТаблиц = МенеджерТаблиц;
	Возврат Запрос;
КонецФункции

Функция ЗапросПроверкиСуществованияФайловСВосстановленнымиСвязями(Знач ТаблицаФайлы)
	МенеджерТаблиц = Новый МенеджерВременныхТаблиц();
	Запрос = Новый Запрос("ВЫБРАТЬ
	|	ВЫРАЗИТЬ(Файлы.АльтернативныйПуть КАК Строка(1024)) КАК АльтернативныйПуть,
	|	ВЫРАЗИТЬ(Файлы.ИспользоватьАльтернативныйПуть КАК Булево) КАК ИспользоватьАльтернативныйПуть,
	|	ВЫРАЗИТЬ(Файлы.Путь КАК Строка(1024)) КАК Путь
	|ПОМЕСТИТЬ Файлы
	|ИЗ
	|	&Файлы КАК Файлы");  
	
	Запрос.МенеджерВременныхТаблиц = МенеджерТаблиц;
	Запрос.УстановитьПараметр("Файлы", ТаблицаФайлы);
	Запрос.Выполнить();
	
	ЧастиЗапросаНайденныхФайлов = Новый Массив;

	ШапкаЗапросаНайденныхФайлов = "ВЫБРАТЬ
	|	ЛОЖЬ КАК ИспользованАльтернативныйПуть,
	|	ЗНАЧЕНИЕ(Справочник.Файлы.ПустаяСсылка) КАК Ссылка,
	|	"""" АльтернативныйПуть,
	|	"""" КАК Путь
	|ПОМЕСТИТЬ СуществующиеФайлы
	|ГДЕ 
	|	ЛОЖЬ";
	ЧастиЗапросаНайденныхФайлов.Добавить(ШапкаЗапросаНайденныхФайлов);

	ШаблонЗапросаНайденныхФайлов = "
	|ВЫБРАТЬ
	|	Файлы.ИспользоватьАльтернативныйПуть КАК ИспользованАльтернативныйПуть,
	|	ПрисоединенныеФайлы.Ссылка,
	|	Файлы.АльтернативныйПуть,
	|	Файлы.Путь КАК Путь
	|ИЗ
	|	Файлы КАК Файлы
	|	ВНУТРЕННЕЕ СОЕДИНЕНИЕ &СправочникПрисоединенныхФайлов КАК ПрисоединенныеФайлы
	|		ПО ПрисоединенныеФайлы.Том В (&Тома)
	|		И ((НЕ Файлы.ИспользоватьАльтернативныйПуть И ВЫРАЗИТЬ(ПрисоединенныеФайлы.ПутьКФайлу КАК Строка(1024)) = Файлы.Путь)
	|			ИЛИ (Файлы.ИспользоватьАльтернативныйПуть И ВЫРАЗИТЬ(ПрисоединенныеФайлы.ПутьКФайлу КАК Строка(1024)) = Файлы.АльтернативныйПуть))";
	
	ТипыПрисоединенныхФайлов = Метаданные.ОпределяемыеТипы.ПрисоединенныйФайл.Тип.Типы();
	Для Каждого Тип Из ТипыПрисоединенныхФайлов Цикл
		МетаданныеПрисоединенныеФайлы = Метаданные.НайтиПоТипу(Тип);
		ТекстЗапроса = СтрЗаменить(ШаблонЗапросаНайденныхФайлов, "&СправочникПрисоединенныхФайлов", МетаданныеПрисоединенныеФайлы.ПолноеИмя());
		ЧастиЗапросаНайденныхФайлов.Добавить(ТекстЗапроса);
	КонецЦикла;
	
	Разделитель = "
	|ОБЪЕДИНИТЬ ВСЕ
	|";
	
	ТекстЗапроса = СтрСоединить(ЧастиЗапросаНайденныхФайлов, Разделитель);
	
	ТекстЗапроса = ТекстЗапроса + ОбщегоНазначения.РазделительПакетаЗапросов() + 
	"ВЫБРАТЬ
	|	СуществующиеФайлы.Ссылка КАК Файл,
	|	СуществующиеФайлыВерсии.Ссылка КАК Версия
	|ПОМЕСТИТЬ ФайлыВерсии
	|ИЗ
	|	СуществующиеФайлы КАК СуществующиеФайлы
	|		ВНУТРЕННЕЕ СОЕДИНЕНИЕ СуществующиеФайлы КАК СуществующиеФайлыВерсии
	|		ПО (ВЫБОР
	|				КОГДА ТИП(Справочник.ВерсииФайлов) = ТИПЗНАЧЕНИЯ(СуществующиеФайлыВерсии.Ссылка)
	|						И ВЫРАЗИТЬ(СуществующиеФайлыВерсии.Ссылка КАК Справочник.ВерсииФайлов).Владелец = СуществующиеФайлы.Ссылка
	|					ТОГДА ИСТИНА
	|				ИНАЧЕ ЛОЖЬ
	|			КОНЕЦ)
	|			И СуществующиеФайлы.Путь = СуществующиеФайлыВерсии.Путь
	|;
	|
	|////////////////////////////////////////////////////////////////////////////////
	|ВЫБРАТЬ
	|	СуществующиеФайлы.Путь КАК Путь
	|ПОМЕСТИТЬ УникальныеСвязи
	|ИЗ
	|	СуществующиеФайлы КАК СуществующиеФайлы
	|		ЛЕВОЕ СОЕДИНЕНИЕ ФайлыВерсии КАК ФайлыВерсии
	|		ПО (СуществующиеФайлы.Ссылка = ФайлыВерсии.Файл
	|				ИЛИ СуществующиеФайлы.Ссылка = ФайлыВерсии.Версия)
	|
	|СГРУППИРОВАТЬ ПО
	|	СуществующиеФайлы.Путь
	|
	|ИМЕЮЩИЕ
	|	СУММА(ВЫБОР
	|			КОГДА СуществующиеФайлы.Ссылка = ЕСТЬNULL(ФайлыВерсии.Файл, СуществующиеФайлы.Ссылка)
	|				ТОГДА 1
	|			ИНАЧЕ 0
	|		КОНЕЦ) = 1
	|;
	|
	|////////////////////////////////////////////////////////////////////////////////
	|ВЫБРАТЬ
	|	Файлы.Путь КАК Путь
	|ИЗ
	|	Файлы КАК Файлы
	|ГДЕ
	|	НЕ Файлы.Путь В
	|				(ВЫБРАТЬ
	|					УникальныеСвязи.Путь КАК Путь
	|				ИЗ
	|					УникальныеСвязи КАК УникальныеСвязи)
	|;
	|
	|////////////////////////////////////////////////////////////////////////////////
	|ВЫБРАТЬ
	|	СуществующиеФайлы.Путь КАК ПолноеИмя,
	|	СуществующиеФайлы.Ссылка КАК Файл
	|ИЗ
	|	СуществующиеФайлы КАК СуществующиеФайлы
	|ГДЕ
	|	СуществующиеФайлы.ИспользованАльтернативныйПуть = ИСТИНА
	|	И СуществующиеФайлы.Путь В
	|			(ВЫБРАТЬ
	|				УникальныеСвязи.Путь КАК Путь
	|			ИЗ
	|				УникальныеСвязи КАК УникальныеСвязи)";

	Запрос = Новый Запрос(ТекстЗапроса);
	Запрос.МенеджерВременныхТаблиц = МенеджерТаблиц;
	Возврат Запрос;
КонецФункции

Функция ФайлыДляУдаления(Каталог, ВремяПоследнейОчистки)
	ФайлыДляУдаления = Новый Массив;
	
	ОписаниеКаталога = Новый Файл(Каталог);
	ПроверятьФайлыВТекущемКаталоге = ВремяПоследнейОчистки <= ОписаниеКаталога.ПолучитьУниверсальноеВремяИзменения();
	Для Каждого Файл Из НайтиФайлы(Каталог, ПолучитьМаскуВсеФайлы(), Ложь) Цикл
		Если Файл.ЭтоКаталог() Тогда
			ОбщегоНазначенияКлиентСервер.ДополнитьМассив(
				ФайлыДляУдаления,
				ФайлыДляУдаления(Файл.ПолноеИмя, ВремяПоследнейОчистки));
		Иначе
			Если ПроверятьФайлыВТекущемКаталоге И НЕ Файл.ЭтоКаталог() Тогда
				ФайлыДляУдаления.Добавить(Файл.ПолноеИмя);
			КонецЕсли;
		КонецЕсли;
	КонецЦикла;
	
	Возврат ФайлыДляУдаления;
КонецФункции

// Вызывается до начала транзакции.
// Для новых файлов все данные файлы должны быть заполнены.
// 
// Параметры:
//  Контекст - см. РаботаСФайламиСлужебный.КонтекстОбновленияФайла
//
Процедура ПередОбновлениемДанныхФайла(Контекст) Экспорт
	Контекст.ИзменяемыеРеквизиты.Вставить("ПутьКФайлу", "");
	Контекст.ИзменяемыеРеквизиты.Вставить("Том", Справочники.ТомаХраненияФайлов.ПустаяСсылка());
	Если НЕ Контекст.ЭтоНовый Тогда
		СвойстваФайла = СвойстваФайлаВТоме(Контекст.ПрисоединенныйФайл);
		Контекст.СтарыйПутьКФайлу = ПолноеИмяФайлаВТоме(СвойстваФайла);
	КонецЕсли;
	
	КонтейнерСвойствФайла = ПараметрыДобавленияФайла();
	КонтейнерСвойствФайла.Ссылка = Контекст.ПрисоединенныйФайл;
	ЗаполнитьЗначенияСвойств(КонтейнерСвойствФайла, Контекст.ПараметрыДобавленияФайла,,"Ссылка");
	КонтейнерСвойствФайла.ПутьКФайлу = ""; // Всегда будем создавать новую версию в томе, не затирая старую.
	
	УстановитьОтключениеБезопасногоРежима(Истина);
	ДобавитьФайл(КонтейнерСвойствФайла, Контекст.ДанныеФайла);
	УстановитьОтключениеБезопасногоРежима(Ложь);
	
	Контекст.ИзменяемыеРеквизиты.ПутьКФайлу = КонтейнерСвойствФайла.ПутьКФайлу;
	Контекст.ИзменяемыеРеквизиты.Том = КонтейнерСвойствФайла.Том;
КонецПроцедуры

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

// Вызывается в транзакции модификации.
// Параметры:
//  Контекст - см. РаботаСФайламиСлужебный.КонтекстОбновленияФайла
//  ПрисоединенныйФайл - ОпределяемыйТип.ПрисоединенныйФайл
//
Процедура ПриОбновленииДанныхФайла(Контекст, ПрисоединенныйФайл) Экспорт
	Возврат; // Не используется
КонецПроцедуры

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

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

КонецПроцедуры

// Параметры:
//  ФайлыДляУдаления - Массив из Строка - полный путь к файлу.
// 
// Возвращаемое значение:
//  Структура:
//   * Всего - Число
//   * Удалено - Число
//   * ОшибкиУдаления - Массив из Структура:
//   * Ошибка - Строка - описание ошибки;
//   * Имя - Строка - имя файла.
// 
Функция УдалитьФайлыТомов(ФайлыДляУдаления) Экспорт
	Результат = Новый Структура;
	Результат.Вставить("Всего", ФайлыДляУдаления.Количество());
	Результат.Вставить("Удалено", 0);
	Результат.Вставить("ОшибкиУдаления", Новый Массив);
	
	Для Каждого Файл Из ФайлыДляУдаления Цикл
		РезультатУдаления = УдалитьФайл(Файл);
		Если РезультатУдаления.Успешно Тогда
			Результат.Удалено = Результат.Удалено + 1;
		Иначе
			Ошибка = Новый Структура;
			Ошибка.Вставить("Ошибка", РезультатУдаления.ИнформацияОбОшибке);
			Ошибка.Вставить("Имя", РезультатУдаления.Имя);
			Результат.ОшибкиУдаления.Добавить(Ошибка);
		КонецЕсли; 
	КонецЦикла;
	
	Возврат Результат;
КонецФункции

#Область НастройкиХраненияФайловВТомах

// Возвращает признак того, что файлы могут храниться и в томах и в информационной базе.
//
// Возвращаемое значение:
//   Булево
//
Функция ХранитьФайлыВТомахИИнформационнойБазе()
	
	УстановитьПривилегированныйРежим(Истина);
	Возврат Константы.СпособХраненияФайлов.Получить() = "ВИнформационнойБазеИТомахНаДиске";
	
КонецФункции

// Возвращает признак того, что файлы в томах хранятся в подкаталогах с именем владельца.
//
// Возвращаемое значение:
//   Булево
//
Функция СоздаватьПодкаталогиСИменамиВладельцев()
	
	УстановитьПривилегированныйРежим(Истина);
	Возврат Константы.СоздаватьПодкаталогиСИменамиВладельцев.Получить();
	
КонецФункции

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

#Область ОбменДанными

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

// Размещает файлы в томах, устанавливая ссылки в ВерсииФайла.
//
Процедура ДобавитьФайлыВТома(ПутьКАрхивуWindows, ПутьКАрхивуLinux) Экспорт
	
	ПолноеИмяФайлаZip = "";
	Если ОбщегоНазначения.ЭтоWindowsСервер() Тогда
		ПолноеИмяФайлаZip = ПутьКАрхивуWindows;
	Иначе
		ПолноеИмяФайлаZip = ПутьКАрхивуLinux;
	КонецЕсли;
	
	ИмяКаталога = ФайловаяСистема.СоздатьВременныйКаталог();
	СоздатьКаталог(ИмяКаталога);
	
	ZipФайл = Новый ЧтениеZipФайла(ПолноеИмяФайлаZip);
	ZipФайл.ИзвлечьВсе(ИмяКаталога, РежимВосстановленияПутейФайловZIP.НеВосстанавливать);
	
	СоответствиеПутейФайлов = Новый Соответствие;
	
	Для Каждого ZIPЭлемент Из ZipФайл.Элементы Цикл
		ПолныйПутьФайла = ИмяКаталога + "\" + ZIPЭлемент.Имя;
		// Формирование имя файла см. РаботаСФайламиСлужебный.ПриОтправкеФайлаСозданиеНачальногоОбраза
		СправочникУникальныйИдентификатор = ZIPЭлемент.Имя;
		
		СоответствиеПутейФайлов.Вставить(СправочникУникальныйИдентификатор, ПолныйПутьФайла);
	КонецЦикла;
	
	НачатьТранзакцию();
	Попытка
		ДобавитьФайлыВТомаПриРазмещении(СоответствиеПутейФайлов, РаботаСФайламиСлужебный.ТипХраненияФайлов());
		ЗафиксироватьТранзакцию();
	Исключение
		ОтменитьТранзакцию();
		ВызватьИсключение;
	КонецПопытки;
	
	ФайловаяСистема.УдалитьВременныйКаталог(ИмяКаталога);
	
КонецПроцедуры

// Добавляет файл в тома при выполнении команды "разместить файлы начального образа".
//
// Параметры:
//   СоответствиеПутейФайлов - Соответствие - соответствие УникальногоИдентификатора файла и пути к файлу.
//   ТипХраненияФайла        - ПеречислениеСсылка.ТипыХраненияФайлов - тип хранения файлов.
//
Процедура ДобавитьФайлыВТомаПриРазмещении(СоответствиеПутейФайлов, ТипХраненияФайла)
		
	Для каждого СведенияОФайлеПуть Из СоответствиеПутейФайлов Цикл
		
		ПолныйПутьНовый = "";
		СведенияОФайле = СведенияОФайлеИзИмени(СведенияОФайлеПуть.Ключ);
		Если НЕ СведенияОФайле.Полные Тогда
			Продолжить;
		КонецЕсли;
		
		СсылкаНаФайл = Справочники[СведенияОФайле.ИмяСправочника].ПолучитьСсылку(СведенияОФайле.УникальныйИдентификатор);
		Если СсылкаНаФайл.Пустая() Тогда
			Продолжить;
		КонецЕсли;
		
		ПолныйПутьФайлаВТоме = СведенияОФайлеПуть.Значение;
		
		НачатьТранзакцию();
		Попытка
			
			БлокировкаДанных = Новый БлокировкаДанных;
			ЭлементБлокировкиДанных = БлокировкаДанных.Добавить("Справочник." + СведенияОФайле.ИмяСправочника);
			ЭлементБлокировкиДанных.УстановитьЗначение("Ссылка", СсылкаНаФайл);
			БлокировкаДанных.Заблокировать();
			
			Объект = СсылкаНаФайл.ПолучитьОбъект(); // ОпределяемыйТип.ПрисоединенныйФайлОбъект
			Объект.ТипХраненияФайла = РаботаСФайламиСлужебный.ТипХраненияФайла(Объект.Размер, Объект.Расширение);
			
			Если ТипХраненияФайла = Перечисления.ТипыХраненияФайлов.ВИнформационнойБазе Тогда
				
				// В базе-приемнике файлы должны храниться в информационной базе - значит там их и разместим (даже если в исходной
				// базе они были в томах).
				
				Объект.Том = Справочники.ТомаХраненияФайлов.ПустаяСсылка();
				Объект.ПутьКФайлу = "";
				Объект.ТипХраненияФайла = Перечисления.ТипыХраненияФайлов.ВИнформационнойБазе;
				
				ДвоичныеДанные = Новый ДвоичныеДанные(ПолныйПутьФайлаВТоме);
				РаботаСФайламиСлужебный.ЗаписатьФайлВИнформационнуюБазу(Объект.Ссылка, ДвоичныеДанные);
				
			Иначе
				
				// В базе-приемнике файлы должны храниться в томах - переместим разархивированный файл на том.
				ФайлИсходный = Новый Файл(ПолныйПутьФайлаВТоме);
				ИмяФайла = ОбщегоНазначенияКлиентСервер.ПолучитьИмяСРасширением(Объект.Наименование, Объект.Расширение);
				ОбщегоНазначения.СократитьИмяФайла(ИмяФайла);
				ПолныйПутьНовый = ФайлИсходный.Путь + ИмяФайла;
				ПереместитьФайл(ПолныйПутьФайлаВТоме, ПолныйПутьНовый);
				
				ДобавитьФайл(Объект, ПолныйПутьНовый);
				
			КонецЕсли;
			
			Объект.ДополнительныеСвойства.Вставить("РазмещениеФайловВТомах", Истина); // Чтобы прошла запись подписанных файлов.
			ОбновлениеИнформационнойБазы.ЗаписатьОбъект(Объект);
			
			ЗафиксироватьТранзакцию();
			
		Исключение
			ОтменитьТранзакцию();
		КонецПопытки;
		
		Если Не ПустаяСтрока(ПолныйПутьНовый) Тогда
			УдалитьФайлы(ПолныйПутьНовый);
		КонецЕсли;

	КонецЦикла;
	
КонецПроцедуры

Функция СведенияОФайлеИзИмени(ИмяФайла)
	СведенияОФайле = Новый Структура;
	СведенияОФайле.Вставить("ИмяСправочника", "");
	СведенияОФайле.Вставить("УникальныйИдентификатор", "");
	СведенияОФайле.Вставить("Полные", Ложь);
	
	ЧастиИмени = СтрРазделить(ИмяФайла, ".");
	Если ЧастиИмени.Количество() = 2 Тогда
		ИмяСправочника = ЧастиИмени[0];
		СведенияОФайле.ИмяСправочника = ?(Метаданные.Справочники.Найти(ИмяСправочника) = Неопределено, "", ИмяСправочника);
		СведенияОФайле.УникальныйИдентификатор = Новый УникальныйИдентификатор(ЧастиИмени[1]);
		СведенияОФайле.Полные = ЗначениеЗаполнено(СведенияОФайле.ИмяСправочника) И ЗначениеЗаполнено(СведенияОФайле.УникальныйИдентификатор);
	КонецЕсли;
		
	Возврат СведенияОФайле;
КонецФункции

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

#Область ОчисткаНенужныхФайлов

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

	Результат.Индексы.Добавить("ПолноеИмя");
	Результат.Индексы.Добавить("СтатусПроверки");
	
	Возврат Результат;
КонецФункции

// Параметры:
//   ТаблицаФайловВТоме - см. РаботаСФайламиВТомахСлужебный.ЛишниеФайлыВТоме
//   Том                  - СправочникСсылка.ТомаХраненияФайлов - ссылка на том.
//
Процедура ЗаполнитьЛишниеФайлы(ТаблицаФайловВТоме, Том) Экспорт
	
	ТипыФайлов = Метаданные.ОпределяемыеТипы.ПрисоединенныйФайл.Тип.Типы();
	
	Запрос = Новый Запрос;
	ПервыйТекстЗапроса = Истина;
	Для Каждого СправочникФайлов Из ТипыФайлов Цикл
		
		МетаданныеСправочника = Метаданные.НайтиПоТипу(СправочникФайлов);
		ЭтоСправочникВерсий = ОбщегоНазначения.ЕстьРеквизитОбъекта("РодительскаяВерсия", МетаданныеСправочника);
		
		ФрагментЗапроса = 
		"ВЫБРАТЬ
		|	СправочникПрисоединенныеФайлы.Ссылка,
		|	&ВладелецФайла КАК ВладелецФайла,
		|	СправочникПрисоединенныеФайлы.Расширение,
		|	СправочникПрисоединенныеФайлы.Наименование,
		|	СправочникПрисоединенныеФайлы.Том,
		|	&Отредактировал КАК Отредактировал,
		|	СправочникПрисоединенныеФайлы.ДатаМодификацииУниверсальная КАК ДатаМодификацииФайла,
		|	СправочникПрисоединенныеФайлы.ПутьКФайлу,
		|	СправочникПрисоединенныеФайлы.ПометкаУдаления
		|ИЗ
		|	&СправочникПрисоединенныеФайлы КАК СправочникПрисоединенныеФайлы
		|ГДЕ
		|	СправочникПрисоединенныеФайлы.Том = &Том
		|	И СправочникПрисоединенныеФайлы.ТипХраненияФайла = ЗНАЧЕНИЕ(Перечисление.ТипыХраненияФайлов.ВТомахНаДиске)
		|	И &УсловиеТекущаяВерсия";
		
		ФрагментЗапроса = СтрЗаменить(ФрагментЗапроса, "&ВладелецФайла", 
			"СправочникПрисоединенныеФайлы." + ?(ЭтоСправочникВерсий, "Владелец.ВладелецФайла", "ВладелецФайла"));
		ФрагментЗапроса = СтрЗаменить(ФрагментЗапроса, "&Отредактировал", 
			"СправочникПрисоединенныеФайлы." + ?(ЭтоСправочникВерсий, "Автор", "Изменил"));
		ФрагментЗапроса = СтрЗаменить(ФрагментЗапроса, "&СправочникПрисоединенныеФайлы", 
			"Справочник." + МетаданныеСправочника.Имя);
		
		Если Не ЭтоСправочникВерсий
			И ОбщегоНазначения.ЕстьРеквизитОбъекта("ТекущаяВерсия", МетаданныеСправочника) Тогда
			
			СправочникВерсийФайлов = Метаданные.НайтиПоТипу(
				МетаданныеСправочника.Реквизиты.ТекущаяВерсия.Тип.Типы()[0]);
			ФрагментЗапроса = СтрЗаменить(ФрагментЗапроса, "&УсловиеТекущаяВерсия",
				"СправочникПрисоединенныеФайлы.ТекущаяВерсия = ЗНАЧЕНИЕ(Справочник."
				+ СправочникВерсийФайлов.Имя + ".ПустаяСсылка)");
			
		Иначе
			ФрагментЗапроса = СтрЗаменить(ФрагментЗапроса, "&УсловиеТекущаяВерсия", "ИСТИНА");
		КонецЕсли;
		
		Запрос.Текст = Запрос.Текст + ?(ПервыйТекстЗапроса,"", "
			|ОБЪЕДИНИТЬ ВСЕ
			|") + ФрагментЗапроса;
		
		ПервыйТекстЗапроса = Ложь;
		
	КонецЦикла;
	
	Запрос.УстановитьПараметр("Том", Том);
	Выборка = Запрос.Выполнить().Выбрать();
	
	СвойстваФайла = СвойстваФайлаВТоме();
	СвойстваФайла.Том = Том; 
	
	ПолныйПутьТома = ПолныйПутьТома(Том);
	СвойстваФайловойСистемыТома =  СвойстваФайловойСистемы(ПолныйПутьТома);
	
	Пока Выборка.Следующий() Цикл
		
		ВерсияСсылка = Выборка.Ссылка;
		ПутьКФайлу   = Выборка.ПутьКФайлу;
		
		Если ЗначениеЗаполнено(Выборка.ПутьКФайлу)
			И ЗначениеЗаполнено(Выборка.Том) Тогда
			
			СвойстваФайла.ПутьКФайлу = ПутьКФайлу;
			
			ПолныйПутьФайла = ПолноеИмяФайлаВТоме(СвойстваФайла);
			СуществующийФайл = ТаблицаФайловВТоме.НайтиСтроки(Новый Структура("ПолноеИмя", ПолныйПутьФайла));
			
			ПоискПоАльтернативномуПути = Ложь;
			
			Если СуществующийФайл.Количество() = 0 И (Не СвойстваФайловойСистемыТома.ТочкаВКонцеИмениФайла
				ИЛИ Не СвойстваФайловойСистемыТома.ПробелВНачалеИмениФайла) Тогда
				
				Если Не СвойстваФайловойСистемыТома.ТочкаВКонцеИмениФайла И Прав(ПутьКФайлу, 1) = "." Тогда
					ПутьКФайлу = Лев(ПутьКФайлу, СтрДлина(ПутьКФайлу)-1);
				КонецЕсли;
				
				Если Не СвойстваФайловойСистемыТома.ПробелВНачалеИмениФайла 
					И СтрНайти(ПутьКФайлу, ПолучитьРазделительПути()+" ") > 0 Тогда
					ПараметрыФайла = Новый Файл(ПутьКФайлу);
					ПутьКФайлу = ПараметрыФайла.Путь+СокрЛП(ПараметрыФайла.Имя);
				КонецЕсли;
				
				Если Выборка.ПутьКФайлу <> ПутьКФайлу Тогда
					СвойстваФайла.ПутьКФайлу = ПутьКФайлу;
				
					ПолныйПутьФайла = ПолноеИмяФайлаВТоме(СвойстваФайла);
					СуществующийФайл = ТаблицаФайловВТоме.НайтиСтроки(Новый Структура("ПолноеИмя", ПолныйПутьФайла));
					ПоискПоАльтернативномуПути = Истина;
				КонецЕсли;
			КонецЕсли;
			
			Если СуществующийФайл.Количество() = 0 Тогда
				НесуществующийФайл = ТаблицаФайловВТоме.Добавить();
				НесуществующийФайл.Файл = ВерсияСсылка;
				НесуществующийФайл.ПолноеИмя = ПолныйПутьФайла;
				НесуществующийФайл.Расширение = Выборка.Расширение;
				НесуществующийФайл.Имя = Выборка.Наименование;
				НесуществующийФайл.Том = Том;
				НесуществующийФайл.Отредактировал = Выборка.Отредактировал;
				НесуществующийФайл.ДатаРедактирования = Выборка.ДатаМодификацииФайла;
				НесуществующийФайл.Количество = 1;
				НесуществующийФайл.СтатусПроверки = ?(Выборка.ПометкаУдаления, "ОК", "НетФайлаВТоме");
			ИначеЕсли ПоискПоАльтернативномуПути Тогда
				СуществующийФайл[0].Файл = ВерсияСсылка;
				СуществующийФайл[0].СтатусПроверки = "ВозможноИсправление";
				СуществующийФайл[0].ДатаРедактирования = Выборка.ДатаМодификацииФайла;
				СуществующийФайл[0].Количество = 1;
				СуществующийФайл[0].Отредактировал = Выборка.Отредактировал;
			Иначе
				СуществующийФайл[0].Файл = ВерсияСсылка;
				СуществующийФайл[0].СтатусПроверки = "ОК";
			КонецЕсли;
			
		КонецЕсли;
		
	КонецЦикла;

КонецПроцедуры

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

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

#Область КонтрольВеденияУчета

Функция ДоступныеТома(ПараметрыПроверки = Неопределено) Экспорт
	
	Запрос = Новый Запрос(
	"ВЫБРАТЬ
	|	ТомаХраненияФайлов.Ссылка КАК СсылкаНаТом,
	|	ТомаХраненияФайлов.Наименование КАК ПредставлениеТома,
	|	ВЫБОР
	|		КОГДА &ЭтоWindowsСервер
	|			ТОГДА ТомаХраненияФайлов.ПолныйПутьWindows
	|		ИНАЧЕ ТомаХраненияФайлов.ПолныйПутьLinux
	|	КОНЕЦ КАК ПолныйПуть
	|ИЗ
	|	Справочник.ТомаХраненияФайлов КАК ТомаХраненияФайлов");
	Запрос.УстановитьПараметр("ЭтоWindowsСервер", ОбщегоНазначения.ЭтоWindowsСервер());
	Результат = Запрос.Выполнить().Выбрать();
	
	ДоступныеТома = Новый Массив;
	Пока Результат.Следующий() Цикл
		
		Если ТомДоступен(Результат.СсылкаНаТом, Результат.ПредставлениеТома, Результат.ПолныйПуть, ПараметрыПроверки) Тогда
			ДоступныеТома.Добавить(Результат.СсылкаНаТом);
		КонецЕсли;
		
	КонецЦикла;
	
	Возврат ДоступныеТома;
	
КонецФункции

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

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

Процедура ПоискСсылокНаНесуществующиеФайлыВТомах(ОбъектМетаданных, ПараметрыПроверки, ДоступныеТома)
	
	МодульКонтрольВеденияУчета = ОбщегоНазначения.ОбщийМодуль("КонтрольВеденияУчета");
	
	ТекстЗапроса =
	"ВЫБРАТЬ ПЕРВЫЕ 1000
	|	ОбъектМетаданных.Ссылка КАК ПроблемныйОбъект,
	|	&ПолеВладельца КАК Владелец,
	|	ПРЕДСТАВЛЕНИЕССЫЛКИ(ОбъектМетаданных.Ссылка) КАК Файл,
	|	ПРЕДСТАВЛЕНИЕССЫЛКИ(ОбъектМетаданных.Том) КАК Том,
	|	ОбъектМетаданных.ПутьКФайлу КАК ПутьКФайлу,
	|	ОбъектМетаданных.Том КАК ТомСсылка,
	|	ОбъектМетаданных.ФайлХранилище КАК ФайлХранилище,
	|	&Автор КАК Автор
	|ИЗ
	|	&ОбъектМетаданных КАК ОбъектМетаданных
	|ГДЕ
	|	ОбъектМетаданных.Ссылка > &Ссылка
	|	И ОбъектМетаданных.ТипХраненияФайла = ЗНАЧЕНИЕ(Перечисление.ТипыХраненияФайлов.ВТомахНаДиске)
	|	И ОбъектМетаданных.Том В(&ДоступныеТома)
	|
	|УПОРЯДОЧИТЬ ПО
	|	ОбъектМетаданных.Ссылка";
	
	ПолноеИмя = ОбъектМетаданных.ПолноеИмя();
	ПолноеИмяВерсийФайлов = Метаданные.Справочники.ВерсииФайлов.ПолноеИмя();
	ТекстЗапроса = СтрЗаменить(ТекстЗапроса, "&ОбъектМетаданных", ПолноеИмя);
	ТекстЗапроса = СтрЗаменить(ТекстЗапроса, "&Автор", 
		?(ОбъектМетаданных.Реквизиты.Найти("Автор") <> Неопределено, "ОбъектМетаданных.Автор", "NULL"));
		
	ПолеВладельца = ?(ПолноеИмя = ПолноеИмяВерсийФайлов, "ПРЕДСТАВЛЕНИЕССЫЛКИ(ОбъектМетаданных.Владелец) ","Неопределено "); //  @query-part-1, @query-part-2
	ТекстЗапроса = СтрЗаменить(ТекстЗапроса, "&ПолеВладельца", ПолеВладельца);
	
	Запрос = Новый Запрос(ТекстЗапроса);
	Запрос.УстановитьПараметр("Ссылка", ОбщегоНазначения.МенеджерОбъектаПоПолномуИмени(ПолноеИмя).ПустаяСсылка());
	Запрос.УстановитьПараметр("ДоступныеТома", ДоступныеТома);
	Результат = Запрос.Выполнить().Выгрузить();
	Пока Результат.Количество() > 0 Цикл
		
		Для Каждого СтрокаРезультата Из Результат Цикл
			
			Если НЕ ЗначениеЗаполнено(СтрокаРезультата.ТомСсылка) Тогда
				Продолжить;
			КонецЕсли;
			
			Если Не ЗначениеЗаполнено(СтрокаРезультата.ПутьКФайлу) Тогда
				Если СтрокаРезультата.Владелец <> Неопределено Тогда
					УточнениеПроблемы = СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
						НСтр("ru = 'У версии ""%1"" файла ""%2"" не указан полный путь к файлу в томе ""%3"".'"),
						СтрокаРезультата.Файл, СтрокаРезультата.Владелец, СтрокаРезультата.Том);
				Иначе
					УточнениеПроблемы = СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
						НСтр("ru = 'У файла ""%1"" не указан полный путь к файлу в томе ""%2"".'"),
						СтрокаРезультата.Файл, СтрокаРезультата.Том);
				КонецЕсли;
				
				ЗарегистрироватьНесуществующийФайлВТоме(МодульКонтрольВеденияУчета, ПараметрыПроверки,
					СтрокаРезультата, УточнениеПроблемы);
			КонецЕсли;
			
			Если ТипЗнч(СтрокаРезультата.ФайлХранилище) = Тип("ХранилищеЗначения")
				И СтрокаРезультата.ФайлХранилище.Получить() <> Неопределено Тогда

				Если СтрокаРезультата.Владелец <> Неопределено Тогда
					УточнениеПроблемы = СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
						НСтр("ru = 'У версии ""%1"" файла ""%2"", хранящегося в томе ""%3"", избыточно хранятся двоичные данные в информационной базе.'"),
						СтрокаРезультата.Файл, СтрокаРезультата.Владелец, СтрокаРезультата.Том);
				Иначе
					УточнениеПроблемы = СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
						НСтр("ru = 'У файла ""%1"", хранящегося в томе ""%2"", избыточно хранятся двоичные данные в информационной базе.'"),
						СтрокаРезультата.Файл, СтрокаРезультата.Том);
				КонецЕсли;
				
				ЗарегистрироватьНесуществующийФайлВТоме(МодульКонтрольВеденияУчета, ПараметрыПроверки,
					СтрокаРезультата, УточнениеПроблемы);
			КонецЕсли;
			
			Если Не ЗначениеЗаполнено(СтрокаРезультата.ПутьКФайлу) Тогда
				Продолжить;
			КонецЕсли;
			
			ПолныйПутьТома = ПолныйПутьТома(СтрокаРезультата.ТомСсылка);
			ПутьКФайлу = ПолныйПутьТома + СтрокаРезультата.ПутьКФайлу;
			ПроверяемыйФайл = Новый Файл(ПутьКФайлу);
			Если ПроверяемыйФайл.Существует() Тогда
				Продолжить;
			КонецЕсли;
				
			Если СтрокаРезультата.Владелец <> Неопределено Тогда
				УточнениеПроблемы = СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
					НСтр("ru = 'Версия ""%1"" файла ""%2"" не существует в томе ""%3"" (%4).'"),
					СтрокаРезультата.Файл, СтрокаРезультата.Владелец, СтрокаРезультата.Том,
					ПутьКФайлу);
			Иначе
				УточнениеПроблемы = СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
					НСтр("ru = 'Файл ""%1"" не существует в томе ""%2"" (%3).'"),
					СтрокаРезультата.Файл, СтрокаРезультата.Том, ПутьКФайлу);
			КонецЕсли;
			
			ЗарегистрироватьНесуществующийФайлВТоме(МодульКонтрольВеденияУчета, ПараметрыПроверки,
				СтрокаРезультата, УточнениеПроблемы);
			
		КонецЦикла;
		
		Запрос.УстановитьПараметр("Ссылка", СтрокаРезультата.ПроблемныйОбъект);
		Результат = Запрос.Выполнить().Выгрузить(); // @skip-check query-in-loop - Порционная обработка данных
		
	КонецЦикла;
	
КонецПроцедуры

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

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

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

#Область ОбновлениеИнформационнойБазы

// См. ОбновлениеИнформационнойБазыБСП.ПриДобавленииОбработчиковОбновления.
Процедура ПриДобавленииОбработчиковОбновления(Обработчики) Экспорт
	
	Обработчик = Обработчики.Добавить();
	Обработчик.НачальноеЗаполнение = Истина;
	Обработчик.ОбщиеДанные = Истина;
	Обработчик.Процедура = "РаботаСФайламиВТомахСлужебный.УстановитьСпособФормированияПутиТома";
	Обработчик.РежимВыполнения = "Оперативно";
	
	Обработчик = Обработчики.Добавить();
	Обработчик.Версия = "2.4.1.1";
	Обработчик.ОбщиеДанные = Истина;
	Обработчик.Процедура = "РаботаСФайламиВТомахСлужебный.ОбновитьПутьТомаLinux";
	Обработчик.РежимВыполнения = "Оперативно";
	
	Обработчик = Обработчики.Добавить();
	Обработчик.Версия = "3.1.2.65";
	Обработчик.ОбщиеДанные = Истина;
	Обработчик.Процедура = "РаботаСФайламиВТомахСлужебный.ЗаполнитьНастройкиХраненияФайлов";
	Обработчик.РежимВыполнения = "Оперативно";
	Обработчик.НачальноеЗаполнение = Истина;
	
	Обработчик = Обработчики.Добавить();
	Обработчик.Версия = "3.1.11.80";
	Обработчик.Процедура = "Справочники.ВерсииФайлов.ОбработатьДанныеДляПереходаНаНовуюВерсию";
	Обработчик.РежимВыполнения = "Отложенно";
	Обработчик.Комментарий = НСтр("ru = 'Исправляет ошибочные пути хранения файлов в томах, а также удаление избыточные двоичных данных в версиях файлов.'");
	Обработчик.Идентификатор = Новый УникальныйИдентификатор("06354049-b702-4f27-8e99-f49b86f7f152");
	Обработчик.ПроцедураПроверки = "ОбновлениеИнформационнойБазы.ДанныеОбновленыНаНовуюВерсиюПрограммы";
	Обработчик.БлокируемыеОбъекты = "Справочник.ВерсииФайлов";
	Обработчик.ПроцедураЗаполненияДанныхОбновления = "Справочники.ВерсииФайлов.ЗарегистрироватьДанныеКОбработкеДляПереходаНаНовуюВерсию";
	Обработчик.ЧитаемыеОбъекты = "Справочник.ВерсииФайлов";
	Обработчик.ИзменяемыеОбъекты = "Справочник.ВерсииФайлов";
	Обработчик.Многопоточный = Истина;
	
КонецПроцедуры

// Устанавливает значение константы СпособХраненияФайлов в зависимости от значения
// константы ХранитьФайлыВТомахНаДиске и инициализирует константу ПараметрыХраненияФайловВИБ.
//
Процедура ЗаполнитьНастройкиХраненияФайлов() Экспорт
	
	Константы.СпособХраненияФайлов.Установить(
		?(Константы.ХранитьФайлыВТомахНаДиске.Получить(),
		"ВТомахНаДиске", "ВИнформационнойБазе"));
		
	УстановитьПараметрыХраненияФайловВИнформационнойБазе(
		Новый Структура("РасширенияФайлов, МаксимальныйРазмер", "", 0));
	
КонецПроцедуры

Процедура ОбновитьПутьТомаLinux() Экспорт
	
	УстановитьПривилегированныйРежим(Истина);
	
	Запрос = Новый Запрос;
	Запрос.Текст = 
		"ВЫБРАТЬ
		|	ТомаХраненияФайлов.Ссылка
		|ИЗ
		|	Справочник.ТомаХраненияФайлов КАК ТомаХраненияФайлов
		|ГДЕ
		|	ТомаХраненияФайлов.ПолныйПутьLinux ПОДОБНО ""%/\"" СПЕЦСИМВОЛ ""~""";
	
	Выборка = Запрос.Выполнить().Выбрать();
	Пока Выборка.Следующий() Цикл
		
		НачатьТранзакцию();
		Попытка
			
			БлокировкаДанных = Новый БлокировкаДанных;
			ЭлементБлокировкиДанных = БлокировкаДанных.Добавить("Справочник.ТомаХраненияФайлов");
			ЭлементБлокировкиДанных.УстановитьЗначение("Ссылка", Выборка.Ссылка);
			БлокировкаДанных.Заблокировать();
			
			Том = Выборка.Ссылка.ПолучитьОбъект(); // СправочникОбъект.ТомаХраненияФайлов
			Том.ПолныйПутьLinux = СтрЗаменить(Том.ПолныйПутьLinux , "/\", "/");
			Том.Записать();
			
			ЗафиксироватьТранзакцию();
			
		Исключение
			
			ОтменитьТранзакцию();
			
			ТекстСообщения = СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
				НСтр("ru = 'Не удалось обработать том хранения файлов %1 по причине:
				|%2'"), 
				Выборка.Ссылка, ОбработкаОшибок.ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
			
			ЗаписьЖурналаРегистрации(ОбновлениеИнформационнойБазы.СобытиеЖурналаРегистрации(), УровеньЖурналаРегистрации.Предупреждение,
				Выборка.Ссылка.Метаданные(), Выборка.Ссылка, ТекстСообщения);
			
		КонецПопытки;
		
	КонецЦикла;
	
КонецПроцедуры

// Для функции см. ПолныйПутьТома
//
Процедура УстановитьСпособФормированияПутиТома() Экспорт
	Константы.ПутьКТомуБезУчетаРегиональныхНастроек.Установить(Истина);
КонецПроцедуры

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

#Область ВспомогательныеПроцедурыИФункции

// Возвращает имя подкаталога в томе по типу владельца файла.
// Имя подкаталога формируется как конкатенация 30 первых символов имени
// объекта метаданных владельца файла + хеш CRC32 от остатка.
//
// Параметры:
//   ВладелецФайла - ОпределяемыйТип.ВладелецПрисоединенныхФайлов
//                 - ОпределяемыйТип.ВладелецФайлов - ссылка на
//                 владельца файла, от метаданных которого необходимо образовать имя подкаталога.
//
// Возвращаемое значение:
//   Строка - имя каталога.
//
Функция ИмяКаталогаВладельцаФайла(ВладелецФайла)
	
	Если Не ЗначениеЗаполнено(ВладелецФайла) Тогда
		Возврат "";
	КонецЕсли;
	
	ИмяОбъектаМетаданных = ВладелецФайла.Метаданные().Имя;
	Если СтрДлина(ИмяОбъектаМетаданных) > 30 Тогда
		ХешОстатка = Новый ХешированиеДанных(ХешФункция.CRC32);
		ХешОстатка.Добавить(Сред(ИмяОбъектаМетаданных, 31));
		ХешСуммаОстатка = ХешОстатка.ХешСумма;
	Иначе
		ХешСуммаОстатка = "";
	КонецЕсли;
	
	Возврат Лев(ИмяОбъектаМетаданных, 30) + ХешСуммаОстатка;
	
КонецФункции

// Возвращает тип хранения добавляемого файла в зависимости от его расширения и размера.
// 
// Параметры:
//   РазмерФайла - Число - размер добавляемого файла в байтах.
//   РасширениеФайла - Строка - расширение добавляемого файла.
//
// Возвращаемое значение:
//   ПеречислениеСсылка.ТипыХраненияФайлов - если способ хранения файлов в настройках
//      указан ВТомахНаДиске, возвращает значение ВТомахНаДиске. Если способ хранения
//      файлов указан ВИнформационнойБазеИТомахНаДиске, то возвращает ВИнформационнойБазе, если
//      файл соответствует параметрам хранения в информационной базе, иначе ВТомахНаДиске.
//
Функция ТипХраненияФайла(Знач РазмерФайла, Знач РасширениеФайла) Экспорт
	
	ТипХранения = Перечисления.ТипыХраненияФайлов.ВТомахНаДиске;
	Если ХранитьФайлыВТомахИИнформационнойБазе() Тогда
		
		ПараметрыХранения = ПараметрыХраненияФайловВИнформационнойБазе();
		Если РазмерФайла <= ПараметрыХранения.МаксимальныйРазмер Тогда
			ТипХранения = Перечисления.ТипыХраненияФайлов.ВИнформационнойБазе;
		Иначе
			РасширениеФайла = НРег(СокрЛП(РасширениеФайла));
			Если СтрНачинаетсяС(РасширениеФайла, ".") Тогда
				РасширениеФайла = Сред(РасширениеФайла, 2, СтрДлина(РасширениеФайла) - 1);
			КонецЕсли;
			Если СтрНайти(НРег(ПараметрыХранения.РасширенияФайлов), РасширениеФайла) > 0 Тогда
				ТипХранения = Перечисления.ТипыХраненияФайлов.ВИнформационнойБазе;
			КонецЕсли;
		КонецЕсли;
		
	КонецЕсли;
	
	Возврат ТипХранения;
	
КонецФункции

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

		Если ОбъемыТомов = Неопределено Тогда
			// @skip-check query-in-loop - Разовый вызов
			ОбъемыТомов = ОбъемыТомов(ТомаХраненияФайлов.ВыгрузитьКолонку("Ссылка"));
		КонецЕсли;
		ОбъемТома = ОбъемыТомов[ТомХраненияФайлов.Ссылка];
		Если ОбъемТома + ПрисоединенныйФайл.Размер <= ТомХраненияФайлов.МаксимальныйРазмер * 1024 * 1024 Тогда
			Возврат ТомХраненияФайлов.Ссылка;
		КонецЕсли;
		
	КонецЦикла;
	
	ТекстОшибки = СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
	НСтр("ru = 'Не удалось добавить файл ""%1"", 
		|т.к. в томах хранения файлов недостаточно места.
		|Обратитесь к администратору.'"),
		ПрисоединенныйФайл.Наименование + "." + ПрисоединенныйФайл.Расширение);
	ВызватьИсключение ТекстОшибки;	
	
КонецФункции

Функция СвойстваФайловойСистемы(Каталог)
	УстановитьОтключениеБезопасногоРежима(Истина);
	УстановитьПривилегированныйРежим(Истина);
	
	ИмяТестовогоКаталога = Каталог + "ПроверкаДоступа"+Формат(ТекущаяДатаСеанса(), "ДФ=ddMMyy_HHmmss;");
	ИмяТестовогоФайла = ИмяТестовогоКаталога + ПолучитьРазделительПути() + " FileNameCheck.";
	СоздатьКаталог(ИмяТестовогоКаталога);
	
	ТекстовыйФайл = Новый ТекстовыйДокумент;
	ТекстовыйФайл.Записать(ИмяТестовогоФайла);
	
	Результат = Новый Структура;
	Результат.Вставить("ПробелВНачалеИмениФайла", Ложь);
	Результат.Вставить("ТочкаВКонцеИмениФайла", Ложь);
	НайденныеФайлы = НайтиФайлы(ИмяТестовогоКаталога,"*FileNameCheck*", Истина);
	Для Каждого НайденныйФайл Из НайденныеФайлы Цикл
		Если СтрЗаканчиваетсяНа(НайденныйФайл.ПолноеИмя, " FileNameCheck.") Тогда
			Результат.ПробелВНачалеИмениФайла = Истина;
			Результат.ТочкаВКонцеИмениФайла = Истина;
			Прервать;
		ИначеЕсли СтрЗаканчиваетсяНа(НайденныйФайл.ПолноеИмя, "FileNameCheck.") Тогда
			Результат.ПробелВНачалеИмениФайла = Ложь;
			Результат.ТочкаВКонцеИмениФайла = Истина;
			Прервать;
		ИначеЕсли СтрЗаканчиваетсяНа(НайденныйФайл.ПолноеИмя, " FileNameCheck") Тогда
			Результат.ПробелВНачалеИмениФайла = Истина;
			Результат.ТочкаВКонцеИмениФайла = Ложь;
			Прервать;
		КонецЕсли;
	КонецЦикла;
	УдалитьФайлы(ИмяТестовогоКаталога);
	Возврат Результат;
КонецФункции

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

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