Files
ds-galo/openspec/specs/phone-album-sync/spec.md
T
iu cc85d792ea chore(openspec): архивировать phone-album-two-way-sync и синхронизировать спеки
Дельта-спеки перенесены в основные: phone-album-sync (+8 требований, 6 изменено,
1 удалено — «Удаление только собственных загрузок»), android-client.

Change перемещён в openspec/changes/archive/2026-10-16-phone-album-two-way-sync.
2026-10-16 23:20:04 +03:00

480 lines
34 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 трактовать как новую загрузку с удалением
устаревшей собственной загрузки.
#### 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 содержать
метаданные провенанса, включая признак происхождения (загружено клиентом или скачано с
сервера), достаточные для восстановления связи с серверным альбомом и принадлежности
загрузок после переустановки приложения.
#### Scenario: Восстановление после переустановки
- **WHEN** приложение переустановлено или его данные очищены, а альбом устройства содержит разгруженные превью
- **THEN** приложение восстанавливает связь с серверным альбомом и принадлежность своих загрузок из метаданных превью
#### Scenario: Метка скачанного элемента
- **WHEN** метка провенанса восстанавливается у элемента, скачанного с сервера
- **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** связь создаётся заново, а восстановление происхождения снова действует по обычным правилам
### 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** приложение связывает элемент с серверным фото, не создавая второй файл