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.
This commit is contained in:
iu
2026-10-16 23:20:04 +03:00
parent d17f62f676
commit cc85d792ea
8 changed files with 214 additions and 35 deletions
+28 -1
View File
@@ -110,7 +110,8 @@ Android-клиент для self-hosted galo: настройка подключ
Приложение SHALL открывать альбом устройства с сеткой медиа и SHALL предоставлять Приложение SHALL открывать альбом устройства с сеткой медиа и SHALL предоставлять
настройки синхронизации этого альбома по свайпу вправо. Настройки SHALL содержать настройки синхронизации этого альбома по свайпу вправо. Настройки SHALL содержать
только выбор режима синхронизации. только выбор режима синхронизации. Сетка SHALL включать как локальные элементы, так и
скачанные с сервера при двусторонней синхронизации.
#### Scenario: Открытие альбома устройства #### Scenario: Открытие альбома устройства
@@ -132,6 +133,11 @@ Android-клиент для self-hosted galo: настройка подключ
- **WHEN** серверной копии разгруженного элемента больше нет в целевом альбоме - **WHEN** серверной копии разгруженного элемента больше нет в целевом альбоме
- **THEN** в сетке на элементе показывается перечёркнутый значок облака - **THEN** в сетке на элементе показывается перечёркнутый значок облака
#### Scenario: Скачанный элемент в сетке
- **WHEN** серверное фото материализовано на устройстве
- **THEN** элемент показывается в сетке альбома устройства наряду с локальными
### Requirement: Доступ к альбомам устройства ### Requirement: Доступ к альбомам устройства
Приложение SHALL запрашивать доступ к медиа устройства, нужный для перечисления Приложение SHALL запрашивать доступ к медиа устройства, нужный для перечисления
@@ -734,3 +740,24 @@ SHALL показывать значками расположение содер
- **WHEN** восстановление оригиналов завершилось - **WHEN** восстановление оригиналов завершилось
- **THEN** ход больше не показывается, а карточка возвращается к метке режима - **THEN** ход больше не показывается, а карточка возвращается к метке режима
### Requirement: Серверный состав в сетке альбома устройства
Сетка альбома устройства SHALL отражать состав целевого серверного альбома: новые
серверные фото SHALL появляться, а удалённые SHALL исчезать по мере синхронизации, без
ручного обновления.
#### Scenario: Новое фото с сервера
- **WHEN** в целевом серверном альбоме появилось новое фото, и синхронизация прошла
- **THEN** материализованный элемент появляется в сетке альбома устройства
#### Scenario: Фото удалено на сервере
- **WHEN** серверное фото удалено на сервере, и синхронизация прошла
- **THEN** соответствующий элемент исчезает из сетки альбома устройства
#### Scenario: Обновление открытого экрана
- **WHEN** экран альбома устройства открыт, и синхронизация изменила состав
- **THEN** сетка обновляется без ручного действия пользователя
+186 -34
View File
@@ -108,50 +108,42 @@ API, а изменение содержимого SHALL трактовать к
- **WHEN** содержимое ассета уже присутствует на сервере (в том числе загружено ранее) - **WHEN** содержимое ассета уже присутствует на сервере (в том числе загружено ранее)
- **THEN** приложение связывает ассет с существующим серверным фото без загрузки - **THEN** приложение связывает ассет с существующим серверным фото без загрузки
### Requirement: Удаление только собственных загрузок
При удалении содержимого на устройстве приложение SHALL удалять на сервере только
фото, загруженные этим клиентом, и SHALL сохранять остальные фото целевого альбома,
в том числе загруженные из других источников.
#### Scenario: Собственная загрузка
- **WHEN** удалено содержимое, которое загрузил этот клиент, и его контрольная сумма отсутствует во всех локальных альбомах, льющихся в этот серверный
- **THEN** приложение удаляет соответствующее серверное фото
#### Scenario: Чужие фото
- **WHEN** в целевом серверном альбоме есть фото, загруженные из других источников
- **THEN** приложение не удаляет их
### Requirement: Отражение серверных изменений на устройстве ### Requirement: Отражение серверных изменений на устройстве
Приложение SHALL отражать изменения целевого серверного альбома — новые фото, Приложение SHALL приводить локальное представление целевого серверного альбома в
переименования, теги, подписи и удаления — в локальном представлении, SHALL NOT соответствие с его составом: новое серверное фото SHALL материализоваться на устройстве
изменять или удалять оригиналы на устройстве. (оригинал в режиме копий, превью в режиме переноса), переименования, теги и подписи SHALL
отражаться, а серверное удаление SHALL убирать локальный элемент. Удаление снятого на
устройстве оригинала SHALL учитывать права сервера и системное согласие.
#### Scenario: Новое с другого устройства #### Scenario: Новое с другого устройства
- **WHEN** другое устройство добавляет фото в целевой серверный альбом - **WHEN** другое устройство добавляет фото в целевой серверный альбом
- **THEN** приложение показывает его в серверном представлении альбома - **THEN** приложение материализует его в альбоме устройства — оригинал в режиме копий, превью в режиме переноса
#### Scenario: Удалено на сервере #### Scenario: Удалено на сервере
- **WHEN** серверное фото удалено на сервере - **WHEN** серверное фото удалено на сервере
- **THEN** приложение убирает его из локального представления, не трогая оригинал на устройстве - **THEN** приложение убирает соответствующий локальный элемент, а для снятого на устройстве оригинала запрашивается системное согласие на удаление
### Requirement: Разгрузка оригиналов в режиме переноса ### Requirement: Разгрузка оригиналов в режиме переноса
В режиме C после успешной загрузки приложение SHALL автоматически заменять оригинал В режиме C после того, как оригинал элемента оказывается на сервере (загружен этим
на устройстве превью, без отдельного действия на экране; видео SHALL заменяться клиентом или скачан при двусторонней синхронизации), приложение SHALL автоматически
постером-кадром. При включении режима C приложение SHALL запрашивать широкий доступ заменять оригинал на устройстве превью, без отдельного действия на экране; видео SHALL
к файлам, чтобы замена выполнялась без системного диалога согласия. заменяться постером-кадром. При включении режима C приложение SHALL запрашивать широкий
доступ к файлам, чтобы замена выполнялась без системного диалога согласия.
#### Scenario: Разгрузка фото #### Scenario: Разгрузка фото
- **WHEN** оригинал фото успешно загружен - **WHEN** оригинал фото успешно загружен
- **THEN** файл в галерее автоматически заменяется превью с сохранением даты съёмки и места в альбоме - **THEN** файл в галерее автоматически заменяется превью с сохранением даты съёмки и места в альбоме
#### Scenario: Разгрузка скачанного оригинала
- **WHEN** альбом переключён в режим переноса, а оригинал элемента был скачан с сервера
- **THEN** оригинал автоматически заменяется превью
#### Scenario: Разгрузка видео #### Scenario: Разгрузка видео
- **WHEN** оригинал видео успешно загружен - **WHEN** оригинал видео успешно загружен
@@ -164,8 +156,9 @@ API, а изменение содержимого SHALL трактовать к
### Requirement: Провенанс разгруженных превью ### Requirement: Провенанс разгруженных превью
Разгруженные превью и постеры видео SHALL содержать метаданные провенанса, Разгруженные превью, постеры видео и скачанные с сервера элементы SHALL содержать
достаточные для восстановления связи с серверным альбомом и принадлежности метаданные провенанса, включая признак происхождения (загружено клиентом или скачано с
сервера), достаточные для восстановления связи с серверным альбомом и принадлежности
загрузок после переустановки приложения. загрузок после переустановки приложения.
#### Scenario: Восстановление после переустановки #### Scenario: Восстановление после переустановки
@@ -173,6 +166,11 @@ API, а изменение содержимого SHALL трактовать к
- **WHEN** приложение переустановлено или его данные очищены, а альбом устройства содержит разгруженные превью - **WHEN** приложение переустановлено или его данные очищены, а альбом устройства содержит разгруженные превью
- **THEN** приложение восстанавливает связь с серверным альбомом и принадлежность своих загрузок из метаданных превью - **THEN** приложение восстанавливает связь с серверным альбомом и принадлежность своих загрузок из метаданных превью
#### Scenario: Метка скачанного элемента
- **WHEN** метка провенанса восстанавливается у элемента, скачанного с сервера
- **THEN** элемент восстанавливается как скачанный, а не как собственная загрузка клиента
#### Scenario: Метка утрачена #### Scenario: Метка утрачена
- **WHEN** метаданные провенанса недоступны (например, файл перезаписан сторонним редактором) - **WHEN** метаданные провенанса недоступны (например, файл перезаписан сторонним редактором)
@@ -182,7 +180,10 @@ API, а изменение содержимого SHALL трактовать к
При выходе из режима C приложение SHALL предложить вернуть оригиналы на устройство При выходе из режима C приложение SHALL предложить вернуть оригиналы на устройство
и SHALL предупредить, если пользователь отказывается. Отвязка альбома и выключение и SHALL предупредить, если пользователь отказывается. Отвязка альбома и выключение
синхронизации SHALL NOT удалять фото с сервера. синхронизации SHALL NOT удалять фото с сервера, а скачанные с сервера файлы SHALL
оставаться в галерее как обычные. Выключение синхронизации SHALL быть явным и
устойчивым: восстановление происхождения SHALL NOT возобновлять связь, пока
пользователь не включит синхронизацию снова.
#### Scenario: Возврат оригиналов #### Scenario: Возврат оригиналов
@@ -197,29 +198,44 @@ API, а изменение содержимого SHALL трактовать к
#### Scenario: Отвязка #### Scenario: Отвязка
- **WHEN** пользователь отвязывает альбом устройства от серверного - **WHEN** пользователь отвязывает альбом устройства от серверного
- **THEN** фото на сервере сохраняются - **THEN** фото на сервере сохраняются, а скачанные файлы остаются в галерее как обычные
#### Scenario: Выключение синхронизации #### Scenario: Выключение синхронизации
- **WHEN** пользователь выключает синхронизацию альбома устройства - **WHEN** пользователь выключает синхронизацию альбома устройства
- **THEN** связь с серверным альбомом снимается, а фото на сервере сохраняются - **THEN** связь с серверным альбомом снимается, а фото на сервере сохраняются
#### Scenario: Выключение не отменяется восстановлением
- **WHEN** пользователь выключил синхронизацию (или отвязал альбом), и затем выполняется проверка с восстановлением происхождения по меткам превью
- **THEN** связь не восстанавливается, а альбом остаётся локальным с выбранным режимом выключено
#### Scenario: Повторное включение
- **WHEN** пользователь снова включает синхронизацию после выключения
- **THEN** связь создаётся заново, а восстановление происхождения снова действует по обычным правилам
### Requirement: Автоматическая фоновая синхронизация ### Requirement: Автоматическая фоновая синхронизация
Синхронизация SHALL выполняться автоматически: при открытом приложении — по Синхронизация SHALL выполняться автоматически в обе стороны: при открытом приложении —
событиям изменения, в фоне — периодически, с учётом ограничений сети, выбранных по событиям изменения, в фоне — периодически, с учётом ограничений сети, выбранных
пользователем. В режиме C разгрузка оригиналов SHALL выполняться автоматически пользователем. В режиме C разгрузка оригиналов SHALL выполняться автоматически после
после успешной загрузки. того, как копия оказалась на сервере.
#### Scenario: Фоновая синхронизация #### Scenario: Фоновая синхронизация
- **WHEN** приложение не открыто, а в альбоме устройства появились изменения - **WHEN** приложение не открыто, а в альбоме устройства появились изменения
- **THEN** синхронизация выполняется в фоне при ближайшей возможности - **THEN** синхронизация выполняется в фоне при ближайшей возможности
#### Scenario: Фоновая загрузка серверных фото
- **WHEN** приложение не открыто, а в целевом серверном альбоме появилось новое фото
- **THEN** приложение материализует его на устройстве при ближайшей возможности
#### Scenario: Ограничение сети #### Scenario: Ограничение сети
- **WHEN** включено ограничение «только Wi-Fi», а доступна только сотовая сеть - **WHEN** включено ограничение «только Wi-Fi», а доступна только сотовая сеть
- **THEN** синхронизация не начинается до появления Wi-Fi - **THEN** синхронизация, включая скачивание серверных фото, не начинается до появления Wi-Fi
#### Scenario: Автоматическая разгрузка #### Scenario: Автоматическая разгрузка
@@ -325,3 +341,139 @@ API, а изменение содержимого SHALL трактовать к
- **WHEN** среди элементов альбома нет превью с серверной копией - **WHEN** среди элементов альбома нет превью с серверной копией
- **THEN** операция не запускается и ход не показывается - **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** приложение связывает элемент с серверным фото, не создавая второй файл