summaryrefslogtreecommitdiff
path: root/core
diff options
context:
space:
mode:
authorBharat Mediratta <bharat@menalto.com>2008-12-28 23:48:15 +0000
committerBharat Mediratta <bharat@menalto.com>2008-12-28 23:48:15 +0000
commited8689f768f81d2c3ed8bee70c43d4f7c71c108e (patch)
tree35fccdad514cd834cc0b7cea86966604e617d3f0 /core
parent1d76689e4b3ea68cada5154d1c0e17b00dec6bd7 (diff)
Expand on the maintenance code to make it more robust and give the
admin more control. You can now track running tasks, resume stalled tasks, cancel running tasks, and remove finished tasks. Added graphics::compose() as a placeholder for future watermark operations. Added CSRF protection to maintenance urls.
Diffstat (limited to 'core')
-rw-r--r--core/controllers/admin_maintenance.php159
-rw-r--r--core/helpers/access.php3
-rw-r--r--core/helpers/core_installer.php5
-rw-r--r--core/helpers/graphics.php21
-rw-r--r--core/models/task.php7
-rw-r--r--core/views/admin_maintenance.html.php138
-rw-r--r--core/views/admin_maintenance_task.html.php2
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);