MDL-46881 core: Allow adhoc tasks to be rescheduled
authorAndrew Nicols <andrew@nicols.co.uk>
Tue, 29 Jan 2019 01:17:19 +0000 (09:17 +0800)
committerAndrew Nicols <andrew@nicols.co.uk>
Wed, 30 Jan 2019 02:55:48 +0000 (10:55 +0800)
lib/classes/task/manager.php
lib/tests/adhoc_task_test.php
lib/upgrade.txt
mod/forum/classes/task/cron_task.php

index 5952018..87fe342 100644 (file)
@@ -129,7 +129,18 @@ class manager {
      * @return bool
      */
     protected static function task_is_scheduled($task) {
+        return false !== self::get_queued_adhoc_task_record($task);
+    }
+
+    /**
+     * Checks if the task with the same classname, component and customdata is already scheduled
+     *
+     * @param adhoc_task $task
+     * @return bool
+     */
+    protected static function get_queued_adhoc_task_record($task) {
         global $DB;
+
         $record = self::record_from_adhoc_task($task);
         $params = [$record->classname, $record->component, $record->customdata];
         $sql = 'classname = ? AND component = ? AND ' .
@@ -139,14 +150,38 @@ class manager {
             $params[] = $record->userid;
             $sql .= " AND userid = ? ";
         }
-        return $DB->record_exists_select('task_adhoc', $sql, $params);
+        return $DB->get_record_select('task_adhoc', $sql, $params);
+    }
+
+    /**
+     * Schedule a new task, or reschedule an existing adhoc task which has matching data.
+     *
+     * Only a task matching the same user, classname, component, and customdata will be rescheduled.
+     * If these values do not match exactly then a new task is scheduled.
+     *
+     * @param \core\task\adhoc_task $task - The new adhoc task information to store.
+     * @since Moodle 3.7
+     */
+    public static function reschedule_or_queue_adhoc_task(adhoc_task $task) : void {
+        global $DB;
+
+        if ($existingrecord = self::get_queued_adhoc_task_record($task)) {
+            // Only update the next run time if it is explicitly set on the task.
+            $nextruntime = $task->get_next_run_time();
+            if ($nextruntime && ($existingrecord->nextruntime != $nextruntime)) {
+                $DB->set_field('task_adhoc', 'nextruntime', $nextruntime, ['id' => $existingrecord->id]);
+            }
+        } else {
+            // There is nothing queued yet. Just queue as normal.
+            self::queue_adhoc_task($task);
+        }
     }
 
     /**
      * Queue an adhoc task to run in the background.
      *
      * @param \core\task\adhoc_task $task - The new adhoc task information to store.
-     * @param bool $checkforexisting - If set to true and the task with the same classname, component and customdata
+     * @param bool $checkforexisting - If set to true and the task with the same user, classname, component and customdata
      *     is already scheduled then it will not schedule a new task. Can be used only for ASAP tasks.
      * @return boolean - True if the config was saved.
      */
index 2b8b7d1..c5d7961 100644 (file)
@@ -150,6 +150,114 @@ class core_adhoc_task_testcase extends advanced_testcase {
         }
     }
 
