11 KiB
11 KiB
Синхронизация статусов bank_profile и material
Определения
Локальные статусы (SQLite)
| Таблица | Поле | Значения |
|---|---|---|
bank_profiles |
status |
"active" | "off" |
materials |
status |
"active" | "off" (enum MaterialStatus::Active / MaterialStatus::Off) |
Серверные статусы (REST API)
| Сущность | Поле | Значения |
|---|---|---|
| bank_profile | state |
"enabled" | "disabled" |
| material | state |
"enabled" | "disabled" |
Маппинг
| Локально | Сервер |
|---|---|
active |
enabled |
off |
disabled |
Инвариант
Локальное состояние — единственный источник истины. Сервер всегда отражает локаль. При любых расхождениях побеждает локальная БД, и сервер дотягивается до неё (а не наоборот).
Pull с сервера (syncBankProfilesFromServer) используется только для создания новых записей (новые bank_profile / новые материалы) и привязки серверных UUID. Статусы существующих записей с сервера в локаль никогда не пишутся.
Основные правила
1. Смена онлайн-статуса устройства
- Device → OFFLINE: bank_profile этого устройства выключается и локально, и на сервере (
PATCH bank_profile/{id}/false). Материалы локально не трогаем — они остаются в том состоянии, в котором были (см. правило 4-А ниже). - Device → ONLINE: локальный статус не меняется. На сервер пушится текущее локальное состояние bank_profile и всех его материалов, если оно расходится с серверным.
2. Периодическая синхронизация
- Каждые 60 секунд запускается фоновая задача, которая сравнивает локальные статусы bank_profile и материалов с серверными и отправляет
PATCHтолько для расходящихся. - Локаль не трогается. Цель — компенсировать сетевые сбои, перезапуски сервера и т.п.
3. Обработка ошибок при включении
- Когда пользователь включает bank_profile или material и
PATCH .../trueпадает (network error / HTTP не-2xx /success:false):- локально оставляем
off; - показываем пользователю сообщение об ошибке (см. правило 6 про UI).
- локально оставляем
- При неудачном выключении локально оставляем
off; рассинхрон с сервером устранит минутный таймер из правила 2.
4. Реконсиляция материалов после включения профиля
- Срабатывает только при включении профиля (
PATCH bank_profile/{id}/true), при выключении — нет (см. правило 4-B про гард ниже). - Отдельной реконсиляции после PATCH не делаем — расхождения материалов подхватит 60s-таймер из правила 2 (в худшем случае — через минуту).
- Допустима оптимизация: «дёрнуть» периодический sync сразу после успешного включения профиля, чтобы не ждать.
Дополнительные правила и инварианты
4-A. Материалы при device → OFFLINE
- При переходе device → OFFLINE правило 1 выключает только профиль. Материалы локально не трогаем — после повторного включения профиля они вернутся к работе без лишних кликов пользователя.
- В UI материалы при
bank_profile.status == "off"отображаются приглушённо или с пометкой «профиль выключен», но ихstatusв БД остаётся как есть.
4-B. Гард на пуш материала при выключенном профиле
- На сервере действует инвариант:
material.state ≤ bank_profile.state. Если профильdisabled, сервер принудительно держит материалы вdisabled. - Чтобы не получить бесконечный diff-цикл, при любом пуше статуса материала (toggle в UI, периодический sync, follow-up после toggle профиля) проверяем локальный
bank_profile.status. Если профильoff—PATCHматериала не отправляем, локальный статус не меняем. - Этот гард уже частично реализован в
AccountSettingsWindow.cpp:556; нужно поднять его на уровеньNetworkService, чтобы покрыть все пути пуша.
5. Дебаунс ADB-флапа
- ADB может мерцать (USB-хаб, спящий экран, Doze). Без дебаунса каждое мерцание сработает как device → OFFLINE.
- Online — мгновенно (0 секунд). Как только устройство видно — сразу триггерим логику device → ONLINE.
- Offline — с задержкой 30 секунд. Устройство считается офлайн только после того, как ADB не показывает его как
deviceнепрерывно в течение 30 секунд. Если за этот период оно вернулось — считаем что флапа не было.
6. Стартовый порядок (избегание гонки)
DeviceScreenerэмитит сигналыdeviceWentOnline(deviceId)/deviceWentOffline(deviceId)только при смене статуса. На «никогда не видел в этой сессии» никаких событий нет.NetworkServiceстартует 60s-таймер только после первого успешного проходаDeviceScreener(сигналfirstPassDone). До этого никаких offline-эвентов не обрабатывает.- Это исключает ситуацию «на старте все устройства выглядят офлайн → массово выключаем профили».
7. Сериализация PATCH-ов
- Mutex на пару
(deviceId, bankProfileId): внутри одного устройства+профиля PATCH-операции последовательны, между разными — могут идти параллельно. - Защищает от гонки: одновременно прилетели device-event, юзер-toggle и периодический sync для одного профиля → они выстраиваются в очередь.
8. UI-обработка ошибок (overlay-pattern)
- При любом PATCH статуса (профиля или материала) UI показывает оверлей «Сохранение…» поверх тоггла.
- Тоггл двигается только после ответа сервера:
- успех → переходит в новое положение, оверлей скрывается;
- ошибка → остаётся в исходном положении, оверлей сменяется сообщением «Ошибка: …», локальный статус не меняется.
- Сейчас частично реализовано через
hideOverlayвAccountSettingsWindow.cpp:559. Применить тот же pattern вDeviceWidget,BankSetupWizardи всех других местах с toggle.
9. Индикация неработающей синхронизации
- В
NetworkServiceведём счётчикm_consecutiveSyncFailures, инкрементируем при каждом проваленном тике (правило 2) или провалеGET /current_profile. Сбрасываем при первом успехе. - При превышении порога (например, 3 тика подряд) эмитим сигнал в
MainWindow, который показывает индикатор «Нет связи с сервером, последняя успешная синхронизация: HH:MM:SS». - При следующем успехе индикатор скрывается.
10. Параллелизм и ограничения нагрузки
- В одном тике периодического sync может образоваться большой объём расхождений (5 устройств × 6 профилей × 4 материала = до 120 PATCH).
- Параллелизм с лимитом 4-8 одновременных PATCH; mutex из правила 7 соблюдается.
- Флаг
m_syncInProgress: если предыдущий тик ещё не завершился, новый тик пропускается. Это предотвращает накопление очереди.
11. Поведение при провале GET /current_profile
- Если периодический
GET /current_profileупал (таймаут/сеть/5xx) — тик пропускается тихо, локаль не трогаем, никакие PATCH не шлём. - Сам факт неудачи учитывается в счётчике из правила 9.
Связанные существующие правила (контекст)
- Toggle материала, когда
bank_profile.status == "off", не идёт на сервер — сохраняется только локально (см. правило 4-B). Серверная синхронизация произойдёт после включения профиля и следующего 60s-тика. - После
BankSetupWizardbank_profileостаётсяoffпо дизайну — пользователь должен включить его вручную.