diff options
-rw-r--r-- | core/controllers/admin_maintenance.php | 159 | ||||
-rw-r--r-- | core/helpers/access.php | 3 | ||||
-rw-r--r-- | core/helpers/core_installer.php | 5 | ||||
-rw-r--r-- | core/helpers/graphics.php | 21 | ||||
-rw-r--r-- | core/models/task.php | 7 | ||||
-rw-r--r-- | core/views/admin_maintenance.html.php | 138 | ||||
-rw-r--r-- | core/views/admin_maintenance_task.html.php | 2 |
7 files changed, 302 insertions, 33 deletions
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"), + "<a href=\"" . url::site("admin/maintenance") . "\">", + "</a>")); + } + + $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; } - public function run($task_id) { + /** + * 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(); - switch($task->name) { - case "rebuild_images": - graphics::rebuild_dirty_images($task); + $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"); } + $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 @@ -129,10 +129,17 @@ class graphics_Core { } /** + * 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, "<a href=\"" . - url::site("admin/maintenance/start/rebuild_images") . + url::site("admin/maintenance/start/rebuild_images?csrf=" . access::csrf_token()) . "\" class=\"gDialogLink\">", "</a>"), "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 @@ <div id="gMaintenance"> <h1> <?= _("Maintenance Tasks") ?> </h1> <p> - <?= _("Occasionally your Gallery will require some maintenance. Here are some tasks you can run to keep it running smoothly.") ?> + <?= _("Occasionally your Gallery will require some maintenance. Here are some tasks you can use to keep it running smoothly.") ?> </p> <div id="gAvailableTasks"> <h2> <?= _("Available Tasks") ?> </h2> - <table style="width: 400px"> - <? foreach ($available_tasks as $task) ?> + <table style="width: 680px" border="1"> <tr> + <th> + <?= _("Name") ?> + </th> + <th> + <?= _("Description") ?> + </th> + <th> + <?= _("Action") ?> + </th> + </tr> + <? foreach ($task_definitions as $task) ?> + <tr class="<?= log::severity_class($task->severity) ?>"> + <td> + <?= $task->name ?> + </td> <td> <?= $task->description ?> </td> <td> - <a href="<?= url::site("admin/maintenance/start/$task->name") ?>" class="gDialogLink"> + <a href="<?= url::site("admin/maintenance/start/$task->callback?csrf=$csrf") ?>" + class="gDialogLink"> <?= _("run") ?> </a> </td> @@ -25,6 +40,119 @@ <div id="gRunningTasks"> <h2> <?= _("Running Tasks") ?> </h2> - <i> Task list goes here </i> + <table style="width: 680px" border="1"> + <tr> + <th> + <?= _("Last Updated") ?> + </th> + <th> + <?= _("Name") ?> + </th> + <th> + <?= _("Status") ?> + </th> + <th> + <?= _("Info") ?> + </th> + <th> + <?= _("Action") ?> + </th> + </tr> + <? foreach ($running_tasks as $task): ?> + <tr class="<?= $task->state == "stalled" ? "gWarning" : "" ?>"> + <td> + <?= date("M j, Y H:i:s", $task->updated) ?> + </td> + <td> + <?= $task->name ?> + </td> + <td> + <? if ($task->done): ?> + <? if ($task->state == "cancelled"): ?> + <?= _("Cancelled") ?> + <? endif ?> + <?= _("Done") ?> + <? elseif ($task->state == "stalled"): ?> + <?= _("Stalled") ?> + <? else: ?> + <?= sprintf(_("%d%% Complete"), $task->percent_complete) ?> + <? endif ?> + </td> + <td> + <?= $task->status ?> + </td> + <td> + <? if ($task->state == "stalled"): ?> + <a href="<?= url::site("admin/maintenance/resume/$task->id?csrf=$csrf") ?>" class="gDialogLink"> + <?= _("resume") ?> + </a> + <? endif ?> + <a href="<?= url::site("admin/maintenance/cancel/$task->id?csrf=$csrf") ?>"> + <?= _("cancel") ?> + </a> + </td> + </tr> + <? endforeach ?> + </table> + </div> + + <div id="gFinishedTasks"> + <h2> <?= _("Finished Tasks") ?> </h2> + + <table style="width: 680px" border="1"> + <tr> + <th> + <?= _("Last Updated") ?> + </th> + <th> + <?= _("Name") ?> + </th> + <th> + <?= _("Status") ?> + </th> + <th> + <?= _("Info") ?> + </th> + <th> + <?= _("Action") ?> + </th> + </tr> + <? foreach ($finished_tasks as $task): ?> + <tr class="<?= $task->state == "success" ? "gSuccess" : "gError" ?>"> + <td> + <?= date("M j, Y H:i:s", $task->updated) ?> + </td> + <td> + <?= $task->name ?> + </td> + <td> + <? if ($task->state == "success"): ?> + <?= _("Success") ?> + <? elseif ($task->state == "error"): ?> + <?= _("Failed") ?> + <? elseif ($task->state == "cancelled"): ?> + <?= _("Cancelled") ?> + <? endif ?> + </td> + <td> + <?= $task->status ?> + </td> + <td> + <? if ($task->done): ?> + <a href="<?= url::site("admin/maintenance/remove/$task->id?csrf=$csrf") ?>"> + <?= _("remove") ?> + </a> + <? else: ?> + <a href="<?= url::site("admin/maintenance/resume/$task->id?csrf=$csrf") ?>"> + <?= _("resume") ?> + </a> + <a href="<?= url::site("admin/maintenance/cancel/$task->id?csrf=$csrf") ?>"> + <?= _("cancel") ?> + </a> + <? endif ?> + </td> + </tr> + <? endforeach ?> + </table> </div> </div> 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 @@ <script type="text/javascript"> update = function() { $.ajax({ - url: "<?= url::site("admin/maintenance/run/$task->id") ?>", + url: "<?= url::site("admin/maintenance/run/$task->id?csrf=$csrf") ?>", dataType: "json", success: function(data) { $("#gStatus").html("" + data.task.status); |