Дельта-спеки перенесены в основные: phone-album-sync (+8 требований, 6 изменено, 1 удалено — «Удаление только собственных загрузок»), android-client. Change перемещён в openspec/changes/archive/2026-10-16-phone-album-two-way-sync.
60 lines
5.5 KiB
Markdown
60 lines
5.5 KiB
Markdown
# Proposal
|
|
|
|
## Why
|
|
|
|
Синхронизация альбома устройства сегодня односторонняя по содержимому: устройство отдаёт
|
|
байты, сервер возвращает только метаданные. Фото, добавленное в целевой серверный альбом из
|
|
веба или с другого устройства, на телефон не приезжает, а связанный серверный альбом ещё и
|
|
скрыт из списка — увидеть его в приложении нельзя. Пользователь ожидает, что
|
|
синхронизированный альбом — один и тот же набор медиа на телефоне и на сервере.
|
|
|
|
## What Changes
|
|
|
|
- Связанная пара «альбом устройства → серверный альбом» становится **двусторонним миррором**:
|
|
состав медиа совпадает, добавление и удаление ходят в обе стороны.
|
|
- **Материализация серверных фото на устройстве**: в режиме копий (B) скачивается оригинал, в
|
|
режиме переноса (C) — превью (для видео — постер). Файлы пишутся в галерею (MediaStore)
|
|
бакета альбома устройства и видны системной галерее.
|
|
- Вводится явный **признак происхождения** элемента (`own` — загружено этим клиентом,
|
|
`mirror` — скачано с сервера). Политика удалений, разгрузка и восстановление после
|
|
переустановки опираются на него, а не на наличие `serverPhotoId`.
|
|
- **Удаление в обе стороны**: удаление элемента на устройстве удаляет соответствующее фото на
|
|
сервере (мягко, в корзину); серверное удаление убирает локальный элемент. Удаление
|
|
принадлежащего иному источнику элемента на устройстве подчиняется правам сервера.
|
|
- **Журнал удалений (надгробия)**: локально удалённый элемент не скачивается заново, пока
|
|
серверное удаление не подтверждено; серверные события не дают эха.
|
|
- Восстановление серверного фото из корзины заново материализует элемент на устройстве.
|
|
- **Отвязка и выключение синхронизации** остаются неразрушающими: фото на сервере сохраняются,
|
|
уже скачанные файлы остаются в галерее как обычные.
|
|
- **Выключение синхронизации устойчиво**: восстановление происхождения по меткам превью не
|
|
возобновляет связь, пока пользователь не включит синхронизацию снова.
|
|
- **Первый проход**: выбор «скачать всё содержимое альбома» или «только новое с этого момента»;
|
|
ограничение «только Wi-Fi» распространяется и на скачивание.
|
|
- **BREAKING**: нет — поведение аддитивное, существующие связи продолжают работать.
|
|
|
|
## Capabilities
|
|
|
|
### New Capabilities
|
|
|
|
Нет — поведение относится к существующей способности синхронизации альбома устройства.
|
|
|
|
### Modified Capabilities
|
|
|
|
- `phone-album-sync`: двусторонний миррор — материализация серверных фото по режимам, признак
|
|
происхождения, надгробия, политика удалений в обе стороны, восстановление из корзины,
|
|
поведение при отвязке.
|
|
- `android-client`: сетка альбома устройства включает скачанные (серверные) элементы, а
|
|
связанный альбом читается как один двусторонний альбом.
|
|
|
|
## Impact
|
|
|
|
- `android/`: движок синхронизации (`data/sync/*` — `SyncPlan`, `SyncCoordinator`, `SyncCheck`,
|
|
`SyncRecovery`, `ProvenanceStamp`, `OffloadManager`, `RestoreManager`, `LocalAssetProvider`),
|
|
новый загрузчик серверных фото, запись в MediaStore без диалога согласия (файлы приложения),
|
|
Room-схема (признак происхождения, надгробия, watermark первого прохода), UI
|
|
(`DeviceAlbumScreen`/`DeviceAlbumViewModel`, `AlbumsScreen`/`AlbumListBuilder`,
|
|
`SyncRepository`).
|
|
- `backend/`: изменений API не требуется — `preview_url`/`medium_url`/`original_url`, `?since=`,
|
|
мягкое удаление и восстановление из корзины уже есть.
|
|
- Спеки: `phone-album-sync`, `android-client`.
|