Files
iu 3a44844ff9 chore(openspec): архивировать fix-album-sync-churn-and-degradation и синхронизировать спеки
В спеку phone-album-sync добавлены требования о роли локального файла,
подтверждении новой версии до удаления устаревшей и разгрузке без
незавершённой загрузки; уточнены синхронизация изменений, провенанс и
возврат оригиналов.
2026-10-20 20:35:52 +03:00

41 KiB

phone-album-sync Specification

Purpose

Двусторонняя синхронизация альбома устройства с существующим серверным альбомом (миррор состава): режимы A/B/C, материализация серверных фото на устройстве, отслеживание изменений, идентичность и дедупликация, удаления в обе стороны, разгрузка места и провенанс разгруженных превью.

Requirements

Requirement: Режимы синхронизации альбома устройства

Альбом устройства SHALL иметь выбираемый режим синхронизации: выключена (A — только локально), копии оригиналов на сервере (B), перенос оригиналов на сервер (C). Пока режим A, приложение SHALL показывать альбом локально и SHALL NOT обращаться к серверу по этому альбому.

Scenario: Выключенная синхронизация

  • WHEN у альбома устройства выбран режим A
  • THEN приложение показывает альбом из локальных файлов и не отправляет запросы на сервер по этому альбому

Scenario: Копии оригиналов

  • WHEN у альбома устройства выбран режим B
  • THEN оригиналы остаются на устройстве, а их копии загружаются в целевой серверный альбом

Scenario: Перенос оригиналов

  • WHEN у альбома устройства выбран режим C
  • THEN оригиналы загружаются на сервер, а на устройстве вместо них остаются превью

Requirement: Выбор целевого серверного альбома

При включении синхронизации пользователь SHALL выбрать серверный альбом назначения — существующий или созданный на месте, с именем, предзаполненным именем альбома устройства. Несколько альбомов устройства SHALL иметь возможность указывать на один серверный альбом.

Scenario: Выбор цели

  • WHEN пользователь включает синхронизацию альбома устройства
  • THEN приложение предлагает выбрать существующий серверный альбом или создать новый с предзаполненным именем и сохраняет выбор

Scenario: Несколько источников в одну цель

  • WHEN два альбома устройства указывают на один серверный альбом
  • THEN оба синхронизируются в него, а связь каждого отражается отдельно

Requirement: Отслеживание изменений на устройстве

Приложение SHALL отслеживать изменения содержимого синхронизируемого альбома устройства — добавление, удаление, переименование, перенос между альбомами и изменение содержимого — и SHALL хранить локальный индекс, позволяющий обнаружить удаления, произошедшие вне приложения.

Scenario: Добавление

  • WHEN в альбоме устройства появляется новое медиа
  • THEN приложение фиксирует его как новое и ставит в очередь синхронизации

Scenario: Удаление вне приложения

  • WHEN медиа удалено из альбома устройства другим приложением или системной галереей
  • THEN приложение обнаруживает отсутствие по локальному индексу

Scenario: Переименование и перенос

  • WHEN медиа переименовано или перемещено между альбомами устройства
  • THEN приложение фиксирует изменение имени или принадлежности альбому

Requirement: Синхронизация изменений с сервером

Для каждого отслеженного изменения приложение SHALL применить его к целевому серверному альбому: загрузить новое содержимое, переименовать и перенести через API. Изменение содержимого SHALL трактоваться как новая загрузка, а устаревшая собственная загрузка SHALL удаляться после подтверждения новой версии и SHALL NOT удаляться из-за смены локальной роли файла (оригинал ↔ превью).

Scenario: Новое содержимое

  • WHEN в альбоме устройства появилось медиа, которого нет на сервере
  • THEN приложение загружает его в целевой серверный альбом

Scenario: Переименование доходит до сервера

  • WHEN медиа переименовано на устройстве и уже синхронизировано
  • THEN приложение переименовывает соответствующее серверное фото

