Возврат из переноса в копии скачивает оригиналы с сервера, но ход нигде не отражался: карточка умела показывать только загрузку, а восстановление шло в области экрана и обрывалось при уходе с него. Добавлен RestoreCoordinator: выполняет восстановление в области приложения (уход с экрана больше не прерывает), ведёт ход «альбом -> восстановлено из всего» и защищает от одновременного запуска по одному альбому. RestoreManager сообщает ход по каждому элементу и вынесен за интерфейс AlbumRestorer; SyncCheck ходит через координатора и, как и раньше, дожидается завершения. Карточка альбома показывает «Копии — восстановление N%» (пульсирует), экран альбома устройства — строку «Восстановление оригиналов: X из Y» с полосой. Изменение OpenSpec: android-album-restore-progress.
5.7 KiB
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
- Нужно ли отдельное сообщение при остановке восстановления из-за нехватки места.