diff --git a/composeApp/src/androidMain/kotlin/org/ooni/probe/background/RunWorker.kt b/composeApp/src/androidMain/kotlin/org/ooni/probe/background/RunWorker.kt
index cb4e5a98..becd5f52 100644
--- a/composeApp/src/androidMain/kotlin/org/ooni/probe/background/RunWorker.kt
+++ b/composeApp/src/androidMain/kotlin/org/ooni/probe/background/RunWorker.kt
@@ -22,9 +22,9 @@ import kotlinx.serialization.encodeToString
import ooniprobe.composeapp.generated.resources.Dashboard_Running_Running
import ooniprobe.composeapp.generated.resources.Dashboard_Running_Stopping_Notice
import ooniprobe.composeapp.generated.resources.Dashboard_Running_Stopping_Title
-import ooniprobe.composeapp.generated.resources.Modal_ResultsNotUploaded_Uploading
import ooniprobe.composeapp.generated.resources.Notification_StopTest
import ooniprobe.composeapp.generated.resources.Res
+import ooniprobe.composeapp.generated.resources.UploadingMissingResults
import ooniprobe.composeapp.generated.resources.notification_channel_name
import org.jetbrains.compose.resources.getString
import org.ooni.probe.AndroidApplication
@@ -143,7 +143,7 @@ class RunWorker(
val progress = state.uploaded + state.failedToUpload + 1
setContentText(
getString(
- Res.string.Modal_ResultsNotUploaded_Uploading,
+ Res.string.UploadingMissingResults,
"$progress/${state.total}",
),
)
diff --git a/composeApp/src/commonMain/composeResources/values/strings-common.xml b/composeApp/src/commonMain/composeResources/values/strings-common.xml
index 2199b650..54499155 100644
--- a/composeApp/src/commonMain/composeResources/values/strings-common.xml
+++ b/composeApp/src/commonMain/composeResources/values/strings-common.xml
@@ -296,4 +296,5 @@
Maximum Websites test duration
Tests will run every hour in the background
Only for manual runs
+ Uploading missing results %1$s
diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/App.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/App.kt
index dcfcc190..cafd010c 100644
--- a/composeApp/src/commonMain/kotlin/org/ooni/probe/App.kt
+++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/App.kt
@@ -24,6 +24,7 @@ import ooniprobe.composeapp.generated.resources.Res
import org.jetbrains.compose.resources.getString
import org.jetbrains.compose.ui.tooling.preview.Preview
import org.ooni.probe.data.models.DeepLink
+import org.ooni.probe.data.models.RunSpecification
import org.ooni.probe.di.Dependencies
import org.ooni.probe.shared.PlatformInfo
import org.ooni.probe.ui.navigation.BottomNavigationBar
@@ -99,6 +100,7 @@ fun App(
dependencies.bootstrapPreferences()
dependencies.configureDescriptorAutoUpdate()
dependencies.fetchDescriptorUpdate(null)
+ dependencies.startSingleRunInner(RunSpecification.OnlyUploadMissingResults)
}
LaunchedEffect(Unit) {
dependencies.finishInProgressData()
diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/background/RunBackgroundTask.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/background/RunBackgroundTask.kt
index 62889795..8a30e959 100644
--- a/composeApp/src/commonMain/kotlin/org/ooni/probe/background/RunBackgroundTask.kt
+++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/background/RunBackgroundTask.kt
@@ -28,8 +28,8 @@ class RunBackgroundTask(
private val uploadMissingMeasurements: (ResultModel.Id?) -> Flow,
private val checkSkipAutoRunNotUploadedLimit: () -> Flow,
private val getNetworkType: () -> NetworkType,
- private val getAutoRunSpecification: suspend () -> RunSpecification,
- private val runDescriptors: suspend (RunSpecification) -> Unit,
+ private val getAutoRunSpecification: suspend () -> RunSpecification.Full,
+ private val runDescriptors: suspend (RunSpecification.Full) -> Unit,
private val setRunBackgroundState: ((RunBackgroundState) -> RunBackgroundState) -> Unit,
private val getRunBackgroundState: () -> Flow,
private val addRunCancelListener: (() -> Unit) -> Unit,
@@ -44,52 +44,57 @@ class RunBackgroundTask(
return@channelFlow
}
- val uploadCancelled = uploadMissingResults(isAutoRun = spec == null)
- if (uploadCancelled) return@channelFlow
+ val isAutoRun = spec == null
+ if (isAutoRun || spec is RunSpecification.OnlyUploadMissingResults) {
+ val uploadCancelled = uploadMissingResults()
+ if (uploadCancelled) return@channelFlow
+ }
+
+ if (spec is RunSpecification.OnlyUploadMissingResults) {
+ setRunBackgroundState { RunBackgroundState.Idle() }
+ return@channelFlow
+ }
- runTests(spec)
+ runTests(spec as? RunSpecification.Full)
// When a test is cancelled, sometimes the last measurement isn't uploaded
getLatestResult().first()?.id.let { latestResultId ->
val idleState = getRunBackgroundState().first()
- uploadMissingResults(isAutoRun = spec == null, resultId = latestResultId)
+ uploadMissingResults(resultId = latestResultId)
updateState(idleState)
}
}.onCompletion {
clearRunCancelListeners()
}
- private suspend fun ProducerScope.uploadMissingResults(
- isAutoRun: Boolean,
- resultId: ResultModel.Id? = null,
- ): Boolean {
+ private suspend fun ProducerScope.uploadMissingResults(resultId: ResultModel.Id? = null): Boolean {
val autoUpload = getPreferenceValueByKey(SettingsKey.UPLOAD_RESULTS).first() == true
- var isCancelled = false
+ if (!autoUpload) return false
- if ((isAutoRun || resultId != null) && autoUpload) {
- coroutineScope {
- val uploadJob = async {
- uploadMissingMeasurements(resultId)
- .collectLatest { uploadState ->
- updateState(RunBackgroundState.UploadingMissingResults(uploadState))
- }
- }
+ var isCancelled = false
- addRunCancelListener {
- isCancelled = true
- if (uploadJob.isActive) uploadJob.cancel()
- CoroutineScope(Dispatchers.Default).launch {
- updateState(RunBackgroundState.Stopping)
+ coroutineScope {
+ val uploadJob = async {
+ uploadMissingMeasurements(resultId)
+ .collectLatest { uploadState ->
+ updateState(RunBackgroundState.UploadingMissingResults(uploadState))
}
- }
+ }
- try {
- uploadJob.await()
- } catch (e: CancellationException) {
- Logger.i("Upload Missing Results (result=$resultId): cancelled")
+ addRunCancelListener {
+ isCancelled = true
+ if (uploadJob.isActive) uploadJob.cancel()
+ CoroutineScope(Dispatchers.Default).launch {
+ updateState(RunBackgroundState.Stopping)
}
}
+
+ try {
+ uploadJob.await()
+ } catch (e: CancellationException) {
+ Logger.i("Upload Missing Results (result=$resultId): cancelled")
+ }
}
if (isCancelled) {
@@ -100,7 +105,7 @@ class RunBackgroundTask(
return false
}
- private suspend fun ProducerScope.runTests(spec: RunSpecification?) {
+ private suspend fun ProducerScope.runTests(spec: RunSpecification.Full?) {
if (checkSkipAutoRunNotUploadedLimit().first()) {
Logger.i("Skipping auto-run tests: too many not-uploaded results")
return
diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/data/models/RunSpecification.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/data/models/RunSpecification.kt
index 6b705764..3c2ffd18 100644
--- a/composeApp/src/commonMain/kotlin/org/ooni/probe/data/models/RunSpecification.kt
+++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/data/models/RunSpecification.kt
@@ -4,11 +4,17 @@ import kotlinx.serialization.Serializable
import org.ooni.engine.models.TaskOrigin
@Serializable
-data class RunSpecification(
- val tests: List,
- val taskOrigin: TaskOrigin,
- val isRerun: Boolean,
-) {
+sealed interface RunSpecification {
+ @Serializable
+ data object OnlyUploadMissingResults : RunSpecification
+
+ @Serializable
+ data class Full(
+ val tests: List,
+ val taskOrigin: TaskOrigin,
+ val isRerun: Boolean,
+ ) : RunSpecification
+
@Serializable
data class Test(
val source: Source,
diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/di/Dependencies.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/di/Dependencies.kt
index 1867688d..1e5a9952 100644
--- a/composeApp/src/commonMain/kotlin/org/ooni/probe/di/Dependencies.kt
+++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/di/Dependencies.kt
@@ -106,7 +106,7 @@ class Dependencies(
@VisibleForTesting
val buildDataStore: () -> DataStore,
private val isBatteryCharging: () -> Boolean,
- private val startSingleRunInner: (RunSpecification) -> Unit,
+ val startSingleRunInner: (RunSpecification) -> Unit,
private val configureAutoRun: suspend (AutoRunParameters) -> Unit,
val configureDescriptorAutoUpdate: suspend () -> Boolean,
val fetchDescriptorUpdate: suspend (List?) -> Unit,
diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/GetAutoRunSpecification.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/GetAutoRunSpecification.kt
index 4f5eaa7f..47455162 100644
--- a/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/GetAutoRunSpecification.kt
+++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/GetAutoRunSpecification.kt
@@ -11,10 +11,10 @@ class GetAutoRunSpecification(
private val getDescriptors: GetTestDescriptors,
private val preferenceRepository: PreferenceRepository,
) {
- suspend operator fun invoke(): RunSpecification {
+ suspend operator fun invoke(): RunSpecification.Full {
val descriptors = getDescriptors().first().filterForAutoRun()
- return RunSpecification(
+ return RunSpecification.Full(
tests = descriptors.map { descriptor ->
RunSpecification.Test(
source = RunSpecification.Test.Source.fromDescriptor(descriptor),
diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/GetTestDescriptorsBySpec.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/GetTestDescriptorsBySpec.kt
index 4ec91cd5..89017c85 100644
--- a/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/GetTestDescriptorsBySpec.kt
+++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/GetTestDescriptorsBySpec.kt
@@ -9,7 +9,7 @@ import org.ooni.probe.data.models.RunSpecification
class GetTestDescriptorsBySpec(
private val getTestDescriptors: () -> Flow>,
) {
- suspend operator fun invoke(spec: RunSpecification): List =
+ suspend operator fun invoke(spec: RunSpecification.Full): List =
getTestDescriptors()
.first()
.filterNot { it.isExpired }
@@ -30,7 +30,7 @@ class GetTestDescriptorsBySpec(
}
// Is this descriptor contained in the RunSpecification's list of tests
- private fun RunSpecification.forDescriptor(descriptor: Descriptor) =
+ private fun RunSpecification.Full.forDescriptor(descriptor: Descriptor) =
tests.firstOrNull { specTest ->
when (descriptor.source) {
is Descriptor.Source.Default -> {
diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/RunDescriptors.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/RunDescriptors.kt
index 529a47d1..58447056 100644
--- a/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/RunDescriptors.kt
+++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/RunDescriptors.kt
@@ -20,7 +20,7 @@ import org.ooni.probe.shared.now
import kotlin.time.Duration
class RunDescriptors(
- private val getTestDescriptorsBySpec: suspend (RunSpecification) -> List,
+ private val getTestDescriptorsBySpec: suspend (RunSpecification.Full) -> List,
private val downloadUrls: suspend (TaskOrigin) -> Result, MkException>,
private val storeResult: suspend (ResultModel) -> ResultModel.Id,
private val markResultAsDone: suspend (ResultModel.Id) -> Unit,
@@ -32,7 +32,7 @@ class RunDescriptors(
private val getEnginePreferences: suspend () -> EnginePreferences,
private val finishInProgressData: suspend () -> Unit,
) {
- suspend operator fun invoke(spec: RunSpecification) {
+ suspend operator fun invoke(spec: RunSpecification.Full) {
setRunBackgroundState { RunBackgroundState.RunningTests() }
val descriptors = getTestDescriptorsBySpec(spec)
@@ -55,7 +55,7 @@ class RunDescriptors(
private suspend fun runDescriptorsCancellable(
descriptors: List,
- spec: RunSpecification,
+ spec: RunSpecification.Full,
) {
addRunCancelListener {
setRunBackgroundState { RunBackgroundState.Stopping }
diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/choosewebsites/ChooseWebsitesViewModel.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/choosewebsites/ChooseWebsitesViewModel.kt
index 88cc5369..349f2ab1 100644
--- a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/choosewebsites/ChooseWebsitesViewModel.kt
+++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/choosewebsites/ChooseWebsitesViewModel.kt
@@ -101,7 +101,7 @@ class ChooseWebsitesViewModel(
}
startBackgroundRun(
- RunSpecification(
+ RunSpecification.Full(
tests = listOf(
RunSpecification.Test(
source = RunSpecification.Test.Source.Default("websites"),
diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/dashboard/RunBackgroundStateSection.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/dashboard/RunBackgroundStateSection.kt
index 600af177..b73e0075 100644
--- a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/dashboard/RunBackgroundStateSection.kt
+++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/dashboard/RunBackgroundStateSection.kt
@@ -27,9 +27,9 @@ import ooniprobe.composeapp.generated.resources.Dashboard_Running_EstimatedTimeL
import ooniprobe.composeapp.generated.resources.Dashboard_Running_Running
import ooniprobe.composeapp.generated.resources.Dashboard_Running_Stopping_Notice
import ooniprobe.composeapp.generated.resources.Dashboard_Running_Stopping_Title
-import ooniprobe.composeapp.generated.resources.Modal_ResultsNotUploaded_Uploading
import ooniprobe.composeapp.generated.resources.OONIRun_Run
import ooniprobe.composeapp.generated.resources.Res
+import ooniprobe.composeapp.generated.resources.UploadingMissingResults
import ooniprobe.composeapp.generated.resources.ic_timer
import org.jetbrains.compose.resources.painterResource
import org.jetbrains.compose.resources.stringResource
@@ -104,7 +104,7 @@ fun RunBackgroundStateSection(
val progress = uploadState.uploaded + uploadState.failedToUpload + 1
Text(
text = stringResource(
- Res.string.Modal_ResultsNotUploaded_Uploading,
+ Res.string.UploadingMissingResults,
"$progress/${uploadState.total}",
),
style = MaterialTheme.typography.bodyLarge,
diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/result/ResultViewModel.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/result/ResultViewModel.kt
index 7c877b9d..d481da49 100644
--- a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/result/ResultViewModel.kt
+++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/result/ResultViewModel.kt
@@ -103,7 +103,7 @@ class ResultViewModel(
private fun getRerunSpecification(): RunSpecification? {
val item = _state.value.result ?: return null
- return RunSpecification(
+ return RunSpecification.Full(
tests = listOf(
RunSpecification.Test(
source = RunSpecification.Test.Source.fromDescriptor(item.descriptor),
diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/run/RunViewModel.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/run/RunViewModel.kt
index 86941bb1..d37e8c4d 100644
--- a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/run/RunViewModel.kt
+++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/run/RunViewModel.kt
@@ -224,7 +224,7 @@ class RunViewModel(
.filter { it.isSelected }
.map { it.item }
}
- return RunSpecification(
+ return RunSpecification.Full(
tests =
selectedTests.map { (descriptor, tests) ->
RunSpecification.Test(
diff --git a/composeApp/src/commonTest/kotlin/org/ooni/probe/ui/choosewebsites/ChooseWebsitesTest.kt b/composeApp/src/commonTest/kotlin/org/ooni/probe/ui/choosewebsites/ChooseWebsitesTest.kt
index dcafeca0..7738b96a 100644
--- a/composeApp/src/commonTest/kotlin/org/ooni/probe/ui/choosewebsites/ChooseWebsitesTest.kt
+++ b/composeApp/src/commonTest/kotlin/org/ooni/probe/ui/choosewebsites/ChooseWebsitesTest.kt
@@ -45,7 +45,7 @@ class ChooseWebsitesTest {
}
onNodeWithText("Test ${websites.size} URLs").performClick()
- val spec = runSpec
+ val spec = runSpec as? RunSpecification.Full
assertNotNull(spec)
assertEquals(false, spec.isRerun)
assertEquals(TaskOrigin.OoniRun, spec.taskOrigin)