From 52cf0e17330799fd30ecd8e95b4d1cd5ea283e59 Mon Sep 17 00:00:00 2001 From: Levi Bostian Date: Tue, 17 Apr 2018 16:43:32 -0500 Subject: [PATCH 1/6] Enforce best practice: All subclasses of a PendingTask must all have a groupId or none of them have a groupId. --- .idea/misc.xml | 2 +- BEST_PRACTICES.md | 17 +++++++++++ .../wendy/db/PendingTasksManager.kt | 30 +++++++++---------- .../PersistedPendingTaskExtensions.kt | 10 +++++++ .../com/levibostian/wendy/service/Wendy.kt | 8 +++++ 5 files changed, 50 insertions(+), 17 deletions(-) create mode 100644 wendy/src/main/java/com/levibostian/wendy/extension/PersistedPendingTaskExtensions.kt diff --git a/.idea/misc.xml b/.idea/misc.xml index 99202cc..c0f68ed 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -25,7 +25,7 @@ - + diff --git a/BEST_PRACTICES.md b/BEST_PRACTICES.md index 54fc01c..ce6b7e2 100644 --- a/BEST_PRACTICES.md +++ b/BEST_PRACTICES.md @@ -6,6 +6,23 @@ You know the `data_id` property in `PendingTask`? That `data_id` property is mea Because you are supposed to query for the data of a job right before a job is run, there is no need to update a `PendingTask`. If the user edits data represented by a certain `data_id` `PendingData`, the freshest data will be the data that gets synced. +# PendingTask subclasses each having 1 specific use case + +Each subclass of PendingTask that you create in your app should represent 1 task a user can perform in the app. + +Example: You are building a grocery list app. Users of your app can complete the following tasks: + +* Add grocery store list items. +* Edit the name of the already added grocery store list items. +* Delete grocery store list items. +* Update the profile picture of their account they created. + +Because there are 4 separate, small tasks that users can do in your app, your app code needs to have 4 separate subclasses of PendingTask. One for each task. Sure, you might think you can and/or should combine the top 3 tasks into a PendingTask subclass called `GroceryStoreListItemPendingTask` and have all of the various abilities combined into 1 and have 1 subclass `ProfilePendingTask` for the last task of updating the profile picture. This is not the way Wendy is intended to work. + +Wendy requires each subclass of `PendingTask` is designed to perform 1 task. There are checks in Wendy that will throw exceptions on you if you do not follow this rule. If you do, you should not have an issue. Here is a list of those checks Wendy performs: + +* Every instance of a `PendingTask` subclass must **all** have a `groupId` or **all** must *not* have a `groupId`. + # How do I delete a PendingTask that I added to Wendy? You don't delete `PendingTask`s. That is on purpose. diff --git a/wendy/src/main/java/com/levibostian/wendy/db/PendingTasksManager.kt b/wendy/src/main/java/com/levibostian/wendy/db/PendingTasksManager.kt index 2861c1d..c025054 100644 --- a/wendy/src/main/java/com/levibostian/wendy/db/PendingTasksManager.kt +++ b/wendy/src/main/java/com/levibostian/wendy/db/PendingTasksManager.kt @@ -1,6 +1,7 @@ package com.levibostian.wendy.db import android.content.Context +import com.levibostian.wendy.extension.getPendingTask import com.levibostian.wendy.extension.getTaskAssertPopulated import com.levibostian.wendy.service.PendingTask import com.levibostian.wendy.service.Wendy @@ -64,6 +65,15 @@ internal class PendingTasksManager(context: Context) { } } + @Synchronized + internal fun getRandomTaskForTag(tag: String): PendingTask? { + return db.use { + select(PersistedPendingTask.TABLE_NAME) + .whereArgs("(${PersistedPendingTask.COLUMN_TAG} = $tag)") + .exec { parseList(classParser()).firstOrNull()?.getPendingTask() } + } + } + /** * Note: It's assumed that you have checked if the PendingTaskError.taskId exists. */ @@ -105,13 +115,10 @@ internal class PendingTasksManager(context: Context) { } internal fun getAllTasks(): List { - val tasksFactory = Wendy.sharedInstance().tasksFactory return db.use { select(PersistedPendingTask.TABLE_NAME) .exec { - parseList(classParser()).map { - tasksFactory.getTaskAssertPopulated(it.tag).fromSqlObject(it) - } + parseList(classParser()).map { it.getPendingTask() } } } } @@ -138,22 +145,15 @@ internal class PendingTasksManager(context: Context) { @Synchronized internal fun getPendingTaskTaskById(taskId: Long): PendingTask? { - val tasksFactory = Wendy.sharedInstance().tasksFactory return db.use { select(PersistedPendingTask.TABLE_NAME) .whereArgs("${PersistedPendingTask.COLUMN_ID} = $taskId") - .exec { - val task = parseOpt(classParser()) - if (task == null) null else tasksFactory.getTaskAssertPopulated(task.tag).fromSqlObject(task) - } + .exec { parseOpt(classParser())?.getPendingTask() } } } @Synchronized internal fun getNextTaskToRun(afterTaskId: Long = 0, filter: PendingTasksRunner.RunAllTasksFilter? = null): PendingTask? { - val tasksFactory = Wendy.sharedInstance().tasksFactory - - var nextTask: PersistedPendingTask? return db.use { var whereArgs = "(${PersistedPendingTask.COLUMN_ID} > $afterTaskId) AND (${PersistedPendingTask.COLUMN_MANUALLY_RUN} = ${PersistedPendingTask.NOT_MANUALLY_RUN})" @@ -161,11 +161,9 @@ internal class PendingTasksManager(context: Context) { whereArgs += " AND (${PersistedPendingTask.COLUMN_GROUP_ID} = $filterByGroupId)" } - nextTask = select(PersistedPendingTask.TABLE_NAME) + select(PersistedPendingTask.TABLE_NAME) .whereArgs(whereArgs) - .exec { parseList(classParser()).firstOrNull() } - - if (nextTask == null) null else tasksFactory.getTaskAssertPopulated(nextTask!!.tag).fromSqlObject(nextTask!!) + .exec { parseList(classParser()).firstOrNull()?.getPendingTask() } } } diff --git a/wendy/src/main/java/com/levibostian/wendy/extension/PersistedPendingTaskExtensions.kt b/wendy/src/main/java/com/levibostian/wendy/extension/PersistedPendingTaskExtensions.kt new file mode 100644 index 0000000..e6cad81 --- /dev/null +++ b/wendy/src/main/java/com/levibostian/wendy/extension/PersistedPendingTaskExtensions.kt @@ -0,0 +1,10 @@ +package com.levibostian.wendy.extension + +import com.levibostian.wendy.db.PersistedPendingTask +import com.levibostian.wendy.service.PendingTask +import com.levibostian.wendy.service.Wendy + +internal fun PersistedPendingTask.getPendingTask(): PendingTask { + val tasksFactory = Wendy.sharedInstance().tasksFactory + return tasksFactory.getTaskAssertPopulated(this.tag).fromSqlObject(this) +} \ No newline at end of file diff --git a/wendy/src/main/java/com/levibostian/wendy/service/Wendy.kt b/wendy/src/main/java/com/levibostian/wendy/service/Wendy.kt index ffe7273..36bb883 100644 --- a/wendy/src/main/java/com/levibostian/wendy/service/Wendy.kt +++ b/wendy/src/main/java/com/levibostian/wendy/service/Wendy.kt @@ -92,10 +92,18 @@ class Wendy private constructor(context: Context, internal val tasksFactory: Pen * @param pendingTask Task you want to add to Wendy. * * @throws RuntimeException Wendy will check to make sure that you have remembered to add your argument's [PendingTask] subclass to your instance of [PendingTasksFactory] when you call this method. If your [PendingTasksFactory] returns null (which probably means that you forgot to include a [PendingTask]) then an [RuntimeException] will be thrown. + * @throws IllegalArgumentException If your [PendingTask] subclass does not follow the enforced best practice of: All subclasses of a [PendingTask] must **all** have a groupId or **none** of them have a groupId. */ fun addTask(pendingTask: PendingTask, resolveErrorIfTaskExists: Boolean = true): Long { tasksFactory.getTaskAssertPopulated(pendingTask.tag) + tasksManager.getRandomTaskForTag(pendingTask.tag)?.let { similarPendingTask -> + if (similarPendingTask.groupId == null && pendingTask.groupId != null || + similarPendingTask.groupId != null && pendingTask.groupId == null) { + throw IllegalArgumentException("All subclasses of a PendingTask must either **all** have a groupId or **none** of them have a groupId.") + } + } + tasksManager.getExistingTask(pendingTask)?.let { existingPersistedPendingTask -> if (doesErrorExist(existingPersistedPendingTask.id) && resolveErrorIfTaskExists) { resolveError(existingPersistedPendingTask.id) From bf4ccc35e63a477c07868df182640b5c0a4729cd Mon Sep 17 00:00:00 2001 From: Levi Bostian Date: Tue, 17 Apr 2018 16:49:28 -0500 Subject: [PATCH 2/6] Fix bug: runAllTasks() function in task runner was not passing in all paramters for recursion calls. --- .../wendy/service/PendingTasksRunner.kt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/wendy/src/main/java/com/levibostian/wendy/service/PendingTasksRunner.kt b/wendy/src/main/java/com/levibostian/wendy/service/PendingTasksRunner.kt index 09617c2..e7e56ab 100644 --- a/wendy/src/main/java/com/levibostian/wendy/service/PendingTasksRunner.kt +++ b/wendy/src/main/java/com/levibostian/wendy/service/PendingTasksRunner.kt @@ -20,7 +20,7 @@ internal class PendingTasksRunner(val context: Context, @Synchronized @WorkerThread - fun runAllTasks(filter: RunAllTasksFilter? = null) { + fun runAllTasks(filter: RunAllTasksFilter?) { LogUtil.d("Getting next task to run.") val nextTaskToRun = pendingTasksManager.getNextTaskToRun(lastSuccessfulOrFailedTaskId, filter) @@ -36,7 +36,7 @@ internal class PendingTasksRunner(val context: Context, if (nextTaskToRun.groupId != null && failedTasksGroups.contains(nextTaskToRun.groupId!!)) { WendyConfig.logTaskSkipped(nextTaskToRun, ReasonPendingTaskSkipped.PART_OF_FAILED_GROUP) LogUtil.d("Task: $nextTaskToRun belongs to a failing group of tasks. Skipping it.") - runAllTasks() + runAllTasks(filter) return } @@ -44,22 +44,22 @@ internal class PendingTasksRunner(val context: Context, jobRunResult.accept(object : PendingTasksRunnerJobRunResult.Visitor { override fun visitSkippedUnresolvedRecordedError(): Unit? { nextTaskToRun.groupId?.let { failedTasksGroups.add(it) } - return runAllTasks() + return runAllTasks(filter) } override fun visitSuccessful(): Unit? { - return runAllTasks() + return runAllTasks(filter) } override fun visitNotSuccessful(): Unit? { nextTaskToRun.groupId?.let { failedTasksGroups.add(it) } - return runAllTasks() + return runAllTasks(filter) } override fun visitTaskDoesntExist(): Unit? { // Ignore this. If it doesn't exist, it doesn't exist. - return runAllTasks() + return runAllTasks(filter) } override fun visitSkippedNotReady(): Unit? { nextTaskToRun.groupId?.let { failedTasksGroups.add(it) } - return runAllTasks() + return runAllTasks(filter) } }) } From 8ba71277fe8409b5fa29baa6ec5bd2e5457ae1c4 Mon Sep 17 00:00:00 2001 From: Levi Bostian Date: Tue, 17 Apr 2018 16:51:44 -0500 Subject: [PATCH 3/6] Remove rescheduled parameter in listeners as it's not needed. --- wendy/src/main/java/com/levibostian/wendy/WendyConfig.kt | 6 +++--- .../wendy/listeners/PendingTaskStatusListener.kt | 3 +-- .../com/levibostian/wendy/listeners/TaskRunnerListener.kt | 3 +-- .../com/levibostian/wendy/service/PendingTasksRunner.kt | 4 ++-- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/wendy/src/main/java/com/levibostian/wendy/WendyConfig.kt b/wendy/src/main/java/com/levibostian/wendy/WendyConfig.kt index 8cbf8a7..139756b 100644 --- a/wendy/src/main/java/com/levibostian/wendy/WendyConfig.kt +++ b/wendy/src/main/java/com/levibostian/wendy/WendyConfig.kt @@ -115,13 +115,13 @@ internal fun WendyConfig.Companion.logTaskRunning(task: PendingTask) { }) } -internal fun WendyConfig.Companion.logTaskComplete(task: PendingTask, successful: Boolean, rescheduled: Boolean) { +internal fun WendyConfig.Companion.logTaskComplete(task: PendingTask, successful: Boolean) { Handler(Looper.getMainLooper()).post({ WendyConfig.getTaskStatusListenerForTask(task.taskId ?: 0).forEach { - it.complete(task.taskId!!, successful, rescheduled) + it.complete(task.taskId!!, successful) } WendyConfig.getTaskRunnerListeners().forEach { - it.taskComplete(successful, task, rescheduled) + it.taskComplete(successful, task) } }) } diff --git a/wendy/src/main/java/com/levibostian/wendy/listeners/PendingTaskStatusListener.kt b/wendy/src/main/java/com/levibostian/wendy/listeners/PendingTaskStatusListener.kt index 9c6a8ca..787344a 100644 --- a/wendy/src/main/java/com/levibostian/wendy/listeners/PendingTaskStatusListener.kt +++ b/wendy/src/main/java/com/levibostian/wendy/listeners/PendingTaskStatusListener.kt @@ -27,9 +27,8 @@ interface PendingTaskStatusListener { * * @param taskId The taskId of the [PendingTask] that just ran. * @param successful Indicates if the running of the [PendingTask] was successful or not. - * @param rescheduled If the task failed but should run again, the task is rescheduled to run again in the future. */ - @UiThread fun complete(taskId: Long, successful: Boolean, rescheduled: Boolean) + @UiThread fun complete(taskId: Long, successful: Boolean) /** * There was an error recorded to Wendy for this [PendingTask]. diff --git a/wendy/src/main/java/com/levibostian/wendy/listeners/TaskRunnerListener.kt b/wendy/src/main/java/com/levibostian/wendy/listeners/TaskRunnerListener.kt index 4290965..3b357dc 100644 --- a/wendy/src/main/java/com/levibostian/wendy/listeners/TaskRunnerListener.kt +++ b/wendy/src/main/java/com/levibostian/wendy/listeners/TaskRunnerListener.kt @@ -56,9 +56,8 @@ interface TaskRunnerListener { * * @param success Indicates if the task that was run by the task runner was run successfully or not. * @param task The [PendingTask] that was run. - * @param rescheduled If the task failed but should run again, the task is rescheduled to run again in the future. */ - @UiThread fun taskComplete(success: Boolean, task: PendingTask, rescheduled: Boolean) + @UiThread fun taskComplete(success: Boolean, task: PendingTask) /** * The task runner has completed running all of it's tasks. diff --git a/wendy/src/main/java/com/levibostian/wendy/service/PendingTasksRunner.kt b/wendy/src/main/java/com/levibostian/wendy/service/PendingTasksRunner.kt index e7e56ab..29f25b4 100644 --- a/wendy/src/main/java/com/levibostian/wendy/service/PendingTasksRunner.kt +++ b/wendy/src/main/java/com/levibostian/wendy/service/PendingTasksRunner.kt @@ -102,12 +102,12 @@ internal class PendingTasksRunner(val context: Context, LogUtil.d("Task: $taskToRun ran successful. Deleting it.") pendingTasksManager.deleteTask(persistedPendingTaskId) runJobResult = PendingTasksRunnerJobRunResult.SUCCESSFUL - WendyConfig.logTaskComplete(taskToRun, true, false) + WendyConfig.logTaskComplete(taskToRun, true) } PendingTaskResult.FAILED -> { LogUtil.d("Task: $taskToRun failed but is rescheduled. Skipping it.") runJobResult = PendingTasksRunnerJobRunResult.NOT_SUCCESSFUL - WendyConfig.logTaskComplete(taskToRun, false, true) + WendyConfig.logTaskComplete(taskToRun, false) } } From 537ebb8b76929736e3f8330695f3c497c3d3469b Mon Sep 17 00:00:00 2001 From: Levi Bostian Date: Tue, 17 Apr 2018 17:55:40 -0500 Subject: [PATCH 4/6] Change paramter for runAllTasks to a filter object instead of a groupId string. --- .../main/java/com/levibostian/wendyexample/MainActivity.kt | 4 ++-- .../com/levibostian/wendyexample/PendingStatusTextView.kt | 2 +- .../java/com/levibostian/wendy/db/PendingTasksManager.kt | 5 +++-- .../main/java/com/levibostian/wendy/job/PendingTasksJob.kt | 2 +- .../com/levibostian/wendy/service/PendingTasksRunner.kt | 3 +-- wendy/src/main/java/com/levibostian/wendy/service/Wendy.kt | 7 ++++--- .../java/com/levibostian/wendy/types/RunAllTasksFilter.kt | 3 +++ 7 files changed, 15 insertions(+), 11 deletions(-) create mode 100644 wendy/src/main/java/com/levibostian/wendy/types/RunAllTasksFilter.kt diff --git a/app/src/main/java/com/levibostian/wendyexample/MainActivity.kt b/app/src/main/java/com/levibostian/wendyexample/MainActivity.kt index 8465e55..649cd47 100644 --- a/app/src/main/java/com/levibostian/wendyexample/MainActivity.kt +++ b/app/src/main/java/com/levibostian/wendyexample/MainActivity.kt @@ -42,7 +42,7 @@ class MainActivity : AppCompatActivity(), TaskRunnerListener { WendyConfig.automaticallyRunTasks = activity_main_automatically_run_tasks_checkbox.isChecked activity_main_run_all_tasks_button.setOnClickListener { - Wendy.sharedInstance().runTasks() + Wendy.sharedInstance().runTasks(null) } activity_main_tasks_recyclerview.layoutManager = LinearLayoutManager(this) @@ -71,7 +71,7 @@ class MainActivity : AppCompatActivity(), TaskRunnerListener { } override fun taskSkipped(reason: ReasonPendingTaskSkipped, task: PendingTask) { } - override fun taskComplete(success: Boolean, task: PendingTask, rescheduled: Boolean) { + override fun taskComplete(success: Boolean, task: PendingTask) { Handler().postDelayed({ refreshListOfTasks() }, 1000) diff --git a/app/src/main/java/com/levibostian/wendyexample/PendingStatusTextView.kt b/app/src/main/java/com/levibostian/wendyexample/PendingStatusTextView.kt index 1656ffc..dac8e32 100644 --- a/app/src/main/java/com/levibostian/wendyexample/PendingStatusTextView.kt +++ b/app/src/main/java/com/levibostian/wendyexample/PendingStatusTextView.kt @@ -44,7 +44,7 @@ class PendingStatusTextView : TextView, PendingTaskStatusListener { setTextColor(ContextCompat.getColor(mContext, android.R.color.holo_blue_dark)) } - override fun complete(taskId: Long, successful: Boolean, rescheduled: Boolean) { + override fun complete(taskId: Long, successful: Boolean) { text = if (successful) "Success!" else "Failed!" setTextColor(ContextCompat.getColor(mContext, if (successful) android.R.color.holo_green_dark else android.R.color.holo_orange_dark)) } diff --git a/wendy/src/main/java/com/levibostian/wendy/db/PendingTasksManager.kt b/wendy/src/main/java/com/levibostian/wendy/db/PendingTasksManager.kt index c025054..0adac87 100644 --- a/wendy/src/main/java/com/levibostian/wendy/db/PendingTasksManager.kt +++ b/wendy/src/main/java/com/levibostian/wendy/db/PendingTasksManager.kt @@ -6,6 +6,7 @@ import com.levibostian.wendy.extension.getTaskAssertPopulated import com.levibostian.wendy.service.PendingTask import com.levibostian.wendy.service.Wendy import com.levibostian.wendy.service.PendingTasksRunner +import com.levibostian.wendy.types.RunAllTasksFilter import com.levibostian.wendy.util.LogUtil import org.jetbrains.anko.db.* import java.util.* @@ -153,7 +154,7 @@ internal class PendingTasksManager(context: Context) { } @Synchronized - internal fun getNextTaskToRun(afterTaskId: Long = 0, filter: PendingTasksRunner.RunAllTasksFilter? = null): PendingTask? { + internal fun getNextTaskToRun(afterTaskId: Long = 0, filter: RunAllTasksFilter? = null): PendingTask? { return db.use { var whereArgs = "(${PersistedPendingTask.COLUMN_ID} > $afterTaskId) AND (${PersistedPendingTask.COLUMN_MANUALLY_RUN} = ${PersistedPendingTask.NOT_MANUALLY_RUN})" @@ -168,7 +169,7 @@ internal class PendingTasksManager(context: Context) { } @Synchronized - internal fun getTotalNumberOfTasksForRunnerToRun(filter: PendingTasksRunner.RunAllTasksFilter? = null): Int { + internal fun getTotalNumberOfTasksForRunnerToRun(filter: RunAllTasksFilter? = null): Int { return db.use { var whereArgs = "(${PersistedPendingTask.COLUMN_MANUALLY_RUN} = ${PersistedPendingTask.NOT_MANUALLY_RUN})" diff --git a/wendy/src/main/java/com/levibostian/wendy/job/PendingTasksJob.kt b/wendy/src/main/java/com/levibostian/wendy/job/PendingTasksJob.kt index 21ddc11..a99dbc7 100644 --- a/wendy/src/main/java/com/levibostian/wendy/job/PendingTasksJob.kt +++ b/wendy/src/main/java/com/levibostian/wendy/job/PendingTasksJob.kt @@ -18,7 +18,7 @@ internal class PendingTasksJob : Job() { private fun runTheJob() { if (WendyConfig.automaticallyRunTasks) { LogUtil.d("Wendy configured to automatically run tasks. Running the periodically scheduled job.") - Wendy.sharedInstance().runTasks() + Wendy.sharedInstance().runTasks(null) } else LogUtil.d("Wendy configured to *not* automatically run tasks. Skipping execution of periodically scheduled job.") } diff --git a/wendy/src/main/java/com/levibostian/wendy/service/PendingTasksRunner.kt b/wendy/src/main/java/com/levibostian/wendy/service/PendingTasksRunner.kt index 29f25b4..dc36ea8 100644 --- a/wendy/src/main/java/com/levibostian/wendy/service/PendingTasksRunner.kt +++ b/wendy/src/main/java/com/levibostian/wendy/service/PendingTasksRunner.kt @@ -9,6 +9,7 @@ import android.support.annotation.WorkerThread import com.levibostian.wendy.* import com.levibostian.wendy.db.PendingTasksManager import com.levibostian.wendy.types.PendingTaskResult +import com.levibostian.wendy.types.RunAllTasksFilter internal class PendingTasksRunner(val context: Context, private val pendingTasksManager: PendingTasksManager) { @@ -155,8 +156,6 @@ internal class PendingTasksRunner(val context: Context, } - internal class RunAllTasksFilter(val groupId: String?) - /** * Internal purposes job runner result. * diff --git a/wendy/src/main/java/com/levibostian/wendy/service/Wendy.kt b/wendy/src/main/java/com/levibostian/wendy/service/Wendy.kt index 36bb883..36af390 100644 --- a/wendy/src/main/java/com/levibostian/wendy/service/Wendy.kt +++ b/wendy/src/main/java/com/levibostian/wendy/service/Wendy.kt @@ -16,6 +16,7 @@ import com.levibostian.wendy.logErrorRecorded import com.levibostian.wendy.logErrorResolved import com.levibostian.wendy.logNewTaskAdded import com.levibostian.wendy.types.PendingTaskResult +import com.levibostian.wendy.types.RunAllTasksFilter import com.levibostian.wendy.util.LogUtil /** @@ -151,8 +152,8 @@ class Wendy private constructor(context: Context, internal val tasksFactory: Pen * @param groupId Limit running of the tasks to only tasks of this specific group id. * @throws [RuntimeException] when in [WendyConfig.strict] mode and you say that your [PendingTask] was [PendingTaskResult.SUCCESSFUL] when you have an unresolved error recorded for that [PendingTask]. */ - fun runTasks(groupId: String? = null) { - PendingTasksRunner.PendingTasksRunnerAllTasksAsyncTask(runner, tasksManager).execute(PendingTasksRunner.RunAllTasksFilter(groupId)) + fun runTasks(filter: RunAllTasksFilter?) { + PendingTasksRunner.PendingTasksRunnerAllTasksAsyncTask(runner, tasksManager).execute(filter) } /** @@ -280,7 +281,7 @@ class Wendy private constructor(context: Context, internal val tasksFactory: Pen LogUtil.d("Task: $pendingTask successfully resolved previously recorded error.") val groupId: String? = pendingTask.groupId - if (groupId != null) runTasks(groupId) + if (groupId != null) runTasks(RunAllTasksFilter(groupId)) else runTaskIfAbleTo(pendingTask) return true diff --git a/wendy/src/main/java/com/levibostian/wendy/types/RunAllTasksFilter.kt b/wendy/src/main/java/com/levibostian/wendy/types/RunAllTasksFilter.kt new file mode 100644 index 0000000..28ea11a --- /dev/null +++ b/wendy/src/main/java/com/levibostian/wendy/types/RunAllTasksFilter.kt @@ -0,0 +1,3 @@ +package com.levibostian.wendy.types + +class RunAllTasksFilter(val groupId: String?) \ No newline at end of file From 0b9a65dedba86da9acb44e1223f47707fdbec837 Mon Sep 17 00:00:00 2001 From: Levi Bostian Date: Tue, 17 Apr 2018 18:28:22 -0500 Subject: [PATCH 5/6] fix bug when task runner is running a task when another task of the same type tries to get added --- .../wendy/db/PendingTasksManager.kt | 19 ++++++++++++++- .../wendy/db/PersistedPendingTask.kt | 4 ++-- .../wendy/service/PendingTasksRunner.kt | 14 +++++++++-- .../com/levibostian/wendy/service/Wendy.kt | 12 +++++++--- .../wendy/util/PendingTasksUtil.kt | 23 +++++++++++++++++++ 5 files changed, 64 insertions(+), 8 deletions(-) create mode 100644 wendy/src/main/java/com/levibostian/wendy/util/PendingTasksUtil.kt diff --git a/wendy/src/main/java/com/levibostian/wendy/db/PendingTasksManager.kt b/wendy/src/main/java/com/levibostian/wendy/db/PendingTasksManager.kt index 0adac87..431d64f 100644 --- a/wendy/src/main/java/com/levibostian/wendy/db/PendingTasksManager.kt +++ b/wendy/src/main/java/com/levibostian/wendy/db/PendingTasksManager.kt @@ -1,5 +1,6 @@ package com.levibostian.wendy.db +import android.content.ContentValues import android.content.Context import com.levibostian.wendy.extension.getPendingTask import com.levibostian.wendy.extension.getTaskAssertPopulated @@ -30,7 +31,7 @@ internal class PendingTasksManager(context: Context) { val persistedPendingTask = PersistedPendingTask.fromPendingTask(pendingTaskToAdd) return db.use { - persistedPendingTask.createdAt = Date().time + persistedPendingTask.createdAt = Date().time // Very important. This determines the sort order of when tasks run by the task runner. createdAt needs to be set by Wendy internally and only modified by Wendy under certain circumstances. val id = insert(PersistedPendingTask.TABLE_NAME, PersistedPendingTask.COLUMN_CREATED_AT to persistedPendingTask.createdAt, @@ -59,6 +60,7 @@ internal class PendingTasksManager(context: Context) { return db.use { select(PersistedPendingTask.TABLE_NAME) .whereArgs("(${PersistedPendingTask.COLUMN_GROUP_ID} = ${pendingTask.groupId})") + .orderBy(PersistedPendingTask.COLUMN_CREATED_AT, SqlOrderDirection.ASC) .exec { val tasksInGroup: List = parseList(classParser()) tasksInGroup[0].id == taskId @@ -66,6 +68,18 @@ internal class PendingTasksManager(context: Context) { } } + /** + * Note: It's assumed that you have checked if taskId exists. + */ + @Synchronized + internal fun sendPendingTaskToEndOfTheLine(taskId: Long) { + db.use { + update(PersistedPendingTask.TABLE_NAME, PersistedPendingTask.COLUMN_CREATED_AT to Date()) + .whereArgs("(${PersistedPendingTask.COLUMN_ID} = $taskId)") + .exec() + } + } + @Synchronized internal fun getRandomTaskForTag(tag: String): PendingTask? { return db.use { @@ -118,6 +132,7 @@ internal class PendingTasksManager(context: Context) { internal fun getAllTasks(): List { return db.use { select(PersistedPendingTask.TABLE_NAME) + .orderBy(PersistedPendingTask.COLUMN_CREATED_AT, SqlOrderDirection.ASC) .exec { parseList(classParser()).map { it.getPendingTask() } } @@ -137,6 +152,7 @@ internal class PendingTasksManager(context: Context) { internal fun getAllErrors(): List { return db.use { select(PendingTaskError.TABLE_NAME) + .orderBy(PendingTaskError.COLUMN_CREATED_AT, SqlOrderDirection.ASC) .exec { parseList(classParser()).map { it.pendingTask = getPendingTaskTaskById(it.taskId)!! it @@ -164,6 +180,7 @@ internal class PendingTasksManager(context: Context) { select(PersistedPendingTask.TABLE_NAME) .whereArgs(whereArgs) + .orderBy(PersistedPendingTask.COLUMN_CREATED_AT, SqlOrderDirection.ASC) .exec { parseList(classParser()).firstOrNull()?.getPendingTask() } } } diff --git a/wendy/src/main/java/com/levibostian/wendy/db/PersistedPendingTask.kt b/wendy/src/main/java/com/levibostian/wendy/db/PersistedPendingTask.kt index ed7a76b..476054a 100644 --- a/wendy/src/main/java/com/levibostian/wendy/db/PersistedPendingTask.kt +++ b/wendy/src/main/java/com/levibostian/wendy/db/PersistedPendingTask.kt @@ -18,8 +18,8 @@ internal class PersistedPendingTask(var id: Long, // SQL primary key auto increm companion object { internal const val TABLE_NAME = "PendingTask" - internal const val COLUMN_ID = "id" // Primary key of SQL. Maps to PendingTask's taskId property. - internal const val COLUMN_CREATED_AT = "created_at" + internal const val COLUMN_ID = "id" // Primary key of SQL. Maps to PendingTask's taskId property. Simply used as an identifier. Is not used for sort order of when PendingTasks are run by the task runner. + internal const val COLUMN_CREATED_AT = "created_at" // Used to determine the sort order of when PendingTasks are run by the task runner. internal const val COLUMN_MANUALLY_RUN = "manually_run" // 0 == false, 1 == true internal const val COLUMN_GROUP_ID = "group_id" internal const val COLUMN_DATA_ID = "data_id" diff --git a/wendy/src/main/java/com/levibostian/wendy/service/PendingTasksRunner.kt b/wendy/src/main/java/com/levibostian/wendy/service/PendingTasksRunner.kt index dc36ea8..8cc4ead 100644 --- a/wendy/src/main/java/com/levibostian/wendy/service/PendingTasksRunner.kt +++ b/wendy/src/main/java/com/levibostian/wendy/service/PendingTasksRunner.kt @@ -10,6 +10,7 @@ import com.levibostian.wendy.* import com.levibostian.wendy.db.PendingTasksManager import com.levibostian.wendy.types.PendingTaskResult import com.levibostian.wendy.types.RunAllTasksFilter +import com.levibostian.wendy.util.PendingTasksUtil internal class PendingTasksRunner(val context: Context, private val pendingTasksManager: PendingTasksManager) { @@ -85,6 +86,7 @@ internal class PendingTasksRunner(val context: Context, return PendingTasksRunnerJobRunResult.SKIPPED_UNRESOLVED_RECORDED_ERROR } + PendingTasksUtil.resetRerunCurrentlyRunningPendingTask(context) currentlyRunningTask = taskToRun WendyConfig.logTaskRunning(taskToRun) @@ -100,8 +102,16 @@ internal class PendingTasksRunner(val context: Context, if (WendyConfig.strict) throw RuntimeException(errorMessage) else LogUtil.w(errorMessage) } - LogUtil.d("Task: $taskToRun ran successful. Deleting it.") - pendingTasksManager.deleteTask(persistedPendingTaskId) + LogUtil.d("Task: $taskToRun ran successful.") + if (PendingTasksUtil.getRerunCurrentlyRunningPendingTask(context)) { + LogUtil.d("Task: $taskToRun is set to re-run. Not deleting it.") + pendingTasksManager.sendPendingTaskToEndOfTheLine(persistedPendingTaskId) + } else { + LogUtil.d("Deleting task: $taskToRun.") + pendingTasksManager.deleteTask(persistedPendingTaskId) + } + PendingTasksUtil.resetRerunCurrentlyRunningPendingTask(context) + runJobResult = PendingTasksRunnerJobRunResult.SUCCESSFUL WendyConfig.logTaskComplete(taskToRun, true) } diff --git a/wendy/src/main/java/com/levibostian/wendy/service/Wendy.kt b/wendy/src/main/java/com/levibostian/wendy/service/Wendy.kt index 36af390..b800b5d 100644 --- a/wendy/src/main/java/com/levibostian/wendy/service/Wendy.kt +++ b/wendy/src/main/java/com/levibostian/wendy/service/Wendy.kt @@ -18,11 +18,12 @@ import com.levibostian.wendy.logNewTaskAdded import com.levibostian.wendy.types.PendingTaskResult import com.levibostian.wendy.types.RunAllTasksFilter import com.levibostian.wendy.util.LogUtil +import com.levibostian.wendy.util.PendingTasksUtil /** * How you interact with Wendy with [PendingTask] instances you create. Add tasks to Wendy to run, get a list of all the [PendingTask]s registered to Wendy, etc. */ -class Wendy private constructor(context: Context, internal val tasksFactory: PendingTasksFactory) { +class Wendy private constructor(private val context: Context, internal val tasksFactory: PendingTasksFactory) { companion object { private var instance: Wendy? = null @@ -108,9 +109,14 @@ class Wendy private constructor(context: Context, internal val tasksFactory: Pen tasksManager.getExistingTask(pendingTask)?.let { existingPersistedPendingTask -> if (doesErrorExist(existingPersistedPendingTask.id) && resolveErrorIfTaskExists) { resolveError(existingPersistedPendingTask.id) + return existingPersistedPendingTask.id + } + runner.currentlyRunningTask?.let { currentlyRunningTask -> + if (currentlyRunningTask.equals(existingPersistedPendingTask)) { + PendingTasksUtil.setRerunCurrentlyRunningPendingTask(context, true) + return existingPersistedPendingTask.id + } } - - return existingPersistedPendingTask.id } val addedTask = tasksManager.insertPendingTask(pendingTask) diff --git a/wendy/src/main/java/com/levibostian/wendy/util/PendingTasksUtil.kt b/wendy/src/main/java/com/levibostian/wendy/util/PendingTasksUtil.kt new file mode 100644 index 0000000..9e98a18 --- /dev/null +++ b/wendy/src/main/java/com/levibostian/wendy/util/PendingTasksUtil.kt @@ -0,0 +1,23 @@ +package com.levibostian.wendy.util + +import android.content.Context +import android.preference.PreferenceManager + +internal object PendingTasksUtil { + + private const val PREFIX = "WENDY_PREFS_" + private const val RERUN_CURRENTLY_RUNNING_PENDING_TASK_KEY = "${PREFIX}RERUN_CURRENTLY_RUNNING_PENDING_TASK_KEY" + + internal fun getRerunCurrentlyRunningPendingTask(context: Context): Boolean { + return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(RERUN_CURRENTLY_RUNNING_PENDING_TASK_KEY, false) + } + + internal fun setRerunCurrentlyRunningPendingTask(context: Context, rerun: Boolean) { + PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(RERUN_CURRENTLY_RUNNING_PENDING_TASK_KEY, rerun).commit() + } + + internal fun resetRerunCurrentlyRunningPendingTask(context: Context) { + this.setRerunCurrentlyRunningPendingTask(context, false) + } + +} \ No newline at end of file From 6ec1e762f57bca8d4234079dbed0720a952451b8 Mon Sep 17 00:00:00 2001 From: Levi Bostian Date: Wed, 18 Apr 2018 16:07:38 -0500 Subject: [PATCH 6/6] Fix various bugs since last 0.1.1-alpha release after doing some testing. --- .idea/caches/build_file_checksums.ser | Bin 588 -> 588 bytes .idea/misc.xml | 2 +- CHANGELOG.md | 12 +++++ Dangerfile | 6 ++- docs/wendy/alltypes/index.html | 7 +++ .../complete.html | 6 +-- .../-pending-task-status-listener/index.html | 2 +- .../-task-runner-listener/index.html | 2 +- .../-task-runner-listener/task-complete.html | 6 +-- .../-wendy/add-task.html | 2 + .../-wendy/index.html | 2 +- .../-wendy/run-tasks.html | 4 +- .../-run-all-tasks-filter/-init-.html | 18 +++++++ .../-run-all-tasks-filter/group-id.html | 15 ++++++ .../-run-all-tasks-filter/index.html | 45 ++++++++++++++++++ .../com.levibostian.wendy.types/index.html | 9 ++++ docs/wendy/index-outline.html | 40 +++++++++++++--- wendy/build.gradle | 2 +- .../java/com/levibostian/wendy/WendyConfig.kt | 2 +- .../wendy/db/PendingTasksManager.kt | 14 +++--- .../wendy/db/PersistedPendingTask.kt | 23 +++++++++ .../wendy/service/PendingTasksRunner.kt | 31 ++++++------ .../com/levibostian/wendy/service/Wendy.kt | 16 ++++--- .../wendy/types/RunAllTasksFilter.kt | 7 +++ .../wendy/util/PendingTasksUtil.kt | 2 +- 25 files changed, 222 insertions(+), 53 deletions(-) create mode 100644 docs/wendy/com.levibostian.wendy.types/-run-all-tasks-filter/-init-.html create mode 100644 docs/wendy/com.levibostian.wendy.types/-run-all-tasks-filter/group-id.html create mode 100644 docs/wendy/com.levibostian.wendy.types/-run-all-tasks-filter/index.html diff --git a/.idea/caches/build_file_checksums.ser b/.idea/caches/build_file_checksums.ser index 305f35a2d6228c6c0b85b4b798741cdf0b9df859..30161bbe35a12d3a7dbbc90752e994fc6c16e03a 100644 GIT binary patch delta 35 tcmV+;0Nnq~1k41Gm;~D3Xp)hf`w$66Pb|~&#_LgA%t~|00KAi@0i`-%5tIM` delta 35 tcmV+;0Nnq~1k41Gm;}GjB4Cl6`w+2%bPQKW`(p4KQx*pcDJYYt0i_wP4txLr diff --git a/.idea/misc.xml b/.idea/misc.xml index c0f68ed..99202cc 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -25,7 +25,7 @@ - + diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ee7e81..ae913f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +## [0.1.2-alpha] - 2018-04-18 +### Added +- Enforce a new best practice: All subclasses of a PendingTask must all have a groupId or none of them have a groupId. + +### Fixed +- While a PendingTask is running by the task runner, if a duplicate of that PendingTask gets added to Wendy, do not delete the PendingTask if it runs successfully. +- I forgot to include all of the parameters in the recursion calls to the task runner's runAllTasks() function call. That's fixed. + +### Changed +- **Breaking Change** Removed the `rescheduled` parameter in the Wendy listeners when a task is complete. It is always rescheduled if it fails so no need for the paramter. +- **Breaking Change** `Wendy.runAllTasks()` now takes an object for filtering instead of a string for `groupId`. + ## [0.1.1-alpha] - 2018-04-13 ### Added - Add `strict` mode to help developer during development of Wendy. diff --git a/Dangerfile b/Dangerfile index 8d380c1..77da1ce 100644 --- a/Dangerfile +++ b/Dangerfile @@ -1,7 +1,11 @@ if github.branch_for_base == "master" - if !git.modified_files.include? "config/*" + if !git.modified_files.include? "docs/*" warn 'Did you remember to generate documentation via dokku? (Hint: `./gradlew dokka`)' end + if !git.modified_files.include? "CHANGELOG.md" + fail 'You need to edit the CHANGELOG.md file.' + end + android_version_change.assert_version_name_changed("wendy/build.gradle") end if git.modified_files.include? "build.gradle" or git.modified_files.include? "wendy/build.gradle" diff --git a/docs/wendy/alltypes/index.html b/docs/wendy/alltypes/index.html index d16e33b..da2df86 100644 --- a/docs/wendy/alltypes/index.html +++ b/docs/wendy/alltypes/index.html @@ -52,6 +52,13 @@

