Files
ds-galo/openspec/changes/archive/2026-10-16-android-album-restore-progress/design.md
T
iu 19309e2ac0 chore(openspec): архивировать android-album-restore-progress и синхронизировать спеки
Дельта перенесена в основные спеки: android-client («Прогресс восстановления
оригиналов») и phone-album-sync («Ход и устойчивость восстановления оригиналов»).
Изменение перемещено в openspec/changes/archive/2026-10-16-android-album-restore-progress.
2026-10-16 20:15:57 +03:00

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

  • Нужно ли отдельное сообщение при остановке восстановления из-за нехватки места.