diff --git a/Report/appendices/appendix-b.typ b/Report/appendices/appendix-b.typ index e5b7178..1aafe18 100644 --- a/Report/appendices/appendix-b.typ +++ b/Report/appendices/appendix-b.typ @@ -74,6 +74,17 @@ Wallenc — мобильное приложение для Android: `VaultsManag #pz-fig("fig_09_remote_vaults.jpg", [Экран удалённых vault], "fig-09-rp") #pz-fig("fig_10_yandex_oauth.jpg", [OAuth Яндекс], "fig-10-rp") +=== Секреты и 2FA внутри storage + +1. Откройте storage (после создания или из списка). +2. На экране storage перейдите в разделы «Секреты» и «2FA». + +#pz-fig("fig_33_storage_secrets_2fa.jpg", [Экран storage: секреты и 2FA], "fig-33-rp") + +3. В разделе 2FA добавьте TOTP-токен; на экране отображается сгенерированный код. + +#pz-fig("fig_34_2fa_single_token.jpg", [Экран 2FA с одним токеном], "fig-34-rp") + === Фоновые задачи На экране задач отображаются операции шифрования и синхронизации. Уведомления информируют о завершении (см. рис. 12 и 13 в гл. 5). diff --git a/Report/images/IMAGES_REGISTRY.md b/Report/images/IMAGES_REGISTRY.md index 4b2c08a..0cb8e88 100644 --- a/Report/images/IMAGES_REGISTRY.md +++ b/Report/images/IMAGES_REGISTRY.md @@ -1,39 +1,71 @@ # Реестр иллюстраций ПЗ Wallenc -Правило: файл в `Report/images/` только с именем из таблицы. Статус `ready` — можно вставлять в `.typ`. +Правила: -| № | Имя файла | Статус | Где используется | Typst label | -|---|-----------|--------|------------------|-------------| -| 1 | fig_01_start_sync.png | ready | 3.3.3, 4.1.4 | fig-01 | -| 2 | fig_02_vault_lifecycle.png | ready | 3.3.3, 4.1.4 | fig-02 | -| 3 | fig_03_navigation_hub.png | ready | 3.3.3, 4.1.4 | fig-03 | -| 4 | fig_04_domain_class.png | ready | 2.3.2, 4.2.1 | fig-04 | -| 5 | fig_05_local_vaults.jpg | placeholder | 3.4, 4.2.3, 5.2.2, прил. В | fig-05 | -| 6 | fig_06_encrypt_dialog.jpg | placeholder | 3.4, 4.1.1, 5.2.2, прил. В | fig-06 | -| 7 | fig_07_open_close_dialog.jpg | placeholder | 3.4, 4.1.2, прил. В | fig-07 | -| 8 | fig_08_rename_delete_dialog.jpg | placeholder | 3.4, 5.2.2, прил. В | fig-08 | -| 9 | fig_09_remote_vaults.jpg | placeholder | 3.4, 4.2.3, прил. В | fig-09 | -| 10 | fig_10_yandex_oauth.jpg | placeholder | 3.4, 4.2.3, 1.2.2.4, прил. В | fig-10 | -| 11 | fig_11_room_schema.png | ready | 2.3.2, 4.2.2, прил. В | fig-11 | -| 12 | fig_12_tasks_screen.jpg | placeholder | 5.2.3, прил. В | fig-12 | -| 13 | fig_13_tasks_notification.jpg | placeholder | 5.2.3, прил. В | fig-13 | -| 14 | fig_14_context_system.png | ready | 1.2.1, 2.1.4 | fig-14 | -| 15 | fig_15_bpmn_vault.png | ready | 2.1.3 | fig-15 | -| 16 | fig_16_dfd_level0.png | ready | 2.2 | fig-16 | -| 17 | fig_17_use_case.png | ready | 2.3.1 | fig-17 | -| 18 | fig_18_deployment.png | ready | 2.3.3 | fig-18 | -| 19 | fig_19_clean_architecture.png | ready | 2.3, 4.3 | fig-19 | -| 20 | fig_20_oauth_sequence.png | ready | 1.5.2, 4.2.3 | fig-20 | -| 21 | fig_21_encrypt_flow.png | ready | 4.1.1, 5.2.1 | fig-21 | -| 22 | fig_22_cjm_vault.png | ready | 3.3.2 | fig-22 | -| 23 | fig_23_module_deps.png | ready | 4.3 | fig-23 | -| 27 | fig_27_gradle_domain_test.png | ready | 5.2.1 | fig-27 | -| 28 | fig_28_gradle_usecases_test.png | ready | 5.2.2 | fig-28 | -| 29 | fig_29_gradle_ui_test.png | ready | 5.2.3 | fig-29 | -| 30 | fig_30_gradle_test_summary.png | ready | 5.4 | fig-30 | -| 31 | fig_31_gradle_connected_test.png | ready | 5.3 | fig-31 | -| 32 | fig_32_manual_test_checklist.png | ready | 5.3 | fig-32 | +- Файл в `Report/images/` — **только** имя из таблицы. +- Статус `ready` — файл на месте и подключён в `.typ`. +- Статус `placeholder` — файл ещё не подготовлен (сейчас все иллюстрации `ready`). +- Диаграммы PlantUML: `cd Report && bash scripts/render_puml.sh`. -Диаграммы fig_01–04, fig_11, fig_14–23: исходники `Report/puml/fig_*.puml` (`@startuml` = имя PNG), растр — `Report/scripts/render_puml.sh` сразу в `Report/images/` (без копирования). +## Соответствие «new images» → `images/` -Скриншоты UI (fig_05–fig_10, fig_12–13) и Gradle (fig_27–32) — заменить placeholder на реальные снимки перед защитой. +| Исходник в `new images/` | Целевой файл | +|--------------------------|--------------| +| `05.jpg` … `10.jpg`, `12.jpg`, `13.jpg` | `fig_05` … `fig_10`, `fig_12`, `fig_13` | +| `27-domain-test.png` … `31-gradle-connected.png` | `fig_27` … `fig_31` | +| `33-storage-screen.jpg` | `fig_33_storage_secrets_2fa.jpg` | +| `34-2fa-screen.jpg` | `fig_34_2fa_single_token.jpg` | + +--- + +## Полный реестр + +| № | Имя файла | Тип | Статус | Где в ПЗ | Примечание (содержание) | label | +|---|-----------|-----|--------|----------|-------------------------|-------| +| 01 | `fig_01_start_sync.png` | PlantUML | ready | гл. 3.3.3, 4.1.4 | Старт → Room → SyncWorker. **PUML** | fig-01 | +| 02 | `fig_02_vault_lifecycle.png` | PlantUML | ready | гл. 3.3.3, 4.1.4 | Жизненный цикл storage. **PUML** | fig-02 | +| 03 | `fig_03_navigation_hub.png` | PlantUML | ready | гл. 3.3.3, 4.1.4 | Навигация + SyncWorker. **PUML** | fig-03 | +| 04 | `fig_04_domain_class.png` | PlantUML | ready | гл. 2.3.2, 4.2.1 | Классы `:domain`. **PUML** | fig-04 | +| 05 | `fig_05_local_vaults.jpg` | Скриншот UI | ready | гл. 3.4, 5, прил. Б, В | Список storage (локальный vault) | fig-05 | +| 06 | `fig_06_encrypt_dialog.jpg` | Скриншот UI | ready | гл. 3.4, 4.1.1, 5, прил. Б, В | Диалог включения шифрования | fig-06 | +| 07 | `fig_07_open_close_dialog.jpg` | Скриншот UI | ready | гл. 3.4, 4.1.2, прил. Б, В | Открытие/закрытие storage | fig-07 | +| 08 | `fig_08_rename_delete_dialog.jpg` | Скриншот UI | ready | гл. 3.4, 5, прил. Б, В | Переименование или удаление | fig-08 | +| 09 | `fig_09_remote_vaults.jpg` | Скриншот UI | ready | гл. 3.4, 4.2.3, прил. Б, В | Удалённые vault | fig-09 | +| 10 | `fig_10_yandex_oauth.jpg` | Скриншот UI | ready | гл. 1, 3.4, 4.2.3, прил. Б, В | OAuth Яндекс | fig-10 | +| 11 | `fig_11_room_schema.png` | PlantUML | ready | гл. 2.3.2, 4.2.2, прил. В | Схема Room. **PUML** | fig-11 | +| 12 | `fig_12_tasks_screen.jpg` | Скриншот UI | ready | гл. 5.2.3, прил. В | Экран задач | fig-12 | +| 13 | `fig_13_tasks_notification.jpg` | Скриншот UI | ready | гл. 5.2.3, прил. В | Уведомление о задаче | fig-13 | +| 14 | `fig_14_context_system.png` | PlantUML | ready | гл. 1.2.1, 2.1.4 | Контекстная диаграмма. **PUML** | fig-14 | +| 15 | `fig_15_bpmn_vault.png` | PlantUML | ready | гл. 2.1.3 | BPMN. **PUML** | fig-15 | +| 16 | `fig_16_dfd_level0.png` | PlantUML | ready | гл. 2.2 | DFD-0. **PUML** | fig-16 | +| 17 | `fig_17_use_case.png` | PlantUML | ready | гл. 2.3.1 | Прецеденты. **PUML** | fig-17 | +| 18 | `fig_18_deployment.png` | PlantUML | ready | гл. 2.3.3 | Развёртывание. **PUML** | fig-18 | +| 19 | `fig_19_clean_architecture.png` | PlantUML | ready | гл. 2.3, 4.3 | Clean Architecture. **PUML** | fig-19 | +| 20 | `fig_20_oauth_sequence.png` | PlantUML | ready | гл. 1.5.2, 4.2.3 | OAuth sequence. **PUML** | fig-20 | +| 21 | `fig_21_encrypt_flow.png` | PlantUML | ready | гл. 4.1.1, 5.2.1 | Блок-схема шифрования. **PUML** | fig-21 | +| 22 | `fig_22_cjm_vault.png` | PlantUML | ready | гл. 3.3.2 | CJM. **PUML** | fig-22 | +| 23 | `fig_23_module_deps.png` | PlantUML | ready | гл. 4.3 | Зависимости Gradle. **PUML** | fig-23 | +| 27 | `fig_27_gradle_domain_test.png` | Gradle | ready | гл. 5.2.1 | `:domain:test` SUCCESS | fig-27 | +| 28 | `fig_28_gradle_usecases_test.png` | Gradle | ready | гл. 5.2.2 | `:usecases:test` SUCCESS | fig-28 | +| 29 | `fig_29_gradle_ui_test.png` | Gradle | ready | гл. 5.2.3 | `:ui:test` SUCCESS | fig-29 | +| 30 | `fig_30_gradle_test_summary.png` | Gradle | ready | гл. 5.4 | Сводка `test` | fig-30 | +| 31 | `fig_31_gradle_connected_test.png` | Gradle | ready | гл. 5.3 | `connectedDebugAndroidTest` SUCCESS | fig-31 | +| 32 | `fig_32_manual_test_checklist.png` | PlantUML Salt | ready | гл. 5.3 | Таблица-чек-лист T-7…T-12 (как @tbl-testres). **PUML** `fig_32_manual_test_checklist.puml` | fig-32 | +| 33 | `fig_33_storage_secrets_2fa.jpg` | Скриншот UI | ready | гл. 1, 3.4, 5, прил. Б, В | Экран storage: вкладки/разделы секретов и 2FA | fig-33 | +| 34 | `fig_34_2fa_single_token.jpg` | Скриншот UI | ready | гл. 1, 3.4, 5, прил. Б, В | Экран 2FA с одним TOTP-токеном | fig-34 | + +## Нумерация в PDF + +Номер «Рисунок N» — по порядку появления в тексте, не по номеру в имени файла. + +## fig_31 + +Один скрин `./gradlew connectedDebugAndroidTest` из корня репозитория, `BUILD SUCCESSFUL` (см. прежнее описание в истории реестра). + +## Команды + +```bash +cd Report && bash scripts/render_puml.sh +cd Report && python scripts/check_images.py +typst compile --root .. "Пояснительная_записка_ПытковРЕ.typ" +``` diff --git a/Report/images/fig_05_local_vaults.jpg b/Report/images/fig_05_local_vaults.jpg index f2b7de3..969d029 100644 Binary files a/Report/images/fig_05_local_vaults.jpg and b/Report/images/fig_05_local_vaults.jpg differ diff --git a/Report/images/fig_06_encrypt_dialog.jpg b/Report/images/fig_06_encrypt_dialog.jpg index 23a9558..95b8070 100644 Binary files a/Report/images/fig_06_encrypt_dialog.jpg and b/Report/images/fig_06_encrypt_dialog.jpg differ diff --git a/Report/images/fig_07_open_close_dialog.jpg b/Report/images/fig_07_open_close_dialog.jpg index bfd219f..7eaa4dc 100644 Binary files a/Report/images/fig_07_open_close_dialog.jpg and b/Report/images/fig_07_open_close_dialog.jpg differ diff --git a/Report/images/fig_08_rename_delete_dialog.jpg b/Report/images/fig_08_rename_delete_dialog.jpg index 201f75b..1fb62c5 100644 Binary files a/Report/images/fig_08_rename_delete_dialog.jpg and b/Report/images/fig_08_rename_delete_dialog.jpg differ diff --git a/Report/images/fig_09_remote_vaults.jpg b/Report/images/fig_09_remote_vaults.jpg index 7da6093..95f800b 100644 Binary files a/Report/images/fig_09_remote_vaults.jpg and b/Report/images/fig_09_remote_vaults.jpg differ diff --git a/Report/images/fig_10_yandex_oauth.jpg b/Report/images/fig_10_yandex_oauth.jpg index a3213cd..9b1ed8d 100644 Binary files a/Report/images/fig_10_yandex_oauth.jpg and b/Report/images/fig_10_yandex_oauth.jpg differ diff --git a/Report/images/fig_12_tasks_screen.jpg b/Report/images/fig_12_tasks_screen.jpg index 70403f8..7c8806d 100644 Binary files a/Report/images/fig_12_tasks_screen.jpg and b/Report/images/fig_12_tasks_screen.jpg differ diff --git a/Report/images/fig_13_tasks_notification.jpg b/Report/images/fig_13_tasks_notification.jpg index b235cbc..a67eb61 100644 Binary files a/Report/images/fig_13_tasks_notification.jpg and b/Report/images/fig_13_tasks_notification.jpg differ diff --git a/Report/images/fig_27_gradle_domain_test.png b/Report/images/fig_27_gradle_domain_test.png index b84fcfe..3ac3b13 100644 Binary files a/Report/images/fig_27_gradle_domain_test.png and b/Report/images/fig_27_gradle_domain_test.png differ diff --git a/Report/images/fig_28_gradle_usecases_test.png b/Report/images/fig_28_gradle_usecases_test.png index b7863e3..b4840aa 100644 Binary files a/Report/images/fig_28_gradle_usecases_test.png and b/Report/images/fig_28_gradle_usecases_test.png differ diff --git a/Report/images/fig_29_gradle_ui_test.png b/Report/images/fig_29_gradle_ui_test.png index f7b4e8d..3b0b938 100644 Binary files a/Report/images/fig_29_gradle_ui_test.png and b/Report/images/fig_29_gradle_ui_test.png differ diff --git a/Report/images/fig_30_gradle_test_summary.png b/Report/images/fig_30_gradle_test_summary.png index ac5a060..8d71cd4 100644 Binary files a/Report/images/fig_30_gradle_test_summary.png and b/Report/images/fig_30_gradle_test_summary.png differ diff --git a/Report/images/fig_31_gradle_connected_test.png b/Report/images/fig_31_gradle_connected_test.png index 747e9ad..3fe4f5b 100644 Binary files a/Report/images/fig_31_gradle_connected_test.png and b/Report/images/fig_31_gradle_connected_test.png differ diff --git a/Report/images/fig_32_manual_test_checklist.png b/Report/images/fig_32_manual_test_checklist.png index 5fdede5..77aead8 100644 Binary files a/Report/images/fig_32_manual_test_checklist.png and b/Report/images/fig_32_manual_test_checklist.png differ diff --git a/Report/images/fig_33_storage_secrets_2fa.jpg b/Report/images/fig_33_storage_secrets_2fa.jpg new file mode 100644 index 0000000..458c673 Binary files /dev/null and b/Report/images/fig_33_storage_secrets_2fa.jpg differ diff --git a/Report/images/fig_34_2fa_single_token.jpg b/Report/images/fig_34_2fa_single_token.jpg new file mode 100644 index 0000000..12ee601 Binary files /dev/null and b/Report/images/fig_34_2fa_single_token.jpg differ diff --git a/Report/includes/ch01.typ b/Report/includes/ch01.typ index cb44bf4..deae5df 100644 --- a/Report/includes/ch01.typ +++ b/Report/includes/ch01.typ @@ -48,7 +48,7 @@ ==== Работа с содержимым storage -Операции чтения и записи выполняются через единый интерфейс файлового доступа независимо от типа хранилища. +Операции чтения и записи выполняются через единый интерфейс файлового доступа независимо от типа хранилища. Внутри открытого storage доступны текстовые секреты и генерация TOTP для 2FA (рис. @fig-33, @fig-34). ==== Удалённые хранилища и авторизация во внешних провайдерах diff --git a/Report/includes/ch03.typ b/Report/includes/ch03.typ index 90b1eab..eabdca1 100644 --- a/Report/includes/ch03.typ +++ b/Report/includes/ch03.typ @@ -54,7 +54,7 @@ CJM сценария «защитить и открыть vault» предста == Проработка прототипа и особенности дизайна -Интерфейс реализован на Jetpack Compose @compose-docs. Экраны локальных и удалённых vault, диалоги шифрования и OAuth показаны на рис. @fig-05–@fig-10 (подробно — приложение В и руководство пользователя в приложении Б). +Интерфейс реализован на Jetpack Compose @compose-docs. Экраны локальных и удалённых vault, диалоги шифрования, OAuth, а также разделы текстовых секретов и 2FA внутри storage показаны на рис. @fig-05–@fig-10 и @fig-33–@fig-34 (подробно — приложение В и руководство пользователя в приложении Б). #pz-fig("fig_05_local_vaults.jpg", [Список storage в локальном vault (экран «локальные vault»)], "fig-05") #pz-fig("fig_06_encrypt_dialog.jpg", [Диалог включения шифрования], "fig-06") @@ -62,6 +62,8 @@ CJM сценария «защитить и открыть vault» предста #pz-fig("fig_08_rename_delete_dialog.jpg", [Диалог переименования и удаления], "fig-08") #pz-fig("fig_09_remote_vaults.jpg", [Экран удалённых vault], "fig-09") #pz-fig("fig_10_yandex_oauth.jpg", [Добавление удалённого vault, OAuth Яндекс], "fig-10") +#pz-fig("fig_33_storage_secrets_2fa.jpg", [Экран storage: разделы «Секреты» и «2FA»], "fig-33") +#pz-fig("fig_34_2fa_single_token.jpg", [Экран 2FA: список с одним TOTP-токеном], "fig-34") == Требования к эргономике и доступности diff --git a/Report/includes/ch05.typ b/Report/includes/ch05.typ index 2d7ac22..8073fd9 100644 --- a/Report/includes/ch05.typ +++ b/Report/includes/ch05.typ @@ -46,7 +46,7 @@ [T-9], [Открытие/закрытие vault], [Ручной], [Нет], [Доступ только с ключом (рис. @fig-07)], [T-10], [OAuth Яндекс], [Ручной / IT], [Частично], [Токен в Room (рис. @fig-10)], [T-11], [Экран задач и уведомления], [Ручной], [Частично], [Прогресс и завершение (рис. 12–13)], - [T-12], [Compose: секреты и 2FA], [IT], [Да], [Отображение без падений], + [T-12], [Compose: секреты и 2FA], [IT], [Да], [Отображение без падений (рис. @fig-33–@fig-34)], ) === Критерии начала и окончания @@ -113,7 +113,7 @@ [:app], [YandexDiskLiveIntegrationTest], [Живой API (при наличии токена)], [3], ) -Запуск: `./gradlew connectedDebugAndroidTest`. Результат — рис. @fig-31. +Запуск: `./gradlew connectedDebugAndroidTest`. Результат — рис. @fig-31. Отрисовка экранов секретов и 2FA подтверждена скриншотами @fig-33–@fig-34. #pz-fig("fig_31_gradle_connected_test.png", [Gradle connectedDebugAndroidTest], "fig-31") diff --git a/Report/puml/fig_32_manual_test_checklist.puml b/Report/puml/fig_32_manual_test_checklist.puml new file mode 100644 index 0000000..2442725 --- /dev/null +++ b/Report/puml/fig_32_manual_test_checklist.puml @@ -0,0 +1,15 @@ +@startsalt fig_32_manual_test_checklist +{# +Чек-лист ручного UI-тестирования Wallenc +. +ID | Шаг | Статус | Фактический результат | Рис. +T-7 | Создать storage в LocalVault | OK | Storage в списке | 5 +T-8 | Включить шифрование storage | OK | Статус encrypted | 6 +T-9 | Открыть / закрыть storage | OK | Контент при открытом storage | 7 +T-10 | OAuth Яндекс | OK | Запись в DbYandexAccount | 10 +T-11 | Фоновая задача шифрования | OK | Прогресс на экране задач | 12 +T-12 | Уведомление о завершении | OK | Notification отображён | 13 +. +Платформа: Android (эмулятор / устройство), 2026 +#} +@endsalt diff --git a/Report/scripts/check_images.py b/Report/scripts/check_images.py index 59a427d..b97c171 100755 --- a/Report/scripts/check_images.py +++ b/Report/scripts/check_images.py @@ -6,11 +6,14 @@ import re import sys from pathlib import Path -REQUIRED_READY = { - f"fig_{i:02d}_" for i in range(1, 5) -} | {f"fig_11_"} | {f"fig_{i:02d}_" for i in range(14, 24)} +REQUIRED_READY = ( + {f"fig_{i:02d}_" for i in range(1, 5)} + | {f"fig_11_"} + | {f"fig_{i:02d}_" for i in range(14, 24)} + | {f"fig_32_"} +) -WARN_PLACEHOLDER = {f"fig_{i:02d}_" for i in range(5, 11)} | {f"fig_12_"} | {f"fig_13_"} +WARN_PLACEHOLDER: set[str] = set() def main() -> int: diff --git a/Report/Пояснительная_записка_ПытковРЕ.typ b/Report/Пояснительная_записка_ПытковРЕ.typ index 58521f3..f01828c 100644 --- a/Report/Пояснительная_записка_ПытковРЕ.typ +++ b/Report/Пояснительная_записка_ПытковРЕ.typ @@ -72,3 +72,5 @@ #pz-fig("fig_11_room_schema.png", [Схема Room], "fig-11-app") #pz-fig("fig_12_tasks_screen.jpg", [Экран задач], "fig-12-app") #pz-fig("fig_13_tasks_notification.jpg", [Уведомление о задачах], "fig-13-app") +#pz-fig("fig_33_storage_secrets_2fa.jpg", [Экран storage: секреты и 2FA], "fig-33-app") +#pz-fig("fig_34_2fa_single_token.jpg", [Экран 2FA с токеном], "fig-34-app") diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 921153c..06335ba 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -28,10 +28,6 @@ android { testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunnerArguments["yandex.oauth.token"] = localProps.getProperty("yandex.test.oauth.token").orEmpty() - testInstrumentationRunnerArguments["yandex.user.id"] = - localProps.getProperty("yandex.test.user.id").orEmpty() - testInstrumentationRunnerArguments["yandex.vault.uuid"] = - localProps.getProperty("yandex.test.vault.uuid").orEmpty() vectorDrawables { useSupportLibrary = true } diff --git a/app/src/androidTest/java/com/github/nullptroma/wallenc/app/integration/yandex/ExportYandexTestCredentialsTest.kt b/app/src/androidTest/java/com/github/nullptroma/wallenc/app/integration/yandex/ExportYandexTestCredentialsTest.kt deleted file mode 100644 index c1a134d..0000000 --- a/app/src/androidTest/java/com/github/nullptroma/wallenc/app/integration/yandex/ExportYandexTestCredentialsTest.kt +++ /dev/null @@ -1,53 +0,0 @@ -package com.github.nullptroma.wallenc.app.integration.yandex - -import android.util.Log -import androidx.room.Room -import androidx.test.core.app.ApplicationProvider -import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.github.nullptroma.wallenc.infrastructure.android.db.app.AppDb -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.runBlocking -import org.junit.Ignore -import org.junit.Test -import org.junit.runner.RunWith - -/** - * Ручной helper: после входа в Yandex через приложение печатает в Logcat маскированный - * токен и подсказку для local.properties. Запускать вручную из Android Studio. - */ -@RunWith(AndroidJUnit4::class) -@Ignore("Manual: export Yandex credentials to local.properties") -class ExportYandexTestCredentialsTest { - - @Test - fun printFirstAccountCredentialsToLogcat() { - val context = ApplicationProvider.getApplicationContext() - val db = Room.databaseBuilder(context, AppDb::class.java, "wallenc.db") - .build() - try { - val row = runBlocking { - db.yandexAccountDao.observeAll().first().firstOrNull() - } - if (row == null) { - Log.i(TAG, "No Yandex accounts in DB. Link a vault in the app first.") - return - } - Log.i(TAG, "Add to local.properties:") - Log.i(TAG, "yandex.test.oauth.token=") - Log.i(TAG, "yandex.test.oauth.token.prefix=${maskToken(row.oauthToken)}") - Log.i(TAG, "yandex.test.user.id=${row.yandexUserId}") - Log.i(TAG, "yandex.test.vault.uuid=${row.vaultUuid}") - } finally { - db.close() - } - } - - private fun maskToken(token: String): String { - if (token.length <= 8) return "***" - return token.take(4) + "…" + token.takeLast(4) - } - - companion object { - private const val TAG = "WallencYandexExport" - } -} diff --git a/app/src/androidTest/java/com/github/nullptroma/wallenc/app/integration/yandex/YandexDiskLiveIntegrationTest.kt b/app/src/androidTest/java/com/github/nullptroma/wallenc/app/integration/yandex/YandexDiskLiveIntegrationTest.kt index a117d33..b1e9fa5 100644 --- a/app/src/androidTest/java/com/github/nullptroma/wallenc/app/integration/yandex/YandexDiskLiveIntegrationTest.kt +++ b/app/src/androidTest/java/com/github/nullptroma/wallenc/app/integration/yandex/YandexDiskLiveIntegrationTest.kt @@ -12,11 +12,15 @@ import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 +/** + * Live-прогон Disk API. Пути только `app:/` — как в [YandexVault]; токен из Auth SDK + * обычно имеет `cloud_api:disk.app_folder`, без полного доступа к `disk:/` (там 403). + */ @RunWith(JUnit4::class) class YandexDiskLiveIntegrationTest { private lateinit var repository: YandexDiskRepository - private val testFolder = "disk:/wallenc-integration-test" + private val testFolder = "app:/wallenc-integration-test" private val probeFileName = "wallenc-probe.txt" private val probePath = "$testFolder/$probeFileName" private val probePayload = "wallenc-integration-probe".encodeToByteArray() diff --git a/app/src/main/java/com/github/nullptroma/wallenc/app/sync/StorageSyncBootstrap.kt b/app/src/main/java/com/github/nullptroma/wallenc/app/sync/StorageSyncBootstrap.kt index 0cf310d..dfffccd 100644 --- a/app/src/main/java/com/github/nullptroma/wallenc/app/sync/StorageSyncBootstrap.kt +++ b/app/src/main/java/com/github/nullptroma/wallenc/app/sync/StorageSyncBootstrap.kt @@ -1,11 +1,9 @@ package com.github.nullptroma.wallenc.app.sync import com.github.nullptroma.wallenc.domain.datatypes.StorageSyncPaths -import com.github.nullptroma.wallenc.domain.interfaces.IStorageSyncGroupStore import com.github.nullptroma.wallenc.domain.interfaces.IVaultsManager import com.github.nullptroma.wallenc.domain.tasks.StorageSyncTriggerReason import com.github.nullptroma.wallenc.usecases.RunStorageSyncUseCase -import com.github.nullptroma.wallenc.usecases.StorageSyncReadiness import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.FlowPreview @@ -13,13 +11,10 @@ import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.debounce -import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge import kotlinx.coroutines.launch -import java.util.concurrent.atomic.AtomicBoolean import javax.inject.Inject import javax.inject.Singleton @@ -29,15 +24,11 @@ class StorageSyncBootstrap @Inject constructor( private val scheduler: StorageSyncScheduler, private val vaultsManager: IVaultsManager, private val syncRunner: RunStorageSyncUseCase, - private val syncReadiness: StorageSyncReadiness, - private val groupStore: IStorageSyncGroupStore, ) { private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO) - private val startupSyncScheduled = AtomicBoolean(false) fun start() { scheduler.ensureScheduled() - scheduleStartupSyncOnce() scope.launch { combine( vaultsManager.allStorages, @@ -86,31 +77,4 @@ class StorageSyncBootstrap @Inject constructor( } return System.currentTimeMillis() >= syncRunner.debounceSuppressUntilMs.value } - - /** - * Одна синхронизация после готовности хранилищ при старте процесса — не ждать только WorkManager - * (особенно если periodic work откладывался из‑за перезапусков процесса). - */ - private fun scheduleStartupSyncOnce() { - scope.launch { - combine( - vaultsManager.allStorages, - vaultsManager.unlockManager.openedStorages, - ) { rootStorages, opened -> - (rootStorages + opened.values).distinctBy { it.uuid } - } - .map { it.isNotEmpty() } - .distinctUntilChanged() - .filter { it } - .first() - if (!startupSyncScheduled.compareAndSet(false, true)) { - return@launch - } - if (groupStore.getGroups().isEmpty()) { - return@launch - } - syncReadiness.awaitReady() - syncRunner.enqueue(StorageSyncTriggerReason.Background) - } - } } diff --git a/usecases/src/main/java/com/github/nullptroma/wallenc/usecases/StorageSyncEngine.kt b/usecases/src/main/java/com/github/nullptroma/wallenc/usecases/StorageSyncEngine.kt index 797fb4f..7f77ae8 100644 --- a/usecases/src/main/java/com/github/nullptroma/wallenc/usecases/StorageSyncEngine.kt +++ b/usecases/src/main/java/com/github/nullptroma/wallenc/usecases/StorageSyncEngine.kt @@ -11,6 +11,7 @@ import com.github.nullptroma.wallenc.domain.interfaces.IStorageSyncEngine import com.github.nullptroma.wallenc.domain.interfaces.IStorageSyncGroupStore import com.github.nullptroma.wallenc.domain.interfaces.IVaultsManager import com.github.nullptroma.wallenc.domain.tasks.TaskProgressLabel +import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll @@ -316,13 +317,15 @@ class StorageSyncEngine @Inject constructor( } } } - result.exceptionOrNull()?.let { error -> - System.err.println( - "StorageSyncEngine: apply ${entry.operation} ${entry.path} " + - "target=${target.uuid}: ${error.message}", - ) + val error = result.exceptionOrNull() ?: return result.isSuccess + if (error is CancellationException) { + throw error } - return result.isSuccess + System.err.println( + "StorageSyncEngine: apply ${entry.operation} ${entry.path} " + + "target=${target.uuid}: ${error.message}", + ) + return false } private fun compareEntries(a: StorageSyncJournalEntry, b: StorageSyncJournalEntry): Int {