All Types

+com.levibostian.wendy.types.RunAllTasksFilter + +

Filter the Wendy task runner to only run PendingTasks with the following options.

+ + + + com.levibostian.wendy.listeners.TaskRunnerListener

Listen to status updates from the Wendy task runner. You will get notified about the status of the task runner as well as general task updates on all of the tasks that it runs.

diff --git a/docs/wendy/com.levibostian.wendy.listeners/-pending-task-status-listener/complete.html b/docs/wendy/com.levibostian.wendy.listeners/-pending-task-status-listener/complete.html index b511cff..8bb65d3 100644 --- a/docs/wendy/com.levibostian.wendy.listeners/-pending-task-status-listener/complete.html +++ b/docs/wendy/com.levibostian.wendy.listeners/-pending-task-status-listener/complete.html @@ -8,15 +8,13 @@ wendy / com.levibostian.wendy.listeners / PendingTaskStatusListener / complete

complete

- -@UiThread abstract fun complete(taskId: Long, successful: Boolean, rescheduled: Boolean): Unit + +@UiThread abstract fun complete(taskId: Long, successful: Boolean): Unit

The task runner is done running the PendingTask. The task was either successful or not.

Parameters

taskId - The taskId of the PendingTask that just ran.

successful - Indicates if the running of the PendingTask was successful or not.

