Все картинки в отчёте

This commit is contained in:
2026-05-26 23:16:40 +03:00
parent ce70c13f86
commit 3673c4aa8d
29 changed files with 121 additions and 142 deletions

View File

@@ -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).

View File

@@ -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_0104, fig_11, fig_1423: исходники `Report/puml/fig_*.puml` (`@startuml` = имя PNG), растр — `Report/scripts/render_puml.sh` сразу в `Report/images/` (без копирования).
## Соответствие «new images» → `images/`
Скриншоты UI (fig_05fig_10, fig_1213) и Gradle (fig_2732) — заменить 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"
```

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 221 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 222 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 237 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 294 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 399 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 278 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 424 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 407 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 427 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

View File

@@ -48,7 +48,7 @@
==== Работа с содержимым storage
Операции чтения и записи выполняются через единый интерфейс файлового доступа независимо от типа хранилища.
Операции чтения и записи выполняются через единый интерфейс файлового доступа независимо от типа хранилища. Внутри открытого storage доступны текстовые секреты и генерация TOTP для 2FA (рис. @fig-33, @fig-34).
==== Удалённые хранилища и авторизация во внешних провайдерах

View File

@@ -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")
== Требования к эргономике и доступности

View File

@@ -46,7 +46,7 @@
[T-9], [Открытие/закрытие vault], [Ручной], [Нет], [Доступ только с ключом (рис. @fig-07)],
[T-10], [OAuth Яндекс], [Ручной / IT], [Частично], [Токен в Room (рис. @fig-10)],
[T-11], [Экран задач и уведомления], [Ручной], [Частично], [Прогресс и завершение (рис. 1213)],
[T-12], [Compose: секреты и 2FA], [IT], [Да], [Отображение без падений],
[T-12], [Compose: секреты и 2FA], [IT], [Да], [Отображение без падений (рис. @fig-33@fig-34)],
) <tbl-testplan>
=== Критерии начала и окончания
@@ -113,7 +113,7 @@
[:app], [YandexDiskLiveIntegrationTest], [Живой API (при наличии токена)], [3],
) <tbl-androidtest>
Запуск: `./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")

View File

@@ -0,0 +1,15 @@
@startsalt fig_32_manual_test_checklist
{#
<b>Чек-лист ручного UI-тестирования Wallenc</b>
.
<b>ID</b> | <b>Шаг</b> | <b>Статус</b> | <b>Фактический результат</b> | <b>Рис.</b>
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

View File

@@ -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:

View File

@@ -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")

View File

@@ -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
}

View File

@@ -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<android.content.Context>()
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=<copy full token from app debug storage if needed>")
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"
}
}

View File

@@ -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()

View File

@@ -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)
}
}
}

View File

@@ -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 ->
val error = result.exceptionOrNull() ?: return result.isSuccess
if (error is CancellationException) {
throw error
}
System.err.println(
"StorageSyncEngine: apply ${entry.operation} ${entry.path} " +
"target=${target.uuid}: ${error.message}",
)
}
return result.isSuccess
return false
}
private fun compareEntries(a: StorageSyncJournalEntry, b: StorageSyncJournalEntry): Int {