Scenario: Изменение содержимого

  • WHEN содержимое медиа изменено на устройстве
  • THEN приложение загружает новую версию и после её подтверждения удаляет устаревшую собственную загрузку

Requirement: Подтверждение новой версии до удаления устаревшей

Устаревшая собственная загрузка элемента SHALL удаляться только после подтверждения новой версии: сообщённая сервером контрольная сумма принятого объекта SHALL совпадать с отправленной. При расхождении приложение SHALL отклонить загрузку, удалив принятую копию, и SHALL NOT считать элемент загруженным. Если сервер контрольную сумму не сообщил, загрузка SHALL считаться состоявшейся, но устаревшая копия SHALL NOT удаляться, а замена SHALL повториться при следующей синхронизации.

Scenario: Новая версия подтверждена

  • WHEN новая версия загружена, а сообщённая сервером контрольная сумма совпала с отправленной
  • THEN приложение удаляет устаревшую собственную загрузку (мягко, в корзину)

Scenario: Контрольная сумма не совпала

  • WHEN сервер сообщил для принятого объекта контрольную сумму, отличную от отправленной
  • THEN приложение удаляет принятую копию, не считает элемент загруженным и оставляет прежнюю серверную копию в альбоме

Scenario: Контрольная сумма не сообщена

  • WHEN сервер не сообщил контрольную сумму принятого объекта
  • THEN загрузка считается состоявшейся, прежняя серверная копия остаётся в альбоме, а замена повторяется при следующей синхронизации

Scenario: Загрузка не удалась

  • WHEN загрузка новой версии завершилась ошибкой
  • THEN прежняя серверная копия остаётся в альбоме

Requirement: Идентичность по контрольной сумме

Приложение SHALL сопоставлять локальный ассет с серверным фото по контрольной сумме содержимого и SHALL NOT повторно загружать содержимое, уже присутствующее на сервере.

Scenario: Повторная синхронизация

  • WHEN синхронизация запускается повторно без изменений содержимого
  • THEN приложение не создаёт новых загрузок

Scenario: Уже загруженное содержимое

  • WHEN содержимое ассета уже присутствует на сервере (в том числе загружено ранее)
  • THEN приложение связывает ассет с существующим серверным фото без загрузки

Requirement: Отражение серверных изменений на устройстве

Приложение SHALL приводить локальное представление целевого серверного альбома в соответствие с его составом: новое серверное фото SHALL материализоваться на устройстве (оригинал в режиме копий, превью в режиме переноса), переименования, теги и подписи SHALL отражаться, а серверное удаление SHALL убирать локальный элемент. Удаление снятого на устройстве оригинала SHALL учитывать права сервера и системное согласие.

Scenario: Новое с другого устройства

  • WHEN другое устройство добавляет фото в целевой серверный альбом
  • THEN приложение материализует его в альбоме устройства — оригинал в режиме копий, превью в режиме переноса

Scenario: Удалено на сервере

  • WHEN серверное фото удалено на сервере
  • THEN приложение убирает соответствующий локальный элемент, а для снятого на устройстве оригинала запрашивается системное согласие на удаление

Requirement: Разгрузка оригиналов в режиме переноса

В режиме C после того, как оригинал элемента оказывается на сервере (загружен этим клиентом или скачан при двусторонней синхронизации), приложение SHALL автоматически заменять оригинал на устройстве превью, без отдельного действия на экране; видео SHALL заменяться постером-кадром. При включении режима C приложение SHALL запрашивать широкий доступ к файлам, чтобы замена выполнялась без системного диалога согласия.

Scenario: Разгрузка фото

  • WHEN оригинал фото успешно загружен
  • THEN файл в галерее автоматически заменяется превью с сохранением даты съёмки и места в альбоме

Scenario: Разгрузка скачанного оригинала

  • WHEN альбом переключён в режим переноса, а оригинал элемента был скачан с сервера
  • THEN оригинал автоматически заменяется превью

Scenario: Разгрузка видео

  • WHEN оригинал видео успешно загружен
  • THEN видео в галерее автоматически заменяется статичным постером-кадром, а элемент в приложении остаётся видео с оригиналом на сервере

