Изменение описывает три дефекта двусторонней синхронизации: повторную загрузку собственного превью, удаление серверной копии до подтверждения замены и повторную разгрузку только что восстановленного оригинала.
71 lines
7.4 KiB
Markdown
71 lines
7.4 KiB
Markdown
# Proposal
|
|
|
|
## Why
|
|
|
|
Режим переноса (C) может залить на сервер **превью вместо оригинала** и тут же удалить
|
|
устаревшую серверную копию, то есть молча уничтожить оригинал. Причина — планировщик не
|
|
отличает содержимое от роли локального файла: когда состояние элемента в локальном индексе
|
|
расходится с фактической ролью (в индексе «оригинал», на диске превью), смена роли читается
|
|
как «содержимое изменилось» и запускается цикл «удалил → залил → удалил».
|
|
|
|
Реальный инцидент: альбом устройства U2, режим переноса, серверный альбом `N2`. Из 20 фото
|
|
пять перезаливались по кругу (20:03 → 20:08 → 20:42 → 21:24), а два потеряли оригиналы —
|
|
`IMG_20261004_163151.jpg` (3 437 694 Б, 3072x4096 → 259 282 Б, 1200x1600) и
|
|
`IMG_20261009_153136.jpg` (2 226 043 Б → 192 651 Б). Лечиться это будет только сейчас:
|
|
у первого уцелевший оригинал лежит в корзине сервера, у второго — в корзине и в альбоме
|
|
«Первый». Побочно тот же цикл оставляет на телефоне элементы, которых на сервере уже нет:
|
|
в приложении альбом `N2` показывает 26 плиток вместо 20, шесть из них пустые.
|
|
|
|
## What Changes
|
|
|
|
- **Роль не является содержимым.** Приложение SHALL отличать созданное им превью (в том числе
|
|
по метке провенанса) от нового оригинала: превью никогда не загружается как новая версия и
|
|
не влечёт удаления серверной копии, а расхождение состояния в индексе выправляется.
|
|
Настоящее изменение содержимого, как и раньше, синхронизируется новой версией.
|
|
- **Удаление только после подтверждения.** Устаревшая собственная загрузка удаляется только
|
|
после того, как новая версия подтверждена сервером: сообщённая им контрольная сумма
|
|
принятого объекта совпала с отправленной. При расхождении загрузка отклоняется вместе с
|
|
принятой копией, прежняя серверная копия остаётся в альбоме, а замена повторяется при
|
|
следующей синхронизации. Если сервер контрольную сумму не сообщил (например, старая версия
|
|
инстанса), загрузка остаётся состоявшейся, но прежняя копия не удаляется.
|
|
- **Контрольная сумма в ответе на загрузку.** Сервер SHALL отдавать контрольную сумму
|
|
оригинала в данных созданного фото — как требует существующее требование способности
|
|
`photos`; сегодня ответ на загрузку её не содержит (дефект сервера, а не изменение API).
|
|
- **Разгрузка не гоняется с загрузкой.** Оригинал не заменяется превью, пока по этому
|
|
элементу есть незавершённая загрузка.
|
|
- **Возврат оригинала устойчив.** После возврата оригиналов на устройство элемент не
|
|
разгружается превью повторно, пока альбом снова не переведён в режим переноса.
|
|
- **Разовое восстановление двух пострадавших фото** из корзины сервера (и альбома «Первый»
|
|
для второго) с удалением деградировавших копий.
|
|
- **BREAKING**: нет. Успешные сценарии (загрузка нового, разгрузка, повторная синхронизация,
|
|
настоящее изменение содержимого) сохраняют поведение; меняются только защитные условия.
|
|
|
|
## Capabilities
|
|
|
|
### New Capabilities
|
|
|
|
_Нет — поведение относится к существующей способности синхронизации альбома устройства._
|
|
|
|
### Modified Capabilities
|
|
|
|
- `phone-album-sync`: различение роли и содержимого (метка превью против нового оригинала),
|
|
разгрузка только без незавершённой загрузки, удаление устаревшей загрузки только после
|
|
подтверждения новой версии, устойчивый возврат оригиналов.
|
|
|
|
## Impact
|
|
|
|
- **Код (Android):** `data/sync/SyncPlan` (различение роли и содержимого, отложенная замена),
|
|
`data/sync/SyncCoordinator` (выправление состояния, разгрузка без незавершённых загрузок,
|
|
постановка замены), `data/sync/LocalAssetProvider` (метка превью рядом с хешем),
|
|
`data/upload/UploadWorker` (сверка принятой контрольной суммы, удаление устаревшей копии
|
|
после подтверждения, без разгрузки вне координатора), `data/sync/RestoreManager` (состояние
|
|
после возврата оригинала), `data/db` (поле «заменяемое фото» в задании загрузки).
|
|
- **Схема Room:** версия 8 → 9, `ALTER TABLE upload_jobs ADD COLUMN replacesPhotoId TEXT`.
|
|
- **API и бэкенд:** не затрагиваются — используются существующие `DELETE /api/photos/{id}`
|
|
(мягко, в корзину), `POST /api/photos/{id}/restore` и `original_checksum` в ответе загрузки.
|
|
- **Данные инстанса:** разовое восстановление двух оригиналов и удаление деградировавших
|
|
копий (операция, а не постоянное поведение).
|
|
- **Не затрагивается:** серверная модель версий/идентичности, дедупликация вложения, чистка
|
|
кэша альбома на телефоне (пустые плитки удалённых фото — отдельная задача), доверие к
|
|
метаданным MediaStore при кэшировании хешей (сверка контрольной суммы закрывает риск).
|