-

-rescheduled - If the task failed but should run again, the task is rescheduled to run again in the future.

diff --git a/docs/wendy/com.levibostian.wendy.listeners/-pending-task-status-listener/index.html b/docs/wendy/com.levibostian.wendy.listeners/-pending-task-status-listener/index.html index 3928e72..88b7870 100644 --- a/docs/wendy/com.levibostian.wendy.listeners/-pending-task-status-listener/index.html +++ b/docs/wendy/com.levibostian.wendy.listeners/-pending-task-status-listener/index.html @@ -23,7 +23,7 @@

Functions

complete

-abstract fun complete(taskId: Long, successful: Boolean, rescheduled: Boolean): Unit +abstract fun complete(taskId: Long, successful: Boolean): Unit

The task runner is done running the PendingTask. The task was either successful or not.

diff --git a/docs/wendy/com.levibostian.wendy.listeners/-task-runner-listener/index.html b/docs/wendy/com.levibostian.wendy.listeners/-task-runner-listener/index.html index 83814f0..f95969a 100644 --- a/docs/wendy/com.levibostian.wendy.listeners/-task-runner-listener/index.html +++ b/docs/wendy/com.levibostian.wendy.listeners/-task-runner-listener/index.html @@ -67,7 +67,7 @@

Functions