Scenario: Доступ к файлам для разгрузки

  • WHEN пользователь включает режим переноса
  • THEN приложение запрашивает широкий доступ к файлам, и разгрузка выполняется без системного диалога согласия

Requirement: Разгрузка без незавершённой загрузки

Приложение SHALL NOT заменять оригинал превью, пока для этого элемента есть незавершённая загрузка (в очереди или выполняющаяся), чтобы загрузка не отправила превью вместо оригинала.

Scenario: Загрузка в очереди

  • WHEN для элемента есть задание загрузки в очереди или выполняющееся
  • THEN оригинал на устройстве не заменяется превью

Scenario: Загрузка подтверждена

  • WHEN загрузка элемента подтверждена сервером
  • THEN приложение выполняет обычную разгрузку в режиме переноса

Requirement: Провенанс разгруженных превью

Разгруженные превью, постеры видео и скачанные с сервера элементы SHALL содержать метаданные провенанса, включая признак происхождения (загружено клиентом или скачано с сервера), достаточные для восстановления связи с серверным альбомом и принадлежности загрузок после переустановки приложения. Метка SHALL также служить признаком того, что локальный файл является созданным приложением превью: элемент с такой меткой SHALL NOT считаться содержащим оригинал, даже если состояние локального индекса утверждает обратное.

Scenario: Восстановление после переустановки

  • WHEN приложение переустановлено или его данные очищены, а альбом устройства содержит разгруженные превью
  • THEN приложение восстанавливает связь с серверным альбомом и принадлежность своих загрузок из метаданных превью

Scenario: Метка скачанного элемента

  • WHEN метка провенанса восстанавливается у элемента, скачанного с сервера
  • THEN элемент восстанавливается как скачанный, а не как собственная загрузка клиента

Scenario: Метка утрачена

  • WHEN метаданные провенанса недоступны (например, файл перезаписан сторонним редактором)
  • THEN элемент считается неопознанным, не удаляется и не загружается повторно

Scenario: Метка против состояния индекса

  • WHEN файл несёт метку созданного приложением превью, а состояние индекса указывает оригинал
  • THEN элемент считается разгруженным, а не новым оригиналом

Requirement: Роль локального файла не является содержимым

Приложение SHALL отличать превью, созданное им самим при разгрузке (в том числе по метке провенанса), от новой версии оригинала: разгруженный элемент SHALL NOT загружаться повторно и SHALL NOT вызывать удаление серверной копии, а расхождение состояния в локальном индексе SHALL выправляться. Это SHALL относиться и к постеру, которым в режиме переноса заменяется видео.

Scenario: Разгруженный элемент не создаёт новую версию

  • WHEN серверная копия элемента есть в целевом альбоме, а на устройстве лежит созданное приложением превью, в том числе если состояние локального индекса ошибочно указывает оригинал
  • THEN приложение не ставит загрузку и не удаляет серверную копию

Scenario: Состояние выправляется

  • WHEN файл на устройстве является созданным приложением превью, а индекс считает его оригиналом
  • THEN приложение помечает элемент как разгруженный и последующие проходы не создают загрузок

Scenario: Постер разгруженного видео

  • WHEN видео разгружено (заменено постером под новым элементом MediaStore), а его метка указывает на исходное видео
  • THEN приложение связывает элемент с серверным фото видео и не удаляет это фото на сервере, а постер не загружается как новое фото

Scenario: Настоящее изменение содержимого

  • WHEN содержимое медиа заменено содержимым, не созданным приложением при разгрузке
  • THEN приложение загружает новую версию элемента

Requirement: Возврат оригиналов и отвязка

При выходе из режима C приложение SHALL предложить вернуть оригиналы на устройство и SHALL предупредить, если пользователь отказывается. Отвязка альбома и выключение синхронизации SHALL NOT удалять фото с сервера, а скачанные с сервера файлы SHALL оставаться в галерее как обычные. Выключение синхронизации SHALL быть явным и устойчивым: восстановление происхождения SHALL NOT возобновлять связь, пока пользователь не включит синхронизацию снова.

