# Файловая корзина

Файловая корзина (корзина удаленных файлов, *recycle bin*) хранит удаленные файлы в течение заданного срока вместо немедленного уничтожения. Механизм сохраняет сведения об удалении и, пока содержимое файла остается в хранилище, позволяет пользователю с необходимыми правами найти файл, посмотреть дату удаления и удалившего пользователя, а также выгрузить его содержимое.

Корзина включается отдельно для каждого файлового хранилища с помощью параметра **Срок хранения удаленных файлов (дней)** на карточке хранилища. После истечения срока хранения файл удаляется окончательно при ближайшем запуске регламентного задания.

## Ключевые возможности

- хранение удаленных файлов в течение срока, заданного для файлового хранилища;
- сохранение сведений о каждом удалении: даты, удалившего пользователя и объекта-источника, к которому прикреплен файл;
- выгрузка содержимого удаленного файла;
- окончательное удаление файла;
- автоматическая очистка корзины по истечении срока хранения;
- передача в S3 команды на удаление объекта при помещении файла в корзину.

## Работа механизма

**Условия работы**

Корзина выключена по умолчанию. Чтобы включить ее для файлового хранилища, задайте параметр **Срок хранения удаленных файлов (дней)** со значением больше нуля.

Для автоматической очистки корзины в менеджере заданий должно быть активно регламентное задание **Плановая чистка файловой корзины**. Задание регистрируется автоматически во включенном состоянии и запускается ежедневно в 01:00. Дополнительная настройка задания не требуется.

Для работы с журналом и выполнения операций пользователю необходимы соответствующие права доступа.

**Порядок работы**

После удаления файл помещается в корзину или удаляется окончательно.

