Исправлено тире

This commit is contained in:
2026-05-30 16:47:52 +03:00
parent af9cd2dead
commit 229c0629f8
27 changed files with 10430 additions and 10380 deletions

View File

@@ -32,19 +32,19 @@ fun enableEncryption(storage: IStorageInfo, password: String, encryptPath: Boole
}
```
Блок-схема сценария рис. @fig-21.
Блок-схема сценария рис. @fig-21.
#pz-fig("fig_21_encrypt_flow.png", [Блок-схема: enableEncryption checkKey openStorage], "fig-21")
=== Модуль адаптеров хранилищ
`VaultsManager` агрегирует один `LocalVault` и удалённые vault; адаптеры реализуют доступ к файлам внутри каждого `IStorage`. Регистрация удалённых vault через модуль `:vault-contracts`.
`VaultsManager` агрегирует один `LocalVault` и удалённые vault; адаптеры реализуют доступ к файлам внутри каждого `IStorage`. Регистрация удалённых vault через модуль `:vault-contracts`.
=== Модуль синхронизации хранилищ
В Room хранится сущность `DbStorageSyncGroup` набор UUID `Storage`, которые должны иметь согласованное состояние. Запуск синхронизации выполняется через `RunStorageSyncUseCase` / `WorkManager` и debounce при изменении файлов (рис. @fig-01@fig-03). Движок `StorageSyncEngine` (модуль `:usecases`) реализует согласование журналов изменений; доработка касается в основном политики фонового расписания и UX отображения прогресса.
В Room хранится сущность `DbStorageSyncGroup` набор UUID `Storage`, которые должны иметь согласованное состояние. Запуск синхронизации выполняется через `RunStorageSyncUseCase` / `WorkManager` и debounce при изменении файлов (рис. @fig-01@fig-03). Движок `StorageSyncEngine` (модуль `:usecases`) реализует согласование журналов изменений; доработка касается в основном политики фонового расписания и UX отображения прогресса.
Для каждого `Storage` ведётся журнал изменений по относительным путям пользовательских файлов. Запись журнала (`StorageSyncJournalEntry`) содержит операцию (`UPSERT`, `TRASH`, `DELETE`) и ревизию (`sequence`, `actorId`, `createdAt`). Ключи шифрования в обмен не включаются провайдер видит только зашифрованные объекты. Модуль `:task-runtime` обслуживает длительные задачи шифрования и синхронизации без блокировки UI.
Для каждого `Storage` ведётся журнал изменений по относительным путям пользовательских файлов. Запись журнала (`StorageSyncJournalEntry`) содержит операцию (`UPSERT`, `TRASH`, `DELETE`) и ревизию (`sequence`, `actorId`, `createdAt`). Ключи шифрования в обмен не включаются провайдер видит только зашифрованные объекты. Модуль `:task-runtime` обслуживает длительные задачи шифрования и синхронизации без блокировки UI.
=== Алгоритм согласования журналов синхронизации
@@ -52,18 +52,18 @@ fun enableEncryption(storage: IStorageInfo, password: String, encryptPath: Boole
#pz-fig("fig_35_sync_merge_algorithm.png", [Алгоритм согласования журналов (StorageSyncEngine)], "fig-35")
*Подготовка.* По UUID из `DbStorageSyncGroup` загружаются объекты `IStorage`. Если в группе меньше двух хранилищ или несовместимы параметры шифрования, синхронизация пропускается. На каждом accessor запрашивается блокировка sync (lease на удалённом диске best-effort; внутри процесса группа сериализуется `Mutex`). Параллельно для каждого storage вызываются `flushPendingSyncJournal()` и `readSyncJournal()`; служебные пути отфильтровываются (`StorageSyncPaths.isSyncableUserPath`).
*Подготовка.* По UUID из `DbStorageSyncGroup` загружаются объекты `IStorage`. Если в группе меньше двух хранилищ или несовместимы параметры шифрования, синхронизация пропускается. На каждом accessor запрашивается блокировка sync (lease на удалённом диске best-effort; внутри процесса группа сериализуется `Mutex`). Параллельно для каждого storage вызываются `flushPendingSyncJournal()` и `readSyncJournal()`; служебные пути отфильтровываются (`StorageSyncPaths.isSyncableUserPath`).
*Слияние журналов.* Объект `StorageSyncJournalMerge` объединяет журналы в отображение «путь победитель». Для каждого пути остаётся запись с наибольшей ревизией. Сравнение реализовано функцией `compareEntries`: сначала `revision.sequence`, при равенстве `actorId`, затем `createdAt`. Такой порядок обеспечивает детерминированный выбор одной записи при конкурентных изменениях на разных устройствах.
*Слияние журналов.* Объект `StorageSyncJournalMerge` объединяет журналы в отображение «путь победитель». Для каждого пути остаётся запись с наибольшей ревизией. Сравнение реализовано функцией `compareEntries`: сначала `revision.sequence`, при равенстве `actorId`, затем `createdAt`. Такой порядок обеспечивает детерминированный выбор одной записи при конкурентных изменениях на разных устройствах.
*Выбор источника для пути.* После слияния для каждой пары (путь, `winnerEntry`) вызывается `findSourceStorage`:
+ при `UPSERT` первый `Storage`, у которого запись по этому пути *совпадает* с победителем (`compareEntries == 0`): оттуда читаются байты для копирования;
+ при `DELETE` или `TRASH` первый `Storage`, где путь ещё присутствует в журнале (или любой storage группы, если записей нет): оттуда инициируется удаление на целях.
- при `UPSERT` первый `Storage`, у которого запись по этому пути *совпадает* с победителем (`compareEntries == 0`): оттуда читаются байты для копирования;
- при `DELETE` или `TRASH` первый `Storage`, где путь ещё присутствует в журнале (или любой storage группы, если записей нет): оттуда инициируется удаление на целях.
Если для `UPSERT` источник не найден, путь пропускается (файл уже отсутствует на всех носителях).
*Распространение на цели.* Для каждого `target` в группе, отличного от источника, сравнивается ревизия записи на цели с `winnerEntry`. Если цель уже не слабее победителя (`compareEntries(target, winner) >= 0`), шаг пропускается. Иначе вызывается `applyEntry`: для `UPSERT` потоковое копирование `openRead` `openWrite`; для `DELETE` / `TRASH` `delete` или `moveToTrash`. Все операции выполняются с `recordSyncJournal = false`, чтобы не порождать цикл повторной синхронизации. Ошибки отдельных путей учитываются счётчиком `applyFailures`, отмена кооперативная (проверка `syncGeneration`, снятие блокировок в `finally`).
*Распространение на цели.* Для каждого `target` в группе, отличного от источника, сравнивается ревизия записи на цели с `winnerEntry`. Если цель уже не слабее победителя (`compareEntries(target, winner) >= 0`), шаг пропускается. Иначе вызывается `applyEntry`: для `UPSERT` потоковое копирование `openRead` `openWrite`; для `DELETE` / `TRASH` `delete` или `moveToTrash`. Все операции выполняются с `recordSyncJournal = false`, чтобы не порождать цикл повторной синхронизации. Ошибки отдельных путей учитываются счётчиком `applyFailures`, отмена кооперативная (проверка `syncGeneration`, снятие блокировок в `finally`).
Фрагмент сравнения ревизий и выбора источника:
@@ -154,7 +154,7 @@ remoteVaultAuthenticator.beginLink(CloudBrand.YANDEX) { outcome ->
#pz-fig("fig_23_module_deps.png", [Зависимости модулей Gradle], "fig-23")
В основном тексте приведены показательные фрагменты; полные листинги в приложении А.
В основном тексте приведены показательные фрагменты; полные листинги в приложении А.
#include "ch04-expand.typ"
#include "ch04-modules.typ"