Scenario: Возврат оригиналов

  • WHEN пользователь выходит из режима C и соглашается вернуть оригиналы
  • THEN приложение возвращает оригиналы на устройство вместо превью

Scenario: Отказ от возврата

  • WHEN пользователь выходит из режима C и отказывается возвращать оригиналы
  • THEN приложение предупреждает, что на устройстве останутся только превью

Scenario: Отвязка

  • WHEN пользователь отвязывает альбом устройства от серверного
  • THEN фото на сервере сохраняются, а скачанные файлы остаются в галерее как обычные

Scenario: Выключение синхронизации

  • WHEN пользователь выключает синхронизацию альбома устройства
  • THEN связь с серверным альбомом снимается, а фото на сервере сохраняются

Scenario: Выключение не отменяется восстановлением

  • WHEN пользователь выключил синхронизацию (или отвязал альбом), и затем выполняется проверка с восстановлением происхождения по меткам превью
  • THEN связь не восстанавливается, а альбом остаётся локальным с выбранным режимом выключено

Scenario: Повторное включение

  • WHEN пользователь снова включает синхронизацию после выключения
  • THEN связь создаётся заново, а восстановление происхождения снова действует по обычным правилам

Scenario: Возврат устойчив

  • WHEN оригиналы возвращены на устройство после выхода из режима переноса
  • THEN приложение не разгружает их повторно, пока альбом не переведён в режим переноса снова

Requirement: Автоматическая фоновая синхронизация

Синхронизация SHALL выполняться автоматически в обе стороны: при открытом приложении — по событиям изменения, в фоне — периодически, с учётом ограничений сети, выбранных пользователем. В режиме C разгрузка оригиналов SHALL выполняться автоматически после того, как копия оказалась на сервере.

Scenario: Фоновая синхронизация

  • WHEN приложение не открыто, а в альбоме устройства появились изменения
  • THEN синхронизация выполняется в фоне при ближайшей возможности

Scenario: Фоновая загрузка серверных фото

  • WHEN приложение не открыто, а в целевом серверном альбоме появилось новое фото
  • THEN приложение материализует его на устройстве при ближайшей возможности

Scenario: Ограничение сети

  • WHEN включено ограничение «только Wi-Fi», а доступна только сотовая сеть
  • THEN синхронизация, включая скачивание серверных фото, не начинается до появления Wi-Fi

Scenario: Автоматическая разгрузка

  • WHEN оригиналы загружены в режиме C
  • THEN замена оригиналов превью выполняется автоматически, без отдельного подтверждения

Requirement: Локальные превью

Приложение SHALL генерировать превью на устройстве и хранить их локально; при просмотре элемента без локального оригинала приложение SHALL подгружать оригинал с сервера.

Scenario: Сетка из превью

  • WHEN открыт альбом устройства
  • THEN сетка показывает локальные превью

Scenario: Просмотр без локального оригинала

  • WHEN пользователь открывает элемент, чей оригинал разгружен на сервер
  • THEN приложение подгружает оригинал с сервера для полноэкранного просмотра

Requirement: Обнаружение потери серверной копии превью

При проверке синхронизации приложение SHALL сопоставлять серверное фото каждого разгруженного превью с фото целевого альбома и при его отсутствии SHALL помечать элемент как потерявший серверную копию, сохраняя превью и провенанс.

Scenario: Копии больше нет

  • WHEN серверное фото разгруженного превью отсутствует в целевом альбоме
  • THEN приложение помечает элемент как потерявший серверную копию

Scenario: Превью не трогается

  • WHEN элемент помечен как потерявший серверную копию
  • THEN превью-файл на устройстве сохраняется и не загружается повторно как оригинал

Requirement: Восстановление связи при возврате серверного фото

При проверке синхронизации приложение SHALL снимать пометку с элемента, если его серверное фото снова присутствует в целевом альбоме.