+    /**
+     * Ensure that the reschedule_or_queue_adhoc_task function will schedule a new task if no tasks exist.
+     */
+    public function test_reschedule_or_queue_adhoc_task_no_existing() {
+        $this->resetAfterTest(true);
+
+        // Schedule adhoc task.
+        $task = new \core\task\adhoc_test_task();
+        $task->set_custom_data(['courseid' => 10]);
+        \core\task\manager::reschedule_or_queue_adhoc_task($task);
+        $this->assertEquals(1, count(\core\task\manager::get_adhoc_tasks('core\task\adhoc_test_task')));
+    }
+
+    /**
+     * Ensure that the reschedule_or_queue_adhoc_task function will schedule a new task if a task for the same user does
+     * not exist.
+     */
+    public function test_reschedule_or_queue_adhoc_task_different_user() {
+        $this->resetAfterTest(true);
+        $user = \core_user::get_user_by_username('admin');
+
+        // Schedule adhoc task.
+        $task = new \core\task\adhoc_test_task();
+        $task->set_custom_data(['courseid' => 10]);
+        \core\task\manager::reschedule_or_queue_adhoc_task($task);
+
+        // Schedule adhoc task for a different user.
+        $task = new \core\task\adhoc_test_task();
+        $task->set_custom_data(['courseid' => 10]);
+        $task->set_userid($user->id);
+        \core\task\manager::reschedule_or_queue_adhoc_task($task);
+
+        $this->assertEquals(2, count(\core\task\manager::get_adhoc_tasks('core\task\adhoc_test_task')));
+    }
+
+    /**
+     * Ensure that the reschedule_or_queue_adhoc_task function will schedule a new task if a task with different custom
+     * data exists.
+     */
+    public function test_reschedule_or_queue_adhoc_task_different_data() {
+        $this->resetAfterTest(true);
+
+        // Schedule adhoc task.
+        $task = new \core\task\adhoc_test_task();
+        $task->set_custom_data(['courseid' => 10]);
+        \core\task\manager::reschedule_or_queue_adhoc_task($task);
+
+        // Schedule adhoc task for a different user.
+        $task = new \core\task\adhoc_test_task();
+        $task->set_custom_data(['courseid' => 11]);
+        \core\task\manager::reschedule_or_queue_adhoc_task($task);
+
+        $this->assertEquals(2, count(\core\task\manager::get_adhoc_tasks('core\task\adhoc_test_task')));
+    }
+
+    /**
+     * Ensure that the reschedule_or_queue_adhoc_task function will not make any change for matching data if no time was
+     * specified.
+     */
+    public function test_reschedule_or_queue_adhoc_task_match_no_change() {
+        $this->resetAfterTest(true);
+
+        // Schedule adhoc task.
+        $task = new \core\task\adhoc_test_task();
+        $task->set_custom_data(['courseid' => 10]);
+        $task->set_next_run_time(time() + DAYSECS);
+        \core\task\manager::reschedule_or_queue_adhoc_task($task);
+
+        $before = \core\task\manager::get_adhoc_tasks('core\task\adhoc_test_task');
+
+        // Schedule the task again but do not specify a time.
+        $task = new \core\task\adhoc_test_task();
+        $task->set_custom_data(['courseid' => 10]);
+        \core\task\manager::reschedule_or_queue_adhoc_task($task);
+
+        $this->assertEquals(1, count(\core\task\manager::get_adhoc_tasks('core\task\adhoc_test_task')));
+        $this->assertEquals($before, \core\task\manager::get_adhoc_tasks('core\task\adhoc_test_task'));
+    }
+
+    /**
+     * Ensure that the reschedule_or_queue_adhoc_task function will update the run time if there are planned changes.
+     */
+    public function test_reschedule_or_queue_adhoc_task_match_update_runtime() {
+        $this->resetAfterTest(true);
+        $initialruntime = time() + DAYSECS;
+        $newruntime = time() + WEEKSECS;
+
+        // Schedule adhoc task.
+        $task = new \core\task\adhoc_test_task();
+        $task->set_custom_data(['courseid' => 10]);
+        $task->set_next_run_time($initialruntime);
+        \core\task\manager::reschedule_or_queue_adhoc_task($task);
+
+        $before = \core\task\manager::get_adhoc_tasks('core\task\adhoc_test_task');
+
+        // Schedule the task again.
+        $task = new \core\task\adhoc_test_task();
+        $task->set_custom_data(['courseid' => 10]);
+        $task->set_next_run_time($newruntime);
+        \core\task\manager::reschedule_or_queue_adhoc_task($task);
+
+        $tasks = \core\task\manager::get_adhoc_tasks('core\task\adhoc_test_task');
+        $this->assertEquals(1, count($tasks));
+        $this->assertNotEquals($before, $tasks);
+        $firsttask = reset($tasks);
+        $this->assertEquals($newruntime, $firsttask->get_next_run_time());
+    }
+
     /**
      * Test queue_adhoc_task "if not scheduled".
      */
index 85225fd..54e5523 100644 (file)
@@ -6,6 +6,8 @@ information provided here is intended especially for developers.
 * The method core_user::is_real_user() now returns false for userid = 0 parameter
 * 'mform1' dependencies (in themes, js...) will stop working because a randomly generated string has been added to the id
 attribute on forms to avoid collisions in forms loaded in AJAX requests.
+* A new method to allow queueing or rescheduling of an existing scheduled task was added. This allows an existing task
+  to be updated or queued as required. This new functionality can be found in \core\task\manager::reschedule_or_queue_adhoc_task.
 
 === 3.6 ===
 
index 74fdfd7..5b88606 100644 (file)
@@ -342,7 +342,7 @@ class cron_task extends \core\task\scheduled_task {
                 $task->set_userid($user->id);
                 $task->set_component('mod_forum');
                 $task->set_next_run_time($digesttime);
-                \core\task\manager::queue_adhoc_task($task, true);
+                \core\task\manager::reschedule_or_queue_adhoc_task($task);
                 $usercounts['digests']++;
                 $send = true;
             }