taskComplete

-abstract fun taskComplete(success: Boolean, task: PendingTask, rescheduled: Boolean): Unit +abstract fun taskComplete(success: Boolean, task: PendingTask): Unit

Task has either successfully run or failed it's run by the task runner.

diff --git a/docs/wendy/com.levibostian.wendy.listeners/-task-runner-listener/task-complete.html b/docs/wendy/com.levibostian.wendy.listeners/-task-runner-listener/task-complete.html index 50f8ef7..52d1a1c 100644 --- a/docs/wendy/com.levibostian.wendy.listeners/-task-runner-listener/task-complete.html +++ b/docs/wendy/com.levibostian.wendy.listeners/-task-runner-listener/task-complete.html @@ -8,15 +8,13 @@ wendy / com.levibostian.wendy.listeners / TaskRunnerListener / taskComplete

taskComplete

- -@UiThread abstract fun taskComplete(success: Boolean, task: PendingTask, rescheduled: Boolean): Unit + +@UiThread abstract fun taskComplete(success: Boolean, task: PendingTask): Unit

Task has either successfully run or failed it's run by the task runner.

Parameters

success - Indicates if the task that was run by the task runner was run successfully or not.

task - The PendingTask that was run.

-

-rescheduled - If the task failed but should run again, the task is rescheduled to run again in the future.