Scenario: Фото вернулось на сервер

  • WHEN серверное фото помеченного элемента снова присутствует в целевом альбоме
  • THEN элемент возвращается к обычному разгруженному состоянию с серверной копией

Requirement: Удаление потерянного превью с устройства

Приложение SHALL позволять удалить потерявшее серверную копию превью с устройства; удаление SHALL удалять превью-файл и запись локального индекса и SHALL NOT обращаться к серверу, поскольку копии там уже нет.

Scenario: Удаление с устройства

  • WHEN пользователь удаляет потерявшее серверную копию превью с устройства
  • THEN превью-файл и запись локального индекса удаляются, а сервер не затрагивается

Scenario: Согласие системы

  • WHEN превью-файл не принадлежит приложению
  • THEN удаление выполняется после системного согласия на удаление

Requirement: Повторная заливка вернувшегося оригинала

Если содержимое с исходной контрольной суммой потерявшего копию элемента снова присутствует на устройстве, приложение SHALL загрузить его на сервер как новую загрузку этого элемента.

Scenario: Вернулся в тот же альбом

  • WHEN оригинал с исходной контрольной суммой снова появился в альбоме устройства
  • THEN приложение загружает его в целевой серверный альбом

Scenario: Вернулся в другой альбом

  • WHEN содержимое с исходной контрольной суммой присутствует в другом альбоме устройства
  • THEN приложение загружает его в целевой серверный альбом

Scenario: Режим переноса

  • WHEN загрузка вернувшегося оригинала завершена в режиме переноса
  • THEN элемент снова проходит обычную разгрузку в превью

Requirement: Ход и устойчивость восстановления оригиналов

При возврате оригиналов на устройство приложение SHALL выполнять операцию до конца независимо от того, остаётся ли открыт экран альбома устройства, и SHALL сообщать ход: сколько элементов восстановлено из скольких намечено.

Scenario: Уход с экрана

  • WHEN восстановление оригиналов запущено и пользователь покидает экран альбома устройства
  • THEN восстановление продолжается и завершается

Scenario: Ход операции

  • WHEN восстановление оригиналов идёт
  • THEN приложение знает и сообщает, сколько элементов уже восстановлено из намеченных

Scenario: Нечего восстанавливать

  • WHEN среди элементов альбома нет превью с серверной копией
  • THEN операция не запускается и ход не показывается

Requirement: Материализация серверных фото на устройстве

При появлении фото в целевом серверном альбоме приложение SHALL материализовать его на устройстве, записав файл в галерею (MediaStore) бакета альбома устройства: в режиме копий — оригинал, в режиме переноса — превью, для видео — постер. Файл SHALL содержать метку происхождения и дату съёмки серверного фото.

Scenario: Материализация в режиме копий

  • WHEN в целевом серверном альбоме появилось новое фото, а альбом устройства в режиме копий
  • THEN приложение скачивает оригинал и создаёт файл в бакете альбома устройства

Scenario: Материализация в режиме переноса

  • WHEN в целевом серверном альбоме появилось новое фото, а альбом устройства в режиме переноса
  • THEN приложение скачивает превью и создаёт файл в бакете альбома устройства

Scenario: Материализация видео

  • WHEN в целевом серверном альбоме появилось видео
  • THEN приложение создаёт в бакете постер-кадр, а оригинал остаётся на сервере

Scenario: Дата съёмки

  • WHEN приложение материализует серверное фото
  • THEN файл получает дату съёмки из серверных метаданных фото

Requirement: Признак происхождения элемента

Каждый элемент альбома устройства SHALL иметь явный признак происхождения: загружено этим клиентом или скачано с сервера. Политика удалений, разгрузка и восстановление SHALL опираться на признак, а не на наличие серверной ссылки.

Scenario: Скачанный элемент не считается своей загрузкой

  • WHEN элемент скачан с сервера при двусторонней синхронизации
  • THEN он помечается как скачанный, а не как загруженный этим клиентом

Requirement: Двустороннее удаление элементов