1. Пользователь удаляет файл — вложение или версию вложения. Раздел системы, из которого выполнено удаление, на результат не влияет: корзина работает для всех стандартных сценариев удаления файла одинаково. Исключения перечислены в разделе [Особенности и ограничения](#особенности-и-ограничения).
2. Система читает настройки файлового хранилища, в котором находится файл. Если срок хранения не задан или не является положительным, запись о файле и его содержимое удаляются сразу. Если задан положительный срок, система помечает файл как удаленный, а содержимое оставляет в хранилище.
3. Вместе с признаком удаления система фиксирует дату удаления, имя удалившего пользователя, объект-источник и его наименование. Объект-источник — объект системы, к которому прикреплен файл. Эти сведения записываются при первом удалении и далее не меняются. Если объект-источник у файла не задан, система фиксирует только дату удаления и удалившего пользователя.
4. Удаленный файл исчезает из обычных списков и карточек файлов и отображается в разделе **Журнал удаленных файлов**. Путь: `Приложение «Настройка системы» > Аудит > Журнал удаленных файлов`.
5. В журнале пользователь с необходимыми правами может найти удаленный файл, выгрузить его содержимое или удалить файл окончательно. При окончательном удалении система удаляет запись о файле и его содержимое из хранилища.
6. Регламентное задание ежедневно в 01:00 окончательно удаляет файлы с истекшим сроком хранения. Задание обходит хранилища с положительным сроком хранения и обрабатывает файлы каждого хранилища порциями по 1000 записей.

Для безвозвратного удаления файла из корзины используйте в журнале операцию **Удалить окончательно**.

```{attention}
Место в файловом хранилище освобождается после истечения срока хранения или окончательного удаления. Исключение для S3 описано в разделе **Синхронизация с хранилищем S3**. Это следует учитывать при выборе срока хранения для хранилищ с большим объемом данных.
```

## Работа в интерфейсе системы

**Настройка файлового хранилища**

Параметры файловой корзины задаются на закладке **Дополнительные настройки** карточки файлового хранилища.

Путь: `Приложение «Настройка системы» > Сущности > Файловое хранилище`.

| Настройка | Тип хранилища | Назначение |
|---|---|---|
| **Срок хранения удаленных файлов (дней)** | все типы | Число дней, в течение которых удаленный файл остается в корзине. Пустое, нулевое или отрицательное значение отключает корзину |
| **Передавать признак удаления в хранилище S3** | только S3 | Передает в S3 команду на удаление объекта при помещении файла в корзину |

Общесистемной настройки срока хранения нет: значение задается у каждого файлового хранилища отдельно. Это позволяет задать длительный срок для хранилища документов и нулевой — для хранилища временных файлов.

**Работа с удаленными файлами**

Работа с удаленными файлами выполняется в разделе **Журнал удаленных файлов**.

Путь: `Аудит > Журнал удаленных файлов`.

Журнал отображает только файлы с признаком удаления и заполненной датой удаления. Последние удаленные файлы отображаются первыми.

![storage](/030_class/service/img/file_storage.png)

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

На панели фильтров можно задать файловое хранилище, имя файла, владельца, удалившего пользователя и период удаления. Условия панели можно дополнить с помощью универсального фильтра.

| Операция | Доступность | Результат |
|---|---|---|
| **Выгрузить файл** | для активной строки удаленного файла | Скачивает содержимое файла из хранилища. Если содержимого в хранилище нет, система сообщает имя файла и файлового хранилища |
| **Удалить окончательно** | для активной строки удаленного файла | После подтверждения безвозвратно удаляет содержимое файла из хранилища и запись `Btk_File` |

## Устройство файловой корзины

При включенной файловой корзине операция `delete` объекта `Btk_File` выполняет логическое удаление: устанавливает признак `bDeleted = 1`, фиксирует дату удаления и сведения об удалившем пользователе. Запись `Btk_File` сохраняется до окончательного удаления.

Механизм реализован в модуле `btk` и включается настройкой файлового хранилища. Прикладные модули используют механизм через стандартную операцию `delete`.

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

### Архитектура механизма

Механизм построен на следующих принципах:

1. **Разделение логического и физического удаления.** Операция `delete` делегирует выполнение в `deleteUsingRecycleBin`, а физическое удаление выполняет отдельная операция `purge`.
2. **Централизация политики удаления.** Прикладной код вызывает стандартную операцию `delete` и не определяет способ удаления. По этой причине `Btk_AttachVerApi` удаляет основной файл версии вложения вызовом `Btk_FileApi().delete(...)`, а не прямым вызовом `deleteUsingRecycleBin`.
3. **Управление поведением через настройки хранилища.** Срок хранения читается из `jSettings` объекта `Btk_FileStorage` в момент удаления, поэтому администратор включает корзину отдельно для каждого хранилища.
4. **Хранение метаданных рядом с файлом.** Дата удаления, удаливший пользователь и объект-источник записываются в атрибуты `dDelete` и `jDeleteData` записи `Btk_File`.

**Дерево компонентов**

<!-- Начало кода -->
```text
Btk_FileApi (политика удаления файла)
├── delete(rop)                  — точка входа прикладного кода, делегирует в deleteUsingRecycleBin
├── deleteUsingRecycleBin(rop, gidpSrc, bScheduleBeforeFlush) — маршрутизация: корзина или purge
├── purge(rop)                   — окончательное удаление: физический файл и запись Btk_File
├── purgeBeforeFlush(rop)        — отложенное окончательное удаление на beforeFlush
└── processRecycleBin()          — регламентная очистка корзины по истечении срока хранения
Btk_FileStorageApi (настройки хранилища)
├── getRecycleBinSettings(idpFileStorage) / getRecycleBinSettings(rop)
└── RecycleBinSettings(nRetentionPeriod, bS3Sync)
ru.bitec.app.btk.file.DeletedFileData(sUser, gidSrcObj, sSrcObjHeadline) (метаданные удаления)
├── forSource(gidpSrc)           — сбор данных удаления
├── forFile(idpFile)             — чтение данных из jDeleteData
└── applyToFile(rop)             — запись dDelete и ключей jDeleteData
```
<!-- Конец кода -->

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

### Данные и настройки файловой корзины

**Состояние файла**

Состояние файла в корзине описывают три атрибута класса `Btk_File`.

| Атрибут | Тип ODM | Заголовок | Назначение |
|---|---|---|---|
| `bDeleted` | Number | Пометка удаления | признак нахождения файла в корзине |
| `dDelete` | Date | Дата удаления | момент помещения файла в корзину |
| `jDeleteData` | Jsonb | Информация об удаленном файле | сведения об удалении: пользователь и объект-источник |

Значение `bDeleted = 1` означает, что файл находится в корзине. Сеттер `setbDeleted` не выполняет действий с файловым хранилищем.

**Метаданные удаления**

| Ключ | Тип значения | Содержание |
|---|---|---|
| `sUser` | строка | имя удалившего пользователя, `Btk_UserApi().getCurrentUserName` |
| `gidSrcObj` | gid | объект-источник, к которому прикреплен файл |
| `sSrcObjHeadline` | строка | заголовок объекта-источника, `Btk_Pkg().getHeadLineByGid` |

Значения `gidSrcObj` и `sSrcObjHeadline` остаются пустыми, если файл удаляют без объекта-источника.

**Настройки файлового хранилища**

Настройки корзины хранятся в ключах jsonb-атрибута `jSettings` объекта `Btk_FileStorage` и отображаются на закладке **Дополнительные настройки** карточки файлового хранилища.

| Ключ | Заголовок в интерфейсе | Тип | Область применения |
|---|---|---|---|
| `nRecycleBinRetentionDays` | Срок хранения удаленных файлов (дней) | число | все типы файловых хранилищ |
| `bNeedS3RecycleBinSync` | Передавать признак удаления в хранилище S3 | логический | только хранилища типа S3 |

Пустое, нулевое или отрицательное значение `nRecycleBinRetentionDays` отключает корзину: стандартная операция `delete` выполняет окончательное удаление вместо помещения файла в корзину.

**Чтение настроек в коде**

`Btk_FileStorageApi` предоставляет две перегрузки `getRecycleBinSettings` — по идентификатору хранилища и по объекту `ApiRop`. Обе возвращают case-класс `RecycleBinSettings(nRetentionPeriod: NPeriod, bS3Sync: Boolean)`.

Пример получения настроек для объекта файлового хранилища:

<!-- Начало кода -->
```scala
val recycleBinSettings =
  Btk_FileStorageApi().getRecycleBinSettings(rvFileStorage)
```
<!-- Конец кода -->

Если ключ `bNeedS3RecycleBinSync` отсутствует, `bS3Sync` принимает значение `false`.

### Жизненный цикл файла

**Состояния записи `Btk_File`**

| Состояние | Признаки | Как достигается | Видимость в формах |
|---|---|---|---|
| Активный | `bDeleted` пусто или `0`, `dDelete` пусто | создание файла | форма «Файлы» и форма администрирования файловых хранилищ |
| В корзине | `bDeleted = 1`, заполнены `dDelete` и `jDeleteData` | `delete` при заданном сроке хранения | журнал удаленных файлов |
| Удален окончательно | запись `Btk_File` отсутствует | `purge`, `purgeBeforeFlush`, регламент `Btk_JobProcessRecycleBin` | нигде |

Обратный переход из состояния «В корзине» в состояние «Активный» не предусмотрен.

### Последовательность обработки удаления

**Помещение файла в корзину**

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

Пример стандартного удаления:

<!-- Начало кода -->
```scala
val ropFile = Btk_FileApi().load(idpFile)
Btk_FileApi().delete(ropFile)
```
<!-- Конец кода -->

Далее механизм выполняет следующие действия:

1. Прикладной код вызывает `Btk_FileApi().delete(rop)`.
2. `delete` делегирует выполнение в `deleteUsingRecycleBin(rop)` с параметрами по умолчанию.
3. Метод проверяет признак `bDeleted`. Значение, отличное от `1`, означает, что файл еще не находится в корзине.
4. `Btk_FileStorageApi().load(...)` загружает хранилище файла. При пустом `idFileStorage` используется хранилище по умолчанию `idDefault`.
5. `getRecycleBinSettings(rvFileStorage)` разбирает `jSettings` и возвращает `RecycleBinSettings`.
6. Поскольку срок хранения положительный, метод вычисляет GID источника `gidpSrc.nvl(rop.get(_.gidSrc))`.
7. `DeletedFileData.forSource(gidvSrc).applyToFile(rop)` записывает `dDelete` и ключи `jDeleteData`.
8. `setbDeleted(rop, 1.nr)` помечает файл как находящийся в корзине.
9. Запись `Btk_File` остается в системе до окончательного удаления.

**Окончательное удаление при выключенной корзине**

Шаги 1–5 совпадают с помещением в корзину, но срок хранения не задан, равен нулю или отрицательный. Далее метод выбирает режим по параметру `bScheduleBeforeFlush`:

- при `true` вызывается `purgeBeforeFlush(rop)` — вызов окончательного удаления откладывается до `beforeFlush`;
- при `false` вызывается `purge(rop)` — окончательное удаление записи начинается в текущем вызове.

**Окончательное удаление из журнала удаленных файлов**

Операция **Удалить окончательно** доступна для активной строки журнала. Система загружает запись `Btk_File` по идентификатору и запрашивает подтверждение. После подтверждения вызывается операция `purge`.

Операция безвозвратно удаляет физический файл из хранилища и запись `Btk_File`, после чего список журнала обновляется.

**Регламентная очистка корзины**

1. По расписанию задание выполняет `Btk_FileApi.processRecycleBin()`.
2. Система получает файловые хранилища с положительным сроком хранения удаленных файлов.
3. Для каждого хранилища система вычисляет предельную дату удаления: из текущей даты вычитает настроенный срок хранения.
4. Отдельный запрос выбирает удаленные файлы текущего хранилища, дата удаления которых меньше предельной.
5. Файлы обрабатываются порциями по 1000 записей. Для каждого файла вызывается `Btk_FileApi().purge(...)`, после каждой порции выполняется `session.flush()`.
6. После завершения обработки файлового хранилища выполняется `session.commit()`.

Срок хранения вычисляется приложением при обработке хранилища, поэтому изменение настройки действует и на файлы, помещенные в корзину ранее. Разделение обхода хранилищ и выбора файлов исключает соединение `Btk_File` с `Btk_FileStorage` и разбор `jSettings` в запросе по таблице файлов.

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

**Синхронизация с хранилищем S3**

Синхронизация с S3 выполняется при одновременном выполнении двух условий:

- настройка `bNeedS3RecycleBinSync` активна;
- тип хранилища равен `Btk_FileStorageTypeApi().idS3`.

Файл помещается в корзину в стандартном порядке: запись сохраняет `bDeleted = 1`, `dDelete` и `jDeleteData`.

Дополнительно на `beforeFlush` планируется вызов `deleteFile(svFullFileName)` по системному имени хранилища. Команда на удаление передается в S3, а метаданные удаления остаются в системе.

При включенной синхронизации содержимое файла может стать недоступно сразу после помещения записи в корзину. Срок хранения удаленных объектов в S3 настраивается отдельно и может быть меньше срока хранения в Global ERP. В этом случае содержимое удаляется из S3 раньше записи `Btk_File`.

### Транзакции и обработка ошибок

Физическое удаление файла в `purge`, `purgeBeforeFlush` и при синхронизации с S3 выполняется через `session.scheduleBeforeFlush`.

Регламентное задание обрабатывает каждое файловое хранилище в отдельной транзакции. Файлы выбираются порциями по 1000 записей. После обработки порции система вызывает `session.flush()`, а после завершения всего хранилища — `session.commit()`.

При вызове `session.flush()` сначала выполняются запланированные действия `beforeFlush`. Поэтому физические файлы начинают удаляться до фиксации удаления записей `Btk_File` в конце обработки хранилища.

Исключение `AppException` при отдельном вызове `purge` записывается в журнал, увеличивает счетчик ошибок и не останавливает обработку остальных файлов порции. Если `AppException` возникает за пределами обработки отдельного файла, транзакция хранилища отменяется, после чего задание переходит к следующему хранилищу.

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

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

## Особенности и ограничения

При работе с файловой корзиной учитывайте следующие особенности и ограничения:

- корзина выключена по умолчанию и начинает использоваться только после задания срока хранения для файлового хранилища;
- восстановление файла из корзины не предусмотрено — пользователю с необходимыми правами доступны просмотр сведений об удалении, выгрузка содержимого и окончательное удаление;
- окончательное удаление необратимо и выполняется с помощью операции **Удалить окончательно**, регламентного задания или повторного удаления файла, уже находящегося в корзине;
- удаленные файлы доступны в разделе **Журнал удаленных файлов** и не отображаются в обычных списках и карточках файлов;
- файлы удаляются не в момент истечения срока хранения, а при ближайшем запуске регламентного задания;
- срок хранения отсчитывается от даты удаления файла, а не от даты его создания или изменения;
- при очистке хранилища по настройкам `Btk_FileStorageCleanSetting` выполняется `purgeBeforeFlush`: запись `Btk_File` и физическое содержимое файла удаляются окончательно, минуя корзину;
- служебные и производные файлы могут удаляться окончательно с помощью `purge` или `purgeBeforeFlush`, минуя корзину;
- срок хранения задается только на уровне файлового хранилища — отдельные политики по типам файлов или объектам-источникам не предусмотрены;
- операции с базой данных и физическим хранилищем не образуют единую транзакцию: при ошибке во время обработки файлового хранилища записи `Btk_File` могут сохраниться в базе данных, хотя часть физических файлов уже удалена.