diff --git a/docs/wendy/com.levibostian.wendy.service/-wendy/add-task.html b/docs/wendy/com.levibostian.wendy.service/-wendy/add-task.html index b40b809..25d22da 100644 --- a/docs/wendy/com.levibostian.wendy.service/-wendy/add-task.html +++ b/docs/wendy/com.levibostian.wendy.service/-wendy/add-task.html @@ -20,5 +20,7 @@

Parameters

Exceptions

RuntimeException - Wendy will check to make sure that you have remembered to add your argument's PendingTask subclass to your instance of PendingTasksFactory when you call this method. If your PendingTasksFactory returns null (which probably means that you forgot to include a PendingTask) then an RuntimeException will be thrown.

+

+IllegalArgumentException - If your PendingTask subclass does not follow the enforced best practice of: All subclasses of a PendingTask must all have a groupId or none of them have a groupId.

diff --git a/docs/wendy/com.levibostian.wendy.service/-wendy/index.html b/docs/wendy/com.levibostian.wendy.service/-wendy/index.html index 0c620c2..6dfb732 100644 --- a/docs/wendy/com.levibostian.wendy.service/-wendy/index.html +++ b/docs/wendy/com.levibostian.wendy.service/-wendy/index.html @@ -108,7 +108,7 @@

Functions