Удаление элемента в альбоме устройства SHALL удалять соответствующее фото на сервере (мягко, в корзину), а серверное удаление SHALL убирать локальный элемент. Для материализованного (серверного) элемента намерением считается только явное удаление в приложении: пропавшая локальная копия SHALL NOT удалять серверное фото, а SHALL материализоваться заново. Операция SHALL подчиняться правам сервера, а удаление файла, снятого на устройстве, SHALL требовать системного согласия.

Scenario: Удаление на устройстве

  • WHEN пользователь удаляет элемент синхронизированного альбома устройства
  • THEN приложение помещает соответствующее серверное фото в корзину

Scenario: Удаление на сервере

  • WHEN серверное фото помещено в корзину
  • THEN приложение убирает соответствующий локальный элемент

Scenario: Пропала локальная копия серверного фото

  • WHEN локальная копия материализованного серверного фото исчезла (очистка галереи, перемещение файла)
  • THEN приложение материализует фото заново и SHALL NOT удалять его на сервере

Scenario: Явное удаление в приложении

  • WHEN пользователь удаляет материализованное серверное фото из приложения
  • THEN приложение помечает намерение и удаляет фото на сервере

Scenario: Нет прав на серверное удаление

  • WHEN пользователь удаляет элемент, а роль на сервере не позволяет удалить это фото
  • THEN серверное удаление отклоняется, приложение не считает операцию успешной и сообщает об этом

Requirement: Журнал удалений и защита от эха

Локально удалённый элемент SHALL помечаться в журнале удалений, чтобы приложение SHALL NOT материализовать его повторно до подтверждения серверного удаления. Повторные и эхо-события удаления SHALL быть идемпотентны.

Scenario: Удаление ещё не подтверждено сервером

  • WHEN элемент удалён на устройстве, а серверное удаление ещё не выполнено
  • THEN при следующем проходе приложение не материализует этот элемент заново

Scenario: Эхо серверного события

  • WHEN серверное событие удаления относится к уже удалённому локальному элементу
  • THEN приложение ничего не меняет повторно

Requirement: Восстановление элемента из корзины

Если серверное фото, удалённое с устройства, восстановлено из корзины сервера, приложение SHALL снова материализовать соответствующий элемент на устройстве.

Scenario: Фото вернулось из корзины

  • WHEN пользователь восстанавливает серверное фото из корзины
  • THEN приложение материализует соответствующий элемент в альбоме устройства

Requirement: Первый проход и ограничение скачивания

При включении синхронизации приложение SHALL предложить выбрать: скачать всё содержимое целевого серверного альбома или только новое с этого момента. Скачивание SHALL подчиняться ограничению «только Wi-Fi»; при ожидании подходящей сети материализация не начинается.

Scenario: Только новое

  • WHEN пользователь выбирает «только новое» при включении синхронизации
  • THEN уже существующее содержимое серверного альбома не скачивается, а материализуются только последующие появления

Scenario: Скачать всё

  • WHEN пользователь выбирает «скачать всё» при включении синхронизации
  • THEN приложение материализует весь текущий состав серверного альбома

Scenario: Ожидание WiFi

  • WHEN включено ограничение «только Wi-Fi», а доступна только сотовая сеть
  • THEN скачивание не начинается до появления Wi-Fi

Requirement: Материализация при нескольких источниках

Когда несколько альбомов устройства указывают на один серверный альбом, приложение SHALL материализовать серверное фото ровно в один из них, чтобы не создавать дубликат в галерее. Выбор целевого бакета SHALL сохраняться. Содержимое, уже присутствующее на устройстве, SHALL NOT дублироваться.

Scenario: Несколько источников в одну цель

  • WHEN два альбома устройства синхронизируются с одним серверным альбомом, и в нём появляется фото
  • THEN приложение создаёт файл ровно в одном из бакетов

Scenario: Содержимое уже на устройстве

  • WHEN появившееся серверное фото по содержимому уже присутствует на устройстве
  • THEN приложение связывает элемент с серверным фото, не создавая второй файл