diff --git a/core/controllers/admin_maintenance.php b/core/controllers/admin_maintenance.php index b695cfcb..fba78a40 100644 --- a/core/controllers/admin_maintenance.php +++ b/core/controllers/admin_maintenance.php @@ -18,49 +18,172 @@ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. */ class Admin_Maintenance_Controller extends Admin_Controller { - public function index() { - $view = new Admin_View("admin.html"); - - $available_tasks = array( - new ArrayObject( - array("name" => "rebuild_images", - "description" => _("Rebuild out of date thumbnails and resizes")), + /** + * Get all available tasks + * @todo move task definition out into the modules + */ + private function _get_task_definitions() { + $dirty_count = graphics::find_dirty_images_query()->count(); + return array( + "graphics::rebuild_dirty_images" => new ArrayObject( + array("name" => _("Rebuild Images"), + "callback" => "graphics::rebuild_dirty_images", + "description" => ( + $dirty_count ? + sprintf( + _("You have %d out-of-date images"), $dirty_count) + : _("All your images are up to date")), + "severity" => $dirty_count ? log::WARNING : log::SUCCESS), ArrayObject::ARRAY_AS_PROPS)); + } + /** + * Show a list of all available, running and finished tasks. + */ + public function index() { + $query = Database::instance()->query( + "UPDATE `tasks` SET `state` = 'stalled' " . + "WHERE done = 0 " . + "AND state <> 'stalled' " . + "AND unix_timestamp(now()) - updated > 120"); + $stalled_count = $query->count(); + if ($stalled_count) { + log::warning("tasks", + sprintf(_("%d tasks are stalled"), $stalled_count), + sprintf(_("%sview%s"), + "", + "")); + } + + $view = new Admin_View("admin.html"); $view->content = new View("admin_maintenance.html"); - $view->content->available_tasks = $available_tasks; - $view->content->running_tasks = ORM::factory("task")->find_all(); + $view->content->task_definitions = $this->_get_task_definitions(); + $view->content->running_tasks = ORM::factory("task")->where("done", 0)->find_all(); + $view->content->finished_tasks = ORM::factory("task")->where("done", 1)->find_all(); + $view->content->csrf = access::csrf_token(); print $view; } - public function start($task_name) { + /** + * Start a new task + * @param string $task_callback + */ + public function start($task_callback) { + access::verify_csrf(); + + $task_definitions = $this->_get_task_definitions(); + $task = ORM::factory("task"); - $task->name = $task_name; + $task->callback = $task_callback; + $task->name = $task_definitions[$task_callback]->name; $task->percent_complete = 0; $task->status = ""; + $task->state = "started"; $task->context = serialize(array()); $task->save(); $view = new View("admin_maintenance_task.html"); + $view->csrf = access::csrf_token(); $view->task = $task; + + log::info("tasks", sprintf(_("Task %s started (task id %d)"), $task->name, $task->id), + html::anchor(url::site("admin/maintenance"), _("maintenance"))); print $view; } + /** + * Resume a stalled task + * @param string $task_id + */ + public function resume($task_id) { + access::verify_csrf(); + + $task = ORM::factory("task", $task_id); + if (!$task->loaded) { + throw new Exception("@todo MISSING_TASK"); + } + $view = new View("admin_maintenance_task.html"); + $view->csrf = access::csrf_token(); + $view->task = $task; + + log::info("tasks", sprintf(_("Task %s resumed (task id %d)"), $task->name, $task->id), + html::anchor(url::site("admin/maintenance"), _("maintenance"))); + print $view; + } + + /** + * Cancel a task. + * @param string $task_id + */ + public function cancel($task_id) { + access::verify_csrf(); + + $task = ORM::factory("task", $task_id); + if (!$task->loaded) { + throw new Exception("@todo MISSING_TASK"); + } + $task->done = 1; + $task->state = "cancelled"; + $task->save(); + + message::success(_("Task cancelled")); + url::redirect("admin/maintenance"); + } + + /** + * Remove a task. + * @param string $task_id + */ + public function remove($task_id) { + access::verify_csrf(); + + $task = ORM::factory("task", $task_id); + if (!$task->loaded) { + throw new Exception("@todo MISSING_TASK"); + } + $task->delete(); + message::success(_("Task removed")); + url::redirect("admin/maintenance"); + } + + /** + * Run a task. This will trigger the task to do a small amount of work, then it will report + * back with status on the task. + * @param string $task_id + */ public function run($task_id) { + access::verify_csrf(); + $task = ORM::factory("task", $task_id); if (!$task->loaded) { throw new Exception("@todo MISSING_TASK"); } - switch($task->name) { - case "rebuild_images": - graphics::rebuild_dirty_images($task); - } - + $task->state = "running"; + call_user_func_array($task->callback, array(&$task)); $task->save(); - print json_encode( - array("status" => "success", - "task" => $task->as_array())); + if ($task->done) { + switch ($task->state) { + case "success": + log::success("tasks", sprintf(_("Task %s completed (task id %d)"), $task->name, $task->id), + html::anchor(url::site("admin/maintenance"), _("maintenance"))); + message::success(_("Task completed successfully")); + break; + + case "error": + log::error("tasks", sprintf(_("Task %s failed (task id %d)"), $task->name, $task->id), + html::anchor(url::site("admin/maintenance"), _("maintenance"))); + message::success(_("Task failed")); + break; + } + print json_encode( + array("result" => "success", + "location" => url::site("admin/maintenance"))); + } else { + print json_encode( + array("result" => "in_progress", + "task" => $task->as_array())); + } } } diff --git a/core/helpers/access.php b/core/helpers/access.php index c6ee1fcc..d05f3df0 100644 --- a/core/helpers/access.php +++ b/core/helpers/access.php @@ -305,7 +305,8 @@ class access_Core { * Verify our Cross Site Request Forgery token is valid, else throw an exception. */ public static function verify_csrf() { - if (Input::instance()->post("csrf") !== Session::instance()->get("csrf")) { + $input = Input::instance(); + if ($input->post("csrf", $input->get("csrf", null)) !== Session::instance()->get("csrf")) { access::forbidden(); } } diff --git a/core/helpers/core_installer.php b/core/helpers/core_installer.php index 46eb24c6..c83d9bcb 100644 --- a/core/helpers/core_installer.php +++ b/core/helpers/core_installer.php @@ -128,11 +128,14 @@ class core_installer { ENGINE=InnoDB DEFAULT CHARSET=utf8;"); $db->query("CREATE TABLE `tasks` ( + `callback` varchar(255) default NULL, `context` text NOT NULL, - `done` boolean DEFAULT 0, + `done` boolean default 0, `id` int(9) NOT NULL auto_increment, + `updated` int(9) default NULL, `name` varchar(255) default NULL, `percent_complete` int(9) default 0, + `state` varchar(32) default NULL, `status` varchar(255) default NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;"); diff --git a/core/helpers/graphics.php b/core/helpers/graphics.php index 68aacaca..62bde88a 100644 --- a/core/helpers/graphics.php +++ b/core/helpers/graphics.php @@ -128,11 +128,18 @@ class graphics_Core { ->save($output_file); } + /** + * Stub. + * @todo implement this + */ + public static function compose($input_file, $output_file, $other_args) { + } + /** * Return a query result that locates all items with dirty images. * @return Database_Result Query result */ - private static function _find_dirty_images_query() { + public static function find_dirty_images_query() { return Database::instance()->query( "SELECT `id` FROM `items` " . "WHERE (`thumb_dirty` = 1 AND (`type` <> 'album' OR `right` - `left` > 1))" . @@ -147,12 +154,12 @@ class graphics_Core { $db = Database::instance(); $db->query("UPDATE `items` SET `thumb_dirty` = 1, `resize_dirty` = 1"); - $count = self::_find_dirty_images_query()->count(); + $count = self::find_dirty_images_query()->count(); if ($count) { message::warning( sprintf(_("%d of your photos are out of date. %sClick here to fix them%s"), $count, "", ""), "graphics_dirty"); } @@ -165,7 +172,7 @@ class graphics_Core { public static function rebuild_dirty_images($task) { $db = Database::instance(); - $result = self::_find_dirty_images_query(); + $result = self::find_dirty_images_query(); $remaining = $result->count(); $completed = $task->get("completed", 0); @@ -194,9 +201,9 @@ class graphics_Core { } $task->set("completed", $completed); - $task->done = ($remaining == 0); - - if ($task->done) { + if ($remaining == 0) { + $task->done = true; + $task->state = "success"; message::clear_permanent("graphics_dirty"); } } diff --git a/core/models/task.php b/core/models/task.php index 697ab7bc..b88e34b7 100644 --- a/core/models/task.php +++ b/core/models/task.php @@ -33,4 +33,11 @@ class Task_Model extends ORM { $context[$key] = $value; $this->context = serialize($context); } + + public function save() { + if (!empty($this->changed)) { + $this->updated = time(); + } + return parent::save(); + } } \ No newline at end of file diff --git a/core/views/admin_maintenance.html.php b/core/views/admin_maintenance.html.php index 0d9f6adb..263fea10 100644 --- a/core/views/admin_maintenance.html.php +++ b/core/views/admin_maintenance.html.php @@ -2,19 +2,34 @@

- +

- - +
+ + + + + + + @@ -25,6 +40,119 @@

- Task list goes here +
+ + + + + +
+ name ?> + description ?> - name") ?>" class="gDialogLink"> + callback?csrf=$csrf") ?>" + class="gDialogLink">
+ + + + + + + + + "> + + + + + + + +
+ + + + + + + + + +
+ updated) ?> + + name ?> + + done): ?> + state == "cancelled"): ?> + + + + state == "stalled"): ?> + + + percent_complete) ?> + + + status ?> + + state == "stalled"): ?> + id?csrf=$csrf") ?>" class="gDialogLink"> + + + + id?csrf=$csrf") ?>"> + + +
+
+ +
+

+ + + + + + + + + + + "> + + + + + + + +
+ + + + + + + + + +
+ updated) ?> + + name ?> + + state == "success"): ?> + + state == "error"): ?> + + state == "cancelled"): ?> + + + + status ?> + + done): ?> + id?csrf=$csrf") ?>"> + + + + id?csrf=$csrf") ?>"> + + + id?csrf=$csrf") ?>"> + + + +
diff --git a/core/views/admin_maintenance_task.html.php b/core/views/admin_maintenance_task.html.php index 4776ecaa..c31de876 100644 --- a/core/views/admin_maintenance_task.html.php +++ b/core/views/admin_maintenance_task.html.php @@ -3,7 +3,7 @@