runTasks

-fun runTasks(groupId: String? = null): Unit +fun runTasks(filter: RunAllTasksFilter?): Unit

Manually run all pending tasks. Wendy takes care of running this periodically for you, but you can manually run tasks here.

diff --git a/docs/wendy/com.levibostian.wendy.service/-wendy/run-tasks.html b/docs/wendy/com.levibostian.wendy.service/-wendy/run-tasks.html index befebb5..5b6e878 100644 --- a/docs/wendy/com.levibostian.wendy.service/-wendy/run-tasks.html +++ b/docs/wendy/com.levibostian.wendy.service/-wendy/run-tasks.html @@ -8,8 +8,8 @@ wendy / com.levibostian.wendy.service / Wendy / runTasks

runTasks

- -fun runTasks(groupId: String? = null): Unit + +fun runTasks(filter: RunAllTasksFilter?): Unit

Manually run all pending tasks. Wendy takes care of running this periodically for you, but you can manually run tasks here.

Note: This will run all tasks even if you use WendyConfig.automaticallyRunTasks to enable/disable running of all the PendingTasks.

Parameters

diff --git a/docs/wendy/com.levibostian.wendy.types/-run-all-tasks-filter/-init-.html b/docs/wendy/com.levibostian.wendy.types/-run-all-tasks-filter/-init-.html new file mode 100644 index 0000000..aba167d --- /dev/null +++ b/docs/wendy/com.levibostian.wendy.types/-run-all-tasks-filter/-init-.html @@ -0,0 +1,18 @@ + + + +RunAllTasksFilter.<init> - wendy + + + +wendy / com.levibostian.wendy.types / RunAllTasksFilter / <init>
+
+

<init>

+ +RunAllTasksFilter(groupId: String?) +

Filter the Wendy task runner to only run PendingTasks with the following options.

+

Parameters

+

+groupId - Filter running all of the PendingTasks in the task runner by this specific groupId.

+ + diff --git a/docs/wendy/com.levibostian.wendy.types/-run-all-tasks-filter/group-id.html b/docs/wendy/com.levibostian.wendy.types/-run-all-tasks-filter/group-id.html new file mode 100644 index 0000000..5b05739 --- /dev/null +++ b/docs/wendy/com.levibostian.wendy.types/-run-all-tasks-filter/group-id.html @@ -0,0 +1,15 @@ + + + +RunAllTasksFilter.groupId - wendy + + + +wendy / com.levibostian.wendy.types / RunAllTasksFilter / groupId
+
+

groupId

+ +val groupId: String? +

Filter running all of the PendingTasks in the task runner by this specific groupId.

+ + diff --git a/docs/wendy/com.levibostian.wendy.types/-run-all-tasks-filter/index.html b/docs/wendy/com.levibostian.wendy.types/-run-all-tasks-filter/index.html new file mode 100644 index 0000000..479bc12 --- /dev/null +++ b/docs/wendy/com.levibostian.wendy.types/-run-all-tasks-filter/index.html @@ -0,0 +1,45 @@ + + + +RunAllTasksFilter - wendy + + + +wendy / com.levibostian.wendy.types / RunAllTasksFilter
+
+

RunAllTasksFilter

+class RunAllTasksFilter +

Filter the Wendy task runner to only run PendingTasks with the following options.

+

Parameters

+

+groupId - Filter running all of the PendingTasks in the task runner by this specific groupId.

+

Constructors

+ + + + + + + +
+

<init>

+
+RunAllTasksFilter(groupId: String?) +

Filter the Wendy task runner to only run PendingTasks with the following options.

+
+

Properties

+ + + + + + + +
+

groupId

+
+val groupId: String? +

Filter running all of the PendingTasks in the task runner by this specific groupId.

+
+ + diff --git a/docs/wendy/com.levibostian.wendy.types/index.html b/docs/wendy/com.levibostian.wendy.types/index.html index 122fe7a..a98d9cd 100644 --- a/docs/wendy/com.levibostian.wendy.types/index.html +++ b/docs/wendy/com.levibostian.wendy.types/index.html @@ -29,6 +29,15 @@

Types

Reasons why a PendingTask was skipped by the task runner.

+ + +

RunAllTasksFilter

+ + +class RunAllTasksFilter +

Filter the Wendy task runner to only run PendingTasks with the following options.

