diff --git a/uhabits-android/lint-baseline.xml b/uhabits-android/lint-baseline.xml index 6de04b37..c55a3507 100644 --- a/uhabits-android/lint-baseline.xml +++ b/uhabits-android/lint-baseline.xml @@ -1578,17 +1578,6 @@ file="src/main/res/mipmap-anydpi-v26"/> - - - - - * - * This file is part of Loop Habit Tracker. - * - * Loop Habit Tracker is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by the - * Free Software Foundation, either version 3 of the License, or (at your - * option) any later version. - * - * Loop Habit Tracker is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - */ -package org.isoron.uhabits.tasks - -import android.os.AsyncTask -import kotlinx.coroutines.runBlocking -import org.isoron.uhabits.core.tasks.Task -import org.isoron.uhabits.core.tasks.TaskRunner -import java.util.HashMap -import java.util.LinkedList - -class AndroidTaskRunner : TaskRunner { - private val activeTasks: LinkedList = LinkedList() - private val taskToAsyncTask: HashMap = HashMap() - private val listeners: LinkedList = LinkedList() - override fun addListener(listener: TaskRunner.Listener) { - listeners.add(listener) - } - - override fun execute(task: Task) { - task.onAttached(this) - CustomAsyncTask(task).execute() - } - - override val activeTaskCount: Int - get() = activeTasks.size - - override fun publishProgress(task: Task, progress: Int) { - val asyncTask = taskToAsyncTask[task] ?: return - asyncTask.publish(progress) - } - - override fun removeListener(listener: TaskRunner.Listener) { - listeners.remove(listener) - } - - private inner class CustomAsyncTask(val task: Task) : AsyncTask() { - - fun publish(progress: Int) { - publishProgress(progress) - } - - @Deprecated("Deprecated in Java") - override fun doInBackground(vararg params: Void?): Void? { - if (isCancelled) return null - runBlocking { task.doInBackground() } - return null - } - - @Deprecated("Deprecated in Java") - override fun onPostExecute(aVoid: Void?) { - if (isCancelled) return - task.onPostExecute() - activeTasks.remove(this) - taskToAsyncTask.remove(task) - for (l in listeners) l.onTaskFinished(task) - } - - @Deprecated("Deprecated in Java") - override fun onPreExecute() { - if (isCancelled) return - for (l in listeners) l.onTaskStarted(task) - activeTasks.add(this) - taskToAsyncTask[task] = this - task.onPreExecute() - } - - @Deprecated("Deprecated in Java") - override fun onProgressUpdate(vararg values: Int?) { - values[0]?.let { task.onProgressUpdate(it) } - } - } -} diff --git a/uhabits-android/src/test/java/org/isoron/uhabits/BaseAndroidJVMTest.kt b/uhabits-android/src/test/java/org/isoron/uhabits/BaseAndroidJVMTest.kt index 68fe38df..cdcf7fe8 100644 --- a/uhabits-android/src/test/java/org/isoron/uhabits/BaseAndroidJVMTest.kt +++ b/uhabits-android/src/test/java/org/isoron/uhabits/BaseAndroidJVMTest.kt @@ -18,12 +18,14 @@ */ package org.isoron.uhabits +import kotlinx.coroutines.test.UnconfinedTestDispatcher import org.isoron.platform.time.LocalDate import org.isoron.platform.time.setToday import org.isoron.uhabits.core.commands.CommandRunner import org.isoron.uhabits.core.models.HabitList import org.isoron.uhabits.core.models.memory.MemoryModelFactory -import org.isoron.uhabits.core.tasks.SingleThreadTaskRunner +import org.isoron.uhabits.core.tasks.CoroutineTaskRunner +import org.isoron.uhabits.core.tasks.TaskRunner import org.isoron.uhabits.core.test.HabitFixtures import org.junit.After import org.junit.Before @@ -33,7 +35,7 @@ open class BaseAndroidJVMTest { private lateinit var habitList: HabitList protected lateinit var fixtures: HabitFixtures private lateinit var modelFactory: MemoryModelFactory - private lateinit var taskRunner: SingleThreadTaskRunner + private lateinit var taskRunner: TaskRunner private lateinit var commandRunner: CommandRunner @Before @@ -42,7 +44,10 @@ open class BaseAndroidJVMTest { modelFactory = MemoryModelFactory() habitList = modelFactory.buildHabitList() fixtures = HabitFixtures(modelFactory, habitList) - taskRunner = SingleThreadTaskRunner() + taskRunner = CoroutineTaskRunner( + mainDispatcher = UnconfinedTestDispatcher(), + ioDispatcher = UnconfinedTestDispatcher() + ) commandRunner = CommandRunner(taskRunner) } diff --git a/uhabits-core/src/commonMain/kotlin/org/isoron/uhabits/core/tasks/SingleThreadTaskRunner.kt b/uhabits-core/src/commonMain/kotlin/org/isoron/uhabits/core/tasks/CoroutineTaskRunner.kt similarity index 54% rename from uhabits-core/src/commonMain/kotlin/org/isoron/uhabits/core/tasks/SingleThreadTaskRunner.kt rename to uhabits-core/src/commonMain/kotlin/org/isoron/uhabits/core/tasks/CoroutineTaskRunner.kt index 36973188..3dbe442e 100644 --- a/uhabits-core/src/commonMain/kotlin/org/isoron/uhabits/core/tasks/SingleThreadTaskRunner.kt +++ b/uhabits-core/src/commonMain/kotlin/org/isoron/uhabits/core/tasks/CoroutineTaskRunner.kt @@ -18,33 +18,51 @@ */ package org.isoron.uhabits.core.tasks -import org.isoron.platform.runSuspend +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext -class SingleThreadTaskRunner : TaskRunner { - override val activeTaskCount: Int - get() = 0 +class CoroutineTaskRunner( + mainDispatcher: CoroutineDispatcher, + private val ioDispatcher: CoroutineDispatcher +) : TaskRunner { + + private val scope = CoroutineScope(SupervisorJob() + mainDispatcher) + private val listeners = mutableListOf() + private var activeCount = 0 + + override val activeTaskCount: Int get() = activeCount - private val listeners: MutableList = mutableListOf() override fun addListener(listener: TaskRunner.Listener) { listeners.add(listener) } - override fun execute(task: Task) { - for (l in listeners) l.onTaskStarted(task) - if (!task.isCanceled()) { - task.onAttached(this) - task.onPreExecute() - runSuspend { task.doInBackground() } - task.onPostExecute() - } - for (l in listeners) l.onTaskFinished(task) - } - - override fun publishProgress(task: Task, progress: Int) { - task.onProgressUpdate(progress) - } - override fun removeListener(listener: TaskRunner.Listener) { listeners.remove(listener) } + + override fun execute(task: Task) { + task.onAttached(this) + scope.launch { + activeCount++ + listeners.forEach { it.onTaskStarted(task) } + task.onPreExecute() + if (!task.isCanceled()) { + withContext(ioDispatcher) { + task.doInBackground() + } + } + task.onPostExecute() + activeCount-- + listeners.forEach { it.onTaskFinished(task) } + } + } + + override fun publishProgress(task: Task, progress: Int) { + scope.launch { + task.onProgressUpdate(progress) + } + } } diff --git a/uhabits-core/src/commonMain/kotlin/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehavior.kt b/uhabits-core/src/commonMain/kotlin/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehavior.kt index fb6c8481..47801c23 100644 --- a/uhabits-core/src/commonMain/kotlin/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehavior.kt +++ b/uhabits-core/src/commonMain/kotlin/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehavior.kt @@ -33,6 +33,7 @@ import org.isoron.uhabits.core.models.NumericalHabitType.AT_MOST import org.isoron.uhabits.core.models.PaletteColor import org.isoron.uhabits.core.preferences.Preferences import org.isoron.uhabits.core.tasks.ExportCSVTask +import org.isoron.uhabits.core.tasks.Task import org.isoron.uhabits.core.tasks.TaskRunner import org.isoron.uhabits.core.ui.callbacks.CheckMarkDialogCallback import org.isoron.uhabits.core.ui.callbacks.NumberPickerCallback @@ -107,10 +108,14 @@ open class ListHabitsBehavior( } open fun onRepairDB() { - taskRunner.execute { - habitList.repair() - screen.showMessage(Message.DATABASE_REPAIRED) - } + taskRunner.execute(object : Task { + override suspend fun doInBackground() { + habitList.repair() + } + override fun onPostExecute() { + screen.showMessage(Message.DATABASE_REPAIRED) + } + }) } open fun onSendBugReport() { diff --git a/uhabits-core/src/commonTest/kotlin/org/isoron/uhabits/core/BaseUnitTest.kt b/uhabits-core/src/commonTest/kotlin/org/isoron/uhabits/core/BaseUnitTest.kt index 8951ada1..30e4fa6e 100644 --- a/uhabits-core/src/commonTest/kotlin/org/isoron/uhabits/core/BaseUnitTest.kt +++ b/uhabits-core/src/commonTest/kotlin/org/isoron/uhabits/core/BaseUnitTest.kt @@ -18,6 +18,7 @@ */ package org.isoron.uhabits.core +import kotlinx.coroutines.test.UnconfinedTestDispatcher import org.isoron.platform.io.Database import org.isoron.platform.io.DatabaseOpener import org.isoron.platform.io.FileOpener @@ -31,7 +32,8 @@ import org.isoron.uhabits.core.commands.CommandRunner import org.isoron.uhabits.core.models.HabitList import org.isoron.uhabits.core.models.ModelFactory import org.isoron.uhabits.core.models.memory.MemoryModelFactory -import org.isoron.uhabits.core.tasks.SingleThreadTaskRunner +import org.isoron.uhabits.core.tasks.CoroutineTaskRunner +import org.isoron.uhabits.core.tasks.TaskRunner import org.isoron.uhabits.core.test.HabitFixtures import kotlin.test.BeforeTest @@ -39,7 +41,7 @@ open class BaseUnitTest { protected open lateinit var habitList: HabitList protected lateinit var fixtures: HabitFixtures protected lateinit var modelFactory: ModelFactory - protected lateinit var taskRunner: SingleThreadTaskRunner + protected lateinit var taskRunner: TaskRunner protected open lateinit var commandRunner: CommandRunner protected val fileOpener: FileOpener = createTestFileOpener() private var _databaseOpener: DatabaseOpener? = null @@ -57,7 +59,10 @@ open class BaseUnitTest { habitList = memoryModelFactory.buildHabitList() fixtures = HabitFixtures(memoryModelFactory, habitList) modelFactory = memoryModelFactory - taskRunner = SingleThreadTaskRunner() + taskRunner = CoroutineTaskRunner( + mainDispatcher = UnconfinedTestDispatcher(), + ioDispatcher = UnconfinedTestDispatcher() + ) commandRunner = CommandRunner(taskRunner) } diff --git a/uhabits-core/src/commonTest/kotlin/org/isoron/uhabits/core/tasks/SingleThreadTaskRunnerTest.kt b/uhabits-core/src/commonTest/kotlin/org/isoron/uhabits/core/tasks/CoroutineTaskRunnerTest.kt similarity index 81% rename from uhabits-core/src/commonTest/kotlin/org/isoron/uhabits/core/tasks/SingleThreadTaskRunnerTest.kt rename to uhabits-core/src/commonTest/kotlin/org/isoron/uhabits/core/tasks/CoroutineTaskRunnerTest.kt index 03757a8c..0a0b4588 100644 --- a/uhabits-core/src/commonTest/kotlin/org/isoron/uhabits/core/tasks/SingleThreadTaskRunnerTest.kt +++ b/uhabits-core/src/commonTest/kotlin/org/isoron/uhabits/core/tasks/CoroutineTaskRunnerTest.kt @@ -21,18 +21,22 @@ package org.isoron.uhabits.core.tasks import dev.mokkery.mock import dev.mokkery.verify.VerifyMode.Companion.order import dev.mokkery.verifySuspend +import kotlinx.coroutines.test.UnconfinedTestDispatcher import org.isoron.uhabits.core.BaseUnitTest import kotlin.test.BeforeTest import kotlin.test.Test -class SingleThreadTaskRunnerTest : BaseUnitTest() { - private lateinit var runner: SingleThreadTaskRunner +class CoroutineTaskRunnerTest : BaseUnitTest() { + private lateinit var runner: CoroutineTaskRunner private var task: Task = mock() @BeforeTest override fun setUp() { super.setUp() - runner = SingleThreadTaskRunner() + runner = CoroutineTaskRunner( + mainDispatcher = UnconfinedTestDispatcher(), + ioDispatcher = UnconfinedTestDispatcher() + ) } @Test