chore(openspec): архивировать android-album-restore-progress и синхронизировать спеки
Дельта перенесена в основные спеки: android-client («Прогресс восстановления оригиналов») и phone-album-sync («Ход и устойчивость восстановления оригиналов»). Изменение перемещено в openspec/changes/archive/2026-10-16-android-album-restore-progress.
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-10-16
|
||||
@@ -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
|
||||
|
||||
- Нужно ли отдельное сообщение при остановке восстановления из-за нехватки места.
|
||||
@@ -0,0 +1,52 @@
|
||||
# Proposal
|
||||
|
||||
## Why
|
||||
|
||||
При возврате из режима переноса в копии (`MOVE → COPY`) приложение скачивает оригиналы с
|
||||
сервера и записывает их поверх превью, но **никак не показывает ход**. Карточка альбома умеет
|
||||
показывать только загрузку (upload-джобы), которых при этом переходе не возникает, поэтому
|
||||
третья строка молчит, а операция идёт.
|
||||
|
||||
Дополнительно восстановление запускается в области экрана (`viewModelScope`): стоит уйти с
|
||||
экрана альбома устройства — и оно обрывается на середине, хотя пользователь именно затем и
|
||||
уходит в список, чтобы посмотреть ход.
|
||||
|
||||
(Для альбома с потерянными копиями восстановление и не начнётся: такие элементы в целевой
|
||||
набор не входят — это отдельный случай, здесь не меняется.)
|
||||
|
||||
## What Changes
|
||||
|
||||
- Восстановление становится наблюдаемой операцией: приложение ведёт счётчик «готово из
|
||||
всего» по каждому альбому устройства.
|
||||
- Восстановление выполняется в области приложения, а не экрана: уход с экрана альбома не
|
||||
прерывает его.
|
||||
- Карточка альбома показывает ход: `Копии — восстановление N%` (и пульсирует, пока идёт).
|
||||
- Экран альбома устройства также показывает ход восстановления.
|
||||
- **BREAKING**: нет. Загрузка, разгрузка и режимы не меняются; добавляется только видимость
|
||||
и устойчивость уже существующей операции.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
_Нет — поведение относится к существующим способностям._
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `phone-album-sync`: восстановление оригиналов выполняется до конца независимо от ухода с
|
||||
экрана и сообщает ход (сколько элементов восстановлено из скольких).
|
||||
- `android-client`: карточка альбома и экран альбома устройства показывают ход
|
||||
восстановления оригиналов.
|
||||
|
||||
## Impact
|
||||
|
||||
- **Код (Android):** `data/sync/RestoreManager` (обратный вызов хода, интерфейс исполнителя),
|
||||
новый `data/sync/RestoreCoordinator` (область приложения + прогресс по альбомам),
|
||||
`di/AppContainer` (создание координатора), `data/sync/SyncCheck` (восстановление через
|
||||
координатора), `ui/devicealbum/DeviceAlbumViewModel` (запуск через координатора, ход в
|
||||
состоянии) и `DeviceAlbumScreen` (строка хода), `domain/sync/AlbumSyncProgress` и
|
||||
`ui/albums/AlbumsViewModel`/`AlbumsScreen` (ход на карточке).
|
||||
- **Схема Room:** без изменений.
|
||||
- **API и бэкенд:** не затрагиваются.
|
||||
- **Веб-клиент:** не затрагивается.
|
||||
- **Не затрагивается:** восстановление потерянных копий из корзины сервера.
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
# Spec Delta
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Прогресс восстановления оригиналов
|
||||
|
||||
Пока идёт восстановление оригиналов, карточка альбома устройства SHALL показывать ход в
|
||||
третьей строке («Копии — восстановление <процент>%»), а экран альбома устройства SHALL
|
||||
показывать ход отдельной строкой. По завершении ход SHALL исчезать.
|
||||
|
||||
#### Scenario: Карточка в списке
|
||||
|
||||
- **WHEN** для альбома устройства идёт восстановление оригиналов
|
||||
- **THEN** карточка показывает «<метка режима> — восстановление <процент>%»
|
||||
|
||||
#### Scenario: Экран альбома устройства
|
||||
|
||||
- **WHEN** для альбома устройства идёт восстановление оригиналов
|
||||
- **THEN** экран альбома показывает ход восстановления
|
||||
|
||||
#### Scenario: Завершение
|
||||
|
||||
- **WHEN** восстановление оригиналов завершилось
|
||||
- **THEN** ход больше не показывается, а карточка возвращается к метке режима
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
# Spec Delta
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Ход и устойчивость восстановления оригиналов
|
||||
|
||||
При возврате оригиналов на устройство приложение SHALL выполнять операцию до конца
|
||||
независимо от того, остаётся ли открыт экран альбома устройства, и SHALL сообщать ход:
|
||||
сколько элементов восстановлено из скольких намечено.
|
||||
|
||||
#### Scenario: Уход с экрана
|
||||
|
||||
- **WHEN** восстановление оригиналов запущено и пользователь покидает экран альбома устройства
|
||||
- **THEN** восстановление продолжается и завершается
|
||||
|
||||
#### Scenario: Ход операции
|
||||
|
||||
- **WHEN** восстановление оригиналов идёт
|
||||
- **THEN** приложение знает и сообщает, сколько элементов уже восстановлено из намеченных
|
||||
|
||||
#### Scenario: Нечего восстанавливать
|
||||
|
||||
- **WHEN** среди элементов альбома нет превью с серверной копией
|
||||
- **THEN** операция не запускается и ход не показывается
|
||||
@@ -0,0 +1,27 @@
|
||||
# Tasks
|
||||
|
||||
## 1. Ход восстановления в менеджере
|
||||
|
||||
- [x] 1.1 Вынести у восстановления интерфейс `AlbumRestorer` и добавить в `RestoreManager.restore` обратный вызов хода (после каждого элемента, включая пропуски); покрыть тестом, что ход доходит до «всего» при успехе и при неудачной записи (`cd android && ./gradlew :app:testDebugUnitTest --tests "*RestoreManagerTest"`).
|
||||
|
||||
## 2. Координатор восстановления
|
||||
|
||||
- [x] 2.1 Добавить `RestoreCoordinator`: карта «альбом → ход», запуск в области приложения, защита от повторного запуска по одному альбому; покрыть тестом «ход появляется и исчезает», «пустой набор — хода нет», «повторный запуск игнорируется» (`cd android && ./gradlew :app:testDebugUnitTest --tests "*RestoreCoordinatorTest"`).
|
||||
|
||||
## 3. Подключение вызывающих
|
||||
|
||||
- [x] 3.1 Создать координатора в `AppContainer` и перевести `DeviceAlbumViewModel.restoreOriginals` на `start`, а `SyncCheck` — на ожидаемый `run`; проверить, что `SyncCheck` по-прежнему выполняет восстановление до конца (`cd android && ./gradlew :app:testDebugUnitTest --tests "*SyncCoordinatorTest" --tests "*DeviceAlbum*"`).
|
||||
|
||||
## 4. Ход на карточке и на экране
|
||||
|
||||
- [x] 4.1 Добавить ход восстановления в `AlbumSyncProgress` (`restorePercent`) с приоритетом над загрузкой и в пульсацию; покрыть `AlbumSyncProgressTest` и связать с координатором в `AlbumsViewModel` (`cd android && ./gradlew :app:testDebugUnitTest --tests "*AlbumSyncProgressTest" --tests "*AlbumsViewModelTest"`).
|
||||
- [x] 4.2 Показать ход отдельной строкой на экране альбома устройства и проверить на карточке в списке; покрыть `DeviceAlbumContentTest` и `AlbumsContentTest` (`cd android && ./gradlew :app:testDebugUnitTest --tests "*DeviceAlbumContentTest" --tests "*AlbumsContentTest"`).
|
||||
|
||||
## 5. Интеграционная проверка
|
||||
|
||||
- [x] 5.1 Прогнать весь Android-набор и собрать debug-APK (`cd android && ./gradlew test :app:assembleDebug`).
|
||||
|
||||
## Workflow follow-up
|
||||
|
||||
- Заархивировать изменение после прохождения ревью проекта.
|
||||
- Проверить результат архивации.
|
||||
Reference in New Issue
Block a user