Улучшена фоновая синхронизация, обработана ошибка

This commit is contained in:
2026-05-23 23:36:35 +03:00
parent 6e719e7f52
commit adc3730b8d
13 changed files with 284 additions and 97 deletions

View File

@@ -3,6 +3,7 @@
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
@@ -72,6 +73,14 @@
android:exported="false"
android:foregroundServiceType="dataSync"
android:description="@string/fgs_task_pipeline_description" />
<receiver
android:name=".sync.StorageSyncBootReceiver"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
</application>
</manifest>

View File

@@ -0,0 +1,12 @@
package com.github.nullptroma.wallenc.app.di
import com.github.nullptroma.wallenc.app.sync.StorageSyncScheduler
import dagger.hilt.EntryPoint
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
@EntryPoint
@InstallIn(SingletonComponent::class)
interface StorageSyncBootEntryPoint {
fun storageSyncScheduler(): StorageSyncScheduler
}

View File

@@ -0,0 +1,22 @@
package com.github.nullptroma.wallenc.app.sync
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import com.github.nullptroma.wallenc.app.di.StorageSyncBootEntryPoint
import dagger.hilt.android.EntryPointAccessors
import timber.log.Timber
class StorageSyncBootReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent?) {
if (intent?.action != Intent.ACTION_BOOT_COMPLETED) {
return
}
val scheduler = EntryPointAccessors.fromApplication(
context.applicationContext,
StorageSyncBootEntryPoint::class.java,
).storageSyncScheduler()
scheduler.ensureScheduled()
Timber.d("Rescheduled periodic storage sync after boot")
}
}

View File

@@ -1,9 +1,11 @@
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
@@ -11,10 +13,13 @@ 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
@@ -24,11 +29,15 @@ 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,
@@ -77,4 +86,31 @@ 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

@@ -27,9 +27,10 @@ class StorageSyncScheduler @Inject constructor(
)
.build()
// KEEP: UPDATE сбрасывает таймер periodic work при каждом onCreate процесса.
WorkManager.getInstance(app).enqueueUniquePeriodicWork(
StorageSyncWorker.UNIQUE_WORK_NAME,
ExistingPeriodicWorkPolicy.UPDATE,
ExistingPeriodicWorkPolicy.KEEP,
request,
)
}