Изменение описывает три дефекта двусторонней синхронизации: повторную загрузку собственного превью, удаление серверной копии до подтверждения замены и повторную разгрузку только что восстановленного оригинала.
7.7 KiB
7.7 KiB
Tasks
1. Роль локального файла вместо состояния индекса
- 1.1 Добавить в
LocalAsset(data/sync/SyncPlan.kt) признаки метки превью (stampedOriginalHash,stampedAssetKey), а вDeviceLocalAssetProviderчитатьProvenanceStampв момент пересчёта хеша файла; решение «локальный файл — созданное приложением превью» вынести в чистую функцию с юнит-тестом (cd android && ./gradlew :app:testDebugUnitTest --tests "*ProvenanceStampTest" --tests "*SyncPlannerTest"). - 1.2 В
SyncPlanner.planне загружать превью и не удалять из-за него серверную копию: использовать метку как признак роли и идентичность оригинала, отдавать новое действиеSyncAction.MarkPreview; покрытьSyncPlannerTestслучаями «состояние индексаLOCAL, файл — превью с меткой» (ожидаетсяMarkPreview), «другое содержимое без метки» (ожидается загрузка) и «превью потерявшего копию элемента» (cd android && ./gradlew :app:testDebugUnitTest --tests "*SyncPlannerTest"). - 1.3 В
SyncCoordinatorприменятьMarkPreview(состояниеPREVIEW) и не разгружать элементы с незавершённой загрузкой (SyncUploadQueue.isPending) вoffloadUploaded; покрытьSyncCoordinatorTestслучаями «расхождение состояния выправляется» и «загрузка в очереди — разгрузки нет» (cd android && ./gradlew :app:testDebugUnitTest --tests "*SyncCoordinatorTest"). - 1.4 Распознавать постер разгруженного видео как превью исходного элемента по метке (
assetуказывает на видео, а не на постер) и не удалять серверную копию видео, уступившего место постеру; покрытьSyncPlannerTestслучаями «постер связывается с видео, не загружается и не удаляется» и «видео без постера удаляется как раньше» (cd android && ./gradlew :app:testDebugUnitTest --tests "*SyncPlannerTest").
2. Замена версии с подтверждением
- 2.1 Добавить
replacesPhotoIdвUploadJobEntity, миграциюMIGRATION_8_9(версия 9) и ветку вMigrationTest; проверитьcd android && ./gradlew :app:testDebugUnitTest --tests "*MigrationTest" --tests "*UploadRepositoryTest". - 2.2 В
SyncPlannerзаменить пару «Upload+DeleteOnServer» для изменённого содержимого наSyncAction.ReplaceUpload(asset, stalePhotoId), а вSyncCoordinatorписать надгробие для прежней копии и ставить задание с полем замены; покрытьSyncPlannerTest(ожидаетсяReplaceUpload) иSyncCoordinatorTest(надгробие и задание с заменой) (cd android && ./gradlew :app:testDebugUnitTest --tests "*SyncPlannerTest" --tests "*SyncCoordinatorTest"). - 2.3 Вынести сверку принятой контрольной суммы в чистую функцию и подключить её в
UploadWorker: совпадениеoriginal_checksumс хешем задания удаляет прежнюю копию и записывает провенанс, расхождение или ошибка — не удаляет, не разгружает и оставляет задание с ошибкой; покрытьSyncUploadTest(cd android && ./gradlew :app:testDebugUnitTest --tests "*SyncUploadTest"). - 2.4 Сервер: заполнять
p.OriginalChecksumвService.Uploadдо ответа и публикации события (существующее требованиеphotos«Контрольная сумма оригинала» распространяется на данные созданного фото, но ответ на загрузку её не отдавал — живая проверка показала, что из-за этого отклонялись все синхронные загрузки); покрыть тестом ответа на загрузку (cd backend && go test ./internal/app/ -run TestUploadResponseExposesOriginalChecksum -v). - 2.5 Клиент остаётся устойчивым к инстансу без контрольной суммы: «не сообщено» SHALL NOT считаться расхождением — загрузка принимается, а прежняя копия не удаляется; покрыть
SyncUploadTestслучаями «не сообщено» и «нет заявки на содержимое» (cd android && ./gradlew :app:testDebugUnitTest --tests "*SyncUploadTest").
3. Состояние после возврата оригинала
- 3.1 В
RestoreManagerпомечать восстановленный элементUPLOADED(оригинал и серверная копия) вместоLOCAL; покрытьRestoreManagerTestиGallerySyncPolicyTestслучаем «после возврата повторной разгрузки нет» (cd android && ./gradlew :app:testDebugUnitTest --tests "*RestoreManagerTest" --tests "*GallerySyncPolicyTest").
5. Интеграционная проверка
- 5.1 Прогнать весь Android-набор и собрать debug-APK (
cd android && ./gradlew test :app:assembleDebug).
Workflow follow-up
- Восстановление пострадавших оригиналов (бывшая задача 4) — ручная процедура владельца инстанса, выполняется вне change, креды в репозиторий и артефакты не попадают:
- вернуть из корзины
6d4e1897-3a7e-4cba-9d85-5bbb4b06f5a4(IMG_20261004_163151.jpg, 3 437 694 Б, 3072x4096) и991ea4ee-debb-4ecb-bc26-e4d1a2140085(IMG_20261009_153136.jpg, 2 226 043 Б, 3072x4096) в альбомN2(POST /api/photos/{id}/restore); - удалить деградировавшие копии
b0b29905-712b-4143-9d8c-0b74aa567ce5и1f49e8fb-ea9c-4764-8daa-7b185be107e9(DELETE /api/photos/{id}); - проверить, что в альбоме осталось по одной записи с каждым именем и оригинал у обеих 3072x4096.
- вернуть из корзины
- Уборка после регрессии первой версии сверки контрольной суммы (живая проверка 2026-10-16): в новом альбоме
U2(e7ae31c9-805c-42f5-8fd2-ea2304f0c485) лежат 19 мягко удалённых копий десяти файлов, которые приложение само же и отклонило. Их список (id и имена) — в отчёте; удаляются точечноDELETE /api/photos/{id}/permanent. Корзину целиком очищать нельзя, пока не восстановлены оригиналыN2: они лежат там же. - Заархивировать изменение после прохождения ревью проекта.
- Проверить результат архивации.