[#1151] Background syncing
* [#1151] Background syncing - A periodic background block synchronization has been added. When the device is connected to the internet using an unmetered connection and is plugged into the power, the background task will start to synchronize blocks randomly between 3 and 4 a.m. - The background worker was in place but not fully working, plus was set to trigger randomly in 24 hours - Changelog update - Closes #1151 - Closes #634 - Its follow-up #1249 - Its follow-up #1258
This commit is contained in:
parent
79de1690bb
commit
bf618a1ba3
|
@ -9,6 +9,11 @@ directly impact users rather than highlighting other key architectural updates.*
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
- A periodic background block synchronization has been added. When the device is connected to the internet using an
|
||||
unmetered connection and is plugged into the power, the background task will start to synchronize blocks randomly
|
||||
between 3 and 4 a.m.
|
||||
|
||||
## [0.2.0 (554)] - 2024-02-13
|
||||
|
||||
### Changed
|
||||
|
|
|
@ -23,4 +23,5 @@
|
|||
# kotlinx.datetime supports kotlinx.serialization, but we don't use kotlinx.serialization elsewhere
|
||||
# in the projects, so the classes aren't present. These warnings are safe to suppress.
|
||||
-dontwarn kotlinx.serialization.KSerializer
|
||||
-dontwarn kotlinx.serialization.Serializable
|
||||
-dontwarn kotlinx.serialization.Serializable
|
||||
-dontwarn kotlinx.serialization.internal.AbstractPolymorphicSerializer
|
|
@ -12,55 +12,117 @@ import cash.z.ecc.android.sdk.Synchronizer
|
|||
import cash.z.ecc.android.sdk.WalletCoordinator
|
||||
import cash.z.ecc.android.sdk.model.PercentDecimal
|
||||
import co.electriccoin.zcash.global.getInstance
|
||||
import co.electriccoin.zcash.spackle.Twig
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.takeWhile
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.datetime.DateTimeUnit
|
||||
import kotlinx.datetime.Instant
|
||||
import kotlinx.datetime.TimeZone
|
||||
import kotlinx.datetime.atTime
|
||||
import kotlinx.datetime.toInstant
|
||||
import kotlinx.datetime.toLocalDateTime
|
||||
import kotlinx.datetime.until
|
||||
import kotlin.random.Random
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.days
|
||||
import kotlin.time.Duration.Companion.hours
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
import kotlin.time.DurationUnit
|
||||
import kotlin.time.toDuration
|
||||
import kotlin.time.toJavaDuration
|
||||
|
||||
// TODO [#1249]: Add documentation and tests on background syncing
|
||||
// TODO [#1249]: https://github.com/Electric-Coin-Company/zashi-android/issues/1249
|
||||
|
||||
@Keep
|
||||
class SyncWorker(context: Context, workerParameters: WorkerParameters) : CoroutineWorker(context, workerParameters) {
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
override suspend fun doWork(): Result {
|
||||
// Enhancements to this implementation would be:
|
||||
// - Quit early if the synchronizer is null after a timeout period
|
||||
// - Return better status information
|
||||
Twig.debug { "BG Sync: starting..." }
|
||||
|
||||
WalletCoordinator.getInstance(applicationContext).synchronizer
|
||||
.flatMapLatest {
|
||||
Twig.debug { "BG Sync: synchronizer: $it" }
|
||||
|
||||
it?.status?.combine(it.progress) { status, progress ->
|
||||
StatusAndProgress(status, progress)
|
||||
StatusAndProgress(status, progress).also {
|
||||
Twig.debug { "BG Sync: result: $it" }
|
||||
}
|
||||
} ?: emptyFlow()
|
||||
}
|
||||
.takeWhile {
|
||||
it.status != Synchronizer.Status.DISCONNECTED && it.progress.isLessThanHundredPercent()
|
||||
it.status != Synchronizer.Status.DISCONNECTED &&
|
||||
it.status != Synchronizer.Status.SYNCED
|
||||
}
|
||||
.collect()
|
||||
|
||||
Twig.debug { "BG Sync: terminating..." }
|
||||
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
companion object {
|
||||
/*
|
||||
* There may be better periods; we have not optimized for this yet.
|
||||
*/
|
||||
private val DEFAULT_SYNC_PERIOD = 24.hours
|
||||
private val SYNC_PERIOD = 24.hours
|
||||
private val SYNC_DAY_SHIFT = 1.days // Move to tomorrow
|
||||
private val SYNC_START_TIME_HOURS = 3.hours // Start around 3 a.m. at night
|
||||
private val SYNC_START_TIME_MINUTES = 60.minutes // Randomize with minutes until 4 a.m.
|
||||
|
||||
fun newWorkRequest(): PeriodicWorkRequest {
|
||||
val targetTimeDiff = calculateTargetTimeDifference()
|
||||
|
||||
Twig.debug { "BG Sync: necessary trigger delay time: $targetTimeDiff" }
|
||||
|
||||
val constraints =
|
||||
Constraints.Builder()
|
||||
.setRequiresStorageNotLow(true)
|
||||
.setRequiredNetworkType(NetworkType.CONNECTED)
|
||||
.setRequiredNetworkType(NetworkType.UNMETERED)
|
||||
.setRequiresCharging(true)
|
||||
.build()
|
||||
|
||||
return PeriodicWorkRequestBuilder<SyncWorker>(DEFAULT_SYNC_PERIOD.toJavaDuration())
|
||||
// TODO [#1258]: Consider using flexInterval in BG sync trigger planning
|
||||
// TODO [#1258]: https://github.com/Electric-Coin-Company/zashi-android/issues/1258
|
||||
return PeriodicWorkRequestBuilder<SyncWorker>(SYNC_PERIOD.toJavaDuration())
|
||||
.setConstraints(constraints)
|
||||
.setInitialDelay(targetTimeDiff.toJavaDuration())
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun calculateTargetTimeDifference(): Duration {
|
||||
val currentTimeZone: TimeZone = TimeZone.currentSystemDefault()
|
||||
|
||||
val now: Instant = Clock.System.now()
|
||||
|
||||
val targetTime =
|
||||
now
|
||||
.plus(SYNC_DAY_SHIFT)
|
||||
.toLocalDateTime(currentTimeZone)
|
||||
.date
|
||||
.atTime(
|
||||
hour = SYNC_START_TIME_HOURS.inWholeHours.toInt(),
|
||||
// Even though the WorkManager will trigger the work approximately at the set time, it's
|
||||
// better to randomize time in 3-4 a.m. This generates a number between 0 (inclusive) and 60
|
||||
// (exclusive)
|
||||
minute = Random.nextInt(0, SYNC_START_TIME_MINUTES.inWholeMinutes.toInt())
|
||||
)
|
||||
|
||||
Twig.debug { "BG Sync: calculated target time: ${targetTime.time}" }
|
||||
|
||||
return now.until(
|
||||
other = targetTime.toInstant(currentTimeZone),
|
||||
unit = DateTimeUnit.MILLISECOND,
|
||||
timeZone = currentTimeZone
|
||||
).toDuration(DurationUnit.MILLISECONDS)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private data class StatusAndProgress(val status: Synchronizer.Status, val progress: PercentDecimal)
|
||||
// Enhancement to this implementation would be returning a better status information
|
||||
private data class StatusAndProgress(
|
||||
val status: Synchronizer.Status,
|
||||
val progress: PercentDecimal
|
||||
)
|
||||
|
|
|
@ -3,6 +3,7 @@ package co.electriccoin.zcash.work
|
|||
import android.content.Context
|
||||
import androidx.work.ExistingPeriodicWorkPolicy
|
||||
import androidx.work.WorkManager
|
||||
import co.electriccoin.zcash.spackle.Twig
|
||||
|
||||
object WorkIds {
|
||||
const val WORK_ID_BACKGROUND_SYNC = "co.electriccoin.zcash.background_sync"
|
||||
|
@ -10,11 +11,23 @@ object WorkIds {
|
|||
fun enableBackgroundSynchronization(context: Context) {
|
||||
val workManager = WorkManager.getInstance(context)
|
||||
|
||||
Twig.debug {
|
||||
"BG Sync: existing work details:" +
|
||||
" ${workManager.getWorkInfosForUniqueWork(WORK_ID_BACKGROUND_SYNC).get()}"
|
||||
}
|
||||
|
||||
// Note: Re-enqueuing existing work is okay. Another approach would be to validate the existing work and
|
||||
// enqueue it if it is not planned yet or not in a valid state
|
||||
workManager.enqueueUniquePeriodicWork(
|
||||
WORK_ID_BACKGROUND_SYNC,
|
||||
ExistingPeriodicWorkPolicy.KEEP,
|
||||
SyncWorker.newWorkRequest()
|
||||
)
|
||||
|
||||
Twig.debug {
|
||||
"BG Sync: newly enqueued work details:" +
|
||||
" ${workManager.getWorkInfosForUniqueWork(WORK_ID_BACKGROUND_SYNC).get()}"
|
||||
}
|
||||
}
|
||||
|
||||
fun disableBackgroundSynchronization(context: Context) {
|
||||
|
|
Loading…
Reference in New Issue