feat(android): показывать ход восстановления оригиналов
Возврат из переноса в копии скачивает оригиналы с сервера, но ход нигде не отражался: карточка умела показывать только загрузку, а восстановление шло в области экрана и обрывалось при уходе с него. Добавлен RestoreCoordinator: выполняет восстановление в области приложения (уход с экрана больше не прерывает), ведёт ход «альбом -> восстановлено из всего» и защищает от одновременного запуска по одному альбому. RestoreManager сообщает ход по каждому элементу и вынесен за интерфейс AlbumRestorer; SyncCheck ходит через координатора и, как и раньше, дожидается завершения. Карточка альбома показывает «Копии — восстановление N%» (пульсирует), экран альбома устройства — строку «Восстановление оригиналов: X из Y» с полосой. Изменение OpenSpec: android-album-restore-progress.
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
# Design
|
||||
|
||||
## Context
|
||||
|
||||
См. `proposal.md` — Why. Что определяет подход:
|
||||
|
||||
- `RestoreManager.restore(assets)` — цикл «получить байты → записать поверх превью → пометить
|
||||
`LOCAL`», без источника хода; возвращает `RestoreReport`.
|
||||
- Вызывают его двое: `DeviceAlbumViewModel.restoreOriginals()` (явное действие и подтверждение
|
||||
выхода из режима C) и `SyncCheck.checkGallery` (неявная починка при открытии альбома).
|
||||
Оба идут в области вызывающего: у экрана это `viewModelScope`, у фонового воркера — его
|
||||
собственная область.
|
||||
- `RestoreManager.restoreTargets` и `GallerySyncPolicy` уже сходятся в одном наборе: превью
|
||||
(`state == PREVIEW`) с серверной копией.
|
||||
- Карточка альбома строит третью строку из `AlbumSyncProgress`, куда сейчас попадают только
|
||||
загрузки (`SyncUpload`) и счётчики индекса.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
|
||||
- Показывать ход восстановления на карточке и на экране альбома.
|
||||
- Не обрывать восстановление при уходе с экрана.
|
||||
- Не менять поведение загрузки, разгрузки и режимов.
|
||||
|
||||
**Non-Goals:**
|
||||
|
||||
- Восстановление потерянных копий из корзины сервера.
|
||||
- Возобновление восстановления после смерти процесса (операция идемпотентна и повторится при
|
||||
следующей проверке синхронизации).
|
||||
- Прогресс по байтам: считаем элементы, как и в очереди загрузки.
|
||||
|
||||
## Decisions
|
||||
|
||||
**Единая точка восстановления — `RestoreCoordinator` в области приложения.** Он владеет
|
||||
картой «альбом → ход», запускает операцию в scope контейнера и защищается от повторного
|
||||
запуска по одному альбому. Альтернатива «добавить колбэк прямо в менеджер и запускать из
|
||||
экрана» отклонена: она не решает обрыв при уходе с экрана.
|
||||
|
||||
**Два входа: `start` и `run`.** Явное действие пользователя использует `start` (область
|
||||
приложения, не ждём). `SyncCheck` использует `run` (ждёт завершения), чтобы фоновый воркер,
|
||||
как и раньше, не отдавал управление до конца восстановления. Оба делят одну защиту и одну
|
||||
карту хода.
|
||||
|
||||
**Прогресс отдаёт сам менеджер.** `restore(assets, onProgress)` вызывает колбэк после каждого
|
||||
элемента, включая пропуски, чтобы полоса доходила до конца. Ход считает `RestoreCoordinator`,
|
||||
а не менеджер: менеджер остаётся про байты и файлы.
|
||||
|
||||
**Интерфейс ради тестируемости.** Координатор зависит от `AlbumRestorer` (реализация —
|
||||
`RestoreManager`), чтобы его можно было проверить без MediaStore.
|
||||
|
||||
**Ход на карточке — новое состояние третьей строки.** `AlbumSyncProgress` получает
|
||||
`restorePercent`; приоритет: потерянные копии → восстановление → загрузка → ошибка →
|
||||
ожидание Wi-Fi → очередь → метка. Пульсация включает и восстановление: это активная работа.
|
||||
|
||||
**Уборка хода — по завершении.** Карта чистится в `finally`, поэтому и успех, и ошибка, и
|
||||
нехватка места убирают строку; незавершённые превью при следующем проходе попробуют снова.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- [Двойной запуск восстановления (экран + фон)] → защита по альбому: второй запуск
|
||||
игнорируется, пока идёт первый.
|
||||
- [Прогресс не переживает смерть процесса] → операция идемпотентна: оставшиеся превью
|
||||
восстанавливаются при следующей проверке; карточка просто теряет строку хода.
|
||||
- [Нехватка места обрывает восстановление раньше времени] → строка хода исчезает, состояние
|
||||
остаётся честным (часть превью осталась); отдельного сообщения о нехватке места не
|
||||
добавляем в этом объёме.
|
||||
- [Лишние эмиссии Room + flows при каждом элементе] → альбомы небольшие, состояние обновляется
|
||||
в памяти; реальной нагрузки нет.
|
||||
|
||||
## Migration Plan
|
||||
|
||||
Схема Room не меняется. Откат — обычная сборка без изменения.
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Нужно ли отдельное сообщение при остановке восстановления из-за нехватки места.
|
||||
Reference in New Issue
Block a user