+ + diff --git a/docs/wendy/index-outline.html b/docs/wendy/index-outline.html index b02fac3..6b41d61 100644 --- a/docs/wendy/index-outline.html +++ b/docs/wendy/index-outline.html @@ -89,7 +89,7 @@ -@UiThread abstract fun complete(taskId: Long, successful: Boolean, rescheduled: Boolean): Unit
+@UiThread abstract fun complete(taskId: Long, successful: Boolean): Unit
@UiThread abstract fun errorRecorded(taskId: Long, errorMessage: String?, errorId: String?): Unit
@UiThread abstract fun errorResolved(taskId: Long): Unit
@UiThread abstract fun running(taskId: Long): Unit
@@ -177,6 +177,20 @@ +class RunAllTasksFilter
+ interface TaskRunnerListener
+class RunAllTasksFilter
+ diff --git a/wendy/build.gradle b/wendy/build.gradle index 5b52b21..adaf152 100644 --- a/wendy/build.gradle +++ b/wendy/build.gradle @@ -10,7 +10,7 @@ android { minSdkVersion 16 targetSdkVersion 27 versionCode 1 - versionName "0.1.1-alpha" + versionName "0.1.2-alpha" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } diff --git a/wendy/src/main/java/com/levibostian/wendy/WendyConfig.kt b/wendy/src/main/java/com/levibostian/wendy/WendyConfig.kt index 139756b..f004b8b 100644 --- a/wendy/src/main/java/com/levibostian/wendy/WendyConfig.kt +++ b/wendy/src/main/java/com/levibostian/wendy/WendyConfig.kt @@ -85,7 +85,7 @@ class WendyConfig { taskStatusListeners.add(TaskStatusListener(taskId, WeakReference(listener))) - if (tasksRunner.currentlyRunningTask?.taskId?.equals(taskId) == true) listener.running(taskId) + if (tasksRunner.currentlyRunningTask?.id?.equals(taskId) == true) listener.running(taskId) } } diff --git a/wendy/src/main/java/com/levibostian/wendy/db/PendingTasksManager.kt b/wendy/src/main/java/com/levibostian/wendy/db/PendingTasksManager.kt index 431d64f..e4df540 100644 --- a/wendy/src/main/java/com/levibostian/wendy/db/PendingTasksManager.kt +++ b/wendy/src/main/java/com/levibostian/wendy/db/PendingTasksManager.kt @@ -59,10 +59,10 @@ internal class PendingTasksManager(context: Context) { return db.use { select(PersistedPendingTask.TABLE_NAME) - .whereArgs("(${PersistedPendingTask.COLUMN_GROUP_ID} = ${pendingTask.groupId})") + .whereArgs("(${PersistedPendingTask.COLUMN_GROUP_ID} = '${pendingTask.groupId}')") .orderBy(PersistedPendingTask.COLUMN_CREATED_AT, SqlOrderDirection.ASC) .exec { - val tasksInGroup: List = parseList(classParser()) + val tasksInGroup: List = parseList(classParser()) tasksInGroup[0].id == taskId } } @@ -74,7 +74,7 @@ internal class PendingTasksManager(context: Context) { @Synchronized internal fun sendPendingTaskToEndOfTheLine(taskId: Long) { db.use { - update(PersistedPendingTask.TABLE_NAME, PersistedPendingTask.COLUMN_CREATED_AT to Date()) + update(PersistedPendingTask.TABLE_NAME, PersistedPendingTask.COLUMN_CREATED_AT to Date().time) .whereArgs("(${PersistedPendingTask.COLUMN_ID} = $taskId)") .exec() } @@ -84,7 +84,7 @@ internal class PendingTasksManager(context: Context) { internal fun getRandomTaskForTag(tag: String): PendingTask? { return db.use { select(PersistedPendingTask.TABLE_NAME) - .whereArgs("(${PersistedPendingTask.COLUMN_TAG} = $tag)") + .whereArgs("(${PersistedPendingTask.COLUMN_TAG} = '$tag')") .exec { parseList(classParser()).firstOrNull()?.getPendingTask() } } } @@ -114,7 +114,7 @@ internal class PendingTasksManager(context: Context) { internal fun getLatestError(pendingTaskId: Long): PendingTaskError? { return db.use { select(PendingTaskError.TABLE_NAME) - .whereArgs("${PendingTaskError.COLUMN_TASK_ID} = '$pendingTaskId'") + .whereArgs("${PendingTaskError.COLUMN_TASK_ID} = $pendingTaskId") .exec { parseOpt(classParser()) } } } @@ -175,7 +175,7 @@ internal class PendingTasksManager(context: Context) { var whereArgs = "(${PersistedPendingTask.COLUMN_ID} > $afterTaskId) AND (${PersistedPendingTask.COLUMN_MANUALLY_RUN} = ${PersistedPendingTask.NOT_MANUALLY_RUN})" filter?.groupId?.let { filterByGroupId -> - whereArgs += " AND (${PersistedPendingTask.COLUMN_GROUP_ID} = $filterByGroupId)" + whereArgs += " AND (${PersistedPendingTask.COLUMN_GROUP_ID} = '$filterByGroupId')" } select(PersistedPendingTask.TABLE_NAME) @@ -191,7 +191,7 @@ internal class PendingTasksManager(context: Context) { var whereArgs = "(${PersistedPendingTask.COLUMN_MANUALLY_RUN} = ${PersistedPendingTask.NOT_MANUALLY_RUN})" filter?.groupId?.let { filterByGroupId -> - whereArgs += " AND (${PersistedPendingTask.COLUMN_GROUP_ID} = $filterByGroupId)" + whereArgs += " AND (${PersistedPendingTask.COLUMN_GROUP_ID} = '$filterByGroupId')" } select(PersistedPendingTask.TABLE_NAME) diff --git a/wendy/src/main/java/com/levibostian/wendy/db/PersistedPendingTask.kt b/wendy/src/main/java/com/levibostian/wendy/db/PersistedPendingTask.kt index 476054a..52cb8e9 100644 --- a/wendy/src/main/java/com/levibostian/wendy/db/PersistedPendingTask.kt +++ b/wendy/src/main/java/com/levibostian/wendy/db/PersistedPendingTask.kt @@ -48,6 +48,29 @@ internal class PersistedPendingTask(var id: Long, // SQL primary key auto increm return if (manuallyRun) MANUALLY_RUN else NOT_MANUALLY_RUN } + /** + * Run comparisons between two instances of [PersistedPendingTask]. + */ + override fun equals(other: Any?): Boolean { + if (other !is PersistedPendingTask) return false + + // If the tasks have the same task id, we can assume they are the same already. + if (other.id == this.id) return true + + // If they have the same dataId and tag, then they are the same in SQL unique terms. + return other.dataId == this.dataId && + other.tag == this.tag + } + + /** + * Your typical Java hashCode() function to match [equals]. + */ + override fun hashCode(): Int { + var result = dataId?.hashCode() ?: 0 + result = 31 * result + tag.hashCode() + return result + } + override fun toString(): String { val dateFormatter = SimpleDateFormat("yyyy.MM.dd G 'at' HH:mm:ss z", Locale.ENGLISH) diff --git a/wendy/src/main/java/com/levibostian/wendy/service/PendingTasksRunner.kt b/wendy/src/main/java/com/levibostian/wendy/service/PendingTasksRunner.kt index 8cc4ead..1ee2dce 100644 --- a/wendy/src/main/java/com/levibostian/wendy/service/PendingTasksRunner.kt +++ b/wendy/src/main/java/com/levibostian/wendy/service/PendingTasksRunner.kt @@ -8,6 +8,8 @@ import kotlin.collections.ArrayList import android.support.annotation.WorkerThread import com.levibostian.wendy.* import com.levibostian.wendy.db.PendingTasksManager +import com.levibostian.wendy.db.PersistedPendingTask +import com.levibostian.wendy.extension.getPendingTask import com.levibostian.wendy.types.PendingTaskResult import com.levibostian.wendy.types.RunAllTasksFilter import com.levibostian.wendy.util.PendingTasksUtil @@ -18,7 +20,7 @@ internal class PendingTasksRunner(val context: Context, private var lastSuccessfulOrFailedTaskId: Long = 0 private var failedTasksGroups: ArrayList = arrayListOf() - internal var currentlyRunningTask: PendingTask? = null + @Volatile internal var currentlyRunningTask: PersistedPendingTask? = null @Synchronized @WorkerThread @@ -72,30 +74,29 @@ internal class PendingTasksRunner(val context: Context, @Synchronized @WorkerThread fun runTask(taskId: Long): PendingTasksRunnerJobRunResult { - val persistedPendingTaskId: Long = pendingTasksManager.getTaskByTaskId(taskId)?.id ?: return PendingTasksRunnerJobRunResult.SKIPPED_TASK_DOESNT_EXIST - val taskToRun: PendingTask = pendingTasksManager.getPendingTaskTaskById(taskId)!! + val persistedPendingTask = pendingTasksManager.getTaskByTaskId(taskId) ?: return PendingTasksRunnerJobRunResult.SKIPPED_TASK_DOESNT_EXIST + val taskToRun: PendingTask = persistedPendingTask.getPendingTask() if (!taskToRun.isReadyToRun()) { WendyConfig.logTaskSkipped(taskToRun, ReasonPendingTaskSkipped.NOT_READY_TO_RUN) LogUtil.d("Task: $taskToRun is not ready to run. Skipping it.") return PendingTasksRunnerJobRunResult.SKIPPED_NOT_READY } - if (pendingTasksManager.getLatestError(taskToRun.taskId!!) != null) { + if (Wendy.shared.doesErrorExist(taskToRun.taskId!!)) { WendyConfig.logTaskSkipped(taskToRun, ReasonPendingTaskSkipped.UNRESOLVED_RECORDED_ERROR) LogUtil.d("Task: $taskToRun has a unresolved error recorded. Skipping it.") return PendingTasksRunnerJobRunResult.SKIPPED_UNRESOLVED_RECORDED_ERROR } PendingTasksUtil.resetRerunCurrentlyRunningPendingTask(context) - currentlyRunningTask = taskToRun + currentlyRunningTask = persistedPendingTask WendyConfig.logTaskRunning(taskToRun) LogUtil.d("Running task: $taskToRun.") val result = taskToRun.runTask() currentlyRunningTask = null - var runJobResult = PendingTasksRunnerJobRunResult.SUCCESSFUL - when (result) { + return when (result) { PendingTaskResult.SUCCESSFUL -> { if (Wendy.shared.doesErrorExist(taskToRun.taskId!!)) { val errorMessage = "You returned ${PendingTaskResult.SUCCESSFUL} for running your ${PendingTask::class.java.simpleName}, but you have unresolved issues for task: $taskToRun. You should resolve the previously recorded error to Wendy, or return ${PendingTaskResult.FAILED}." @@ -103,26 +104,26 @@ internal class PendingTasksRunner(val context: Context, } LogUtil.d("Task: $taskToRun ran successful.") - if (PendingTasksUtil.getRerunCurrentlyRunningPendingTask(context)) { + if (PendingTasksUtil.rerunCurrentlyRunningPendingTask(context)) { LogUtil.d("Task: $taskToRun is set to re-run. Not deleting it.") - pendingTasksManager.sendPendingTaskToEndOfTheLine(persistedPendingTaskId) + pendingTasksManager.sendPendingTaskToEndOfTheLine(taskId) } else { LogUtil.d("Deleting task: $taskToRun.") - pendingTasksManager.deleteTask(persistedPendingTaskId) + pendingTasksManager.deleteTask(taskId) } PendingTasksUtil.resetRerunCurrentlyRunningPendingTask(context) - runJobResult = PendingTasksRunnerJobRunResult.SUCCESSFUL WendyConfig.logTaskComplete(taskToRun, true) + + PendingTasksRunnerJobRunResult.SUCCESSFUL } PendingTaskResult.FAILED -> { LogUtil.d("Task: $taskToRun failed but is rescheduled. Skipping it.") - runJobResult = PendingTasksRunnerJobRunResult.NOT_SUCCESSFUL WendyConfig.logTaskComplete(taskToRun, false) + + PendingTasksRunnerJobRunResult.NOT_SUCCESSFUL } } - - return runJobResult } private fun resetRunner() { @@ -139,7 +140,7 @@ internal class PendingTasksRunner(val context: Context, val filterTasksToRun: RunAllTasksFilter? = params.filterNotNull().firstOrNull() val numTasksToRun = pendingTasksManager.getTotalNumberOfTasksForRunnerToRun(filterTasksToRun) - LogUtil.d("Running all tasks in task runner ${if (filterTasksToRun != null) "(with filter)" else ""}. Running total of: $numTasksToRun tasks.") + LogUtil.d("Running all tasks in task runner${if (filterTasksToRun != null) " (with filter)" else ""}. Running total of: $numTasksToRun tasks.") runner.runAllTasks(filterTasksToRun) diff --git a/wendy/src/main/java/com/levibostian/wendy/service/Wendy.kt b/wendy/src/main/java/com/levibostian/wendy/service/Wendy.kt index b800b5d..538e1c0 100644 --- a/wendy/src/main/java/com/levibostian/wendy/service/Wendy.kt +++ b/wendy/src/main/java/com/levibostian/wendy/service/Wendy.kt @@ -7,6 +7,7 @@ import com.levibostian.wendy.WendyConfig import com.levibostian.wendy.db.PendingTaskError import com.levibostian.wendy.db.PendingTasksManager import com.levibostian.wendy.db.PersistedPendingTask +import com.levibostian.wendy.extension.getPendingTask import com.levibostian.wendy.extension.getTaskAssertPopulated import com.levibostian.wendy.job.PendingTaskJobCreator import com.levibostian.wendy.job.PendingTasksJob @@ -100,23 +101,24 @@ class Wendy private constructor(private val context: Context, internal val tasks tasksFactory.getTaskAssertPopulated(pendingTask.tag) tasksManager.getRandomTaskForTag(pendingTask.tag)?.let { similarPendingTask -> - if (similarPendingTask.groupId == null && pendingTask.groupId != null || - similarPendingTask.groupId != null && pendingTask.groupId == null) { - throw IllegalArgumentException("All subclasses of a PendingTask must either **all** have a groupId or **none** of them have a groupId.") + if (similarPendingTask.groupId == null && pendingTask.groupId != null) { + throw IllegalArgumentException("Cannot add task: $pendingTask. All subclasses of a PendingTask must either **all** have a groupId or **none** of them have a groupId. Other ${pendingTask.tag}'s you have previously added does not have a groupId. The task you are trying to add does have a groupId.") + } + if (similarPendingTask.groupId != null && pendingTask.groupId == null) { + throw IllegalArgumentException("Cannot add task: $pendingTask. All subclasses of a PendingTask must either **all** have a groupId or **none** of them have a groupId. Other ${pendingTask.tag}'s you have previously added does have a groupId. The task you are trying to add does not have a groupId.") } } tasksManager.getExistingTask(pendingTask)?.let { existingPersistedPendingTask -> if (doesErrorExist(existingPersistedPendingTask.id) && resolveErrorIfTaskExists) { resolveError(existingPersistedPendingTask.id) - return existingPersistedPendingTask.id } runner.currentlyRunningTask?.let { currentlyRunningTask -> - if (currentlyRunningTask.equals(existingPersistedPendingTask)) { + if (currentlyRunningTask == existingPersistedPendingTask) { PendingTasksUtil.setRerunCurrentlyRunningPendingTask(context, true) - return existingPersistedPendingTask.id } } + return existingPersistedPendingTask.id } val addedTask = tasksManager.insertPendingTask(pendingTask) @@ -139,7 +141,7 @@ class Wendy private constructor(private val context: Context, internal val tasks LogUtil.d("Task is set to manually run. Skipping execution of newly added task: $pendingTask") return false } - if (isTaskAbleToManuallyRun(pendingTask.taskId!!)) { + if (!isTaskAbleToManuallyRun(pendingTask.taskId!!)) { LogUtil.d("Task is not able to manually run. Skipping execution of newly added task: $pendingTask") return false } diff --git a/wendy/src/main/java/com/levibostian/wendy/types/RunAllTasksFilter.kt b/wendy/src/main/java/com/levibostian/wendy/types/RunAllTasksFilter.kt index 28ea11a..efa2355 100644 --- a/wendy/src/main/java/com/levibostian/wendy/types/RunAllTasksFilter.kt +++ b/wendy/src/main/java/com/levibostian/wendy/types/RunAllTasksFilter.kt @@ -1,3 +1,10 @@ package com.levibostian.wendy.types +import com.levibostian.wendy.service.PendingTask + +/** + * Filter the Wendy task runner to only run [PendingTask]s with the following options. + * + * @param groupId Filter running all of the [PendingTask]s in the task runner by this specific groupId. + */ class RunAllTasksFilter(val groupId: String?) \ No newline at end of file diff --git a/wendy/src/main/java/com/levibostian/wendy/util/PendingTasksUtil.kt b/wendy/src/main/java/com/levibostian/wendy/util/PendingTasksUtil.kt index 9e98a18..5e635dd 100644 --- a/wendy/src/main/java/com/levibostian/wendy/util/PendingTasksUtil.kt +++ b/wendy/src/main/java/com/levibostian/wendy/util/PendingTasksUtil.kt @@ -8,7 +8,7 @@ internal object PendingTasksUtil { private const val PREFIX = "WENDY_PREFS_" private const val RERUN_CURRENTLY_RUNNING_PENDING_TASK_KEY = "${PREFIX}RERUN_CURRENTLY_RUNNING_PENDING_TASK_KEY" - internal fun getRerunCurrentlyRunningPendingTask(context: Context): Boolean { + internal fun rerunCurrentlyRunningPendingTask(context: Context): Boolean { return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(RERUN_CURRENTLY_RUNNING_PENDING_TASK_KEY, false) }