From 14d7797cda340f373e6b2d048155e4d670c8fa68 Mon Sep 17 00:00:00 2001 From: Bharat Mediratta Date: Thu, 2 Jul 2009 04:35:03 -0700 Subject: In remove_path(), if the path is invalid don't echo it back in the UI. --- modules/server_add/controllers/admin_server_add.php | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) (limited to 'modules') diff --git a/modules/server_add/controllers/admin_server_add.php b/modules/server_add/controllers/admin_server_add.php index a3f9aa96..7c39495f 100644 --- a/modules/server_add/controllers/admin_server_add.php +++ b/modules/server_add/controllers/admin_server_add.php @@ -38,10 +38,7 @@ class Admin_Server_Add_Controller extends Admin_Controller { $path = $form->add_path->path->value; $paths[$path] = 1; module::set_var("server_add", "authorized_paths", serialize($paths)); - $form->add_path->inputs->path->value = ""; - message::success(t("Added path %path", array("path" => $path))); - server_add::check_config($paths); url::redirect("admin/server_add"); } else { @@ -61,10 +58,12 @@ class Admin_Server_Add_Controller extends Admin_Controller { $path = $this->input->get("path"); $paths = unserialize(module::get_var("server_add", "authorized_paths")); - unset($paths[$path]); - message::success(t("Removed path %path", array("path" => $path))); - module::set_var("server_add", "authorized_paths", serialize($paths)); - server_add::check_config($paths); + if (isset($paths[$path])) { + unset($paths[$path]); + message::success(t("Removed path %path", array("path" => $path))); + module::set_var("server_add", "authorized_paths", serialize($paths)); + server_add::check_config($paths); + } url::redirect("admin/server_add"); } -- cgit v1.2.3 From c60d7f431dab7b294790b7636257699668a0f91f Mon Sep 17 00:00:00 2001 From: Bharat Mediratta Date: Thu, 2 Jul 2009 04:37:03 -0700 Subject: ORM::Factory -> ORM::factory --- modules/gallery/helpers/movie.php | 2 +- modules/gallery/helpers/photo.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'modules') diff --git a/modules/gallery/helpers/movie.php b/modules/gallery/helpers/movie.php index fcf1cc54..54159294 100644 --- a/modules/gallery/helpers/movie.php +++ b/modules/gallery/helpers/movie.php @@ -82,7 +82,7 @@ class movie_Core { $movie->rand_key = ((float)mt_rand()) / (float)mt_getrandmax(); // Randomize the name if there's a conflict - while (ORM::Factory("item") + while (ORM::factory("item") ->where("parent_id", $parent->id) ->where("name", $movie->name) ->find()->id) { diff --git a/modules/gallery/helpers/photo.php b/modules/gallery/helpers/photo.php index a4bc853b..e8a4f357 100644 --- a/modules/gallery/helpers/photo.php +++ b/modules/gallery/helpers/photo.php @@ -81,7 +81,7 @@ class photo_Core { $photo->rand_key = ((float)mt_rand()) / (float)mt_getrandmax(); // Randomize the name if there's a conflict - while (ORM::Factory("item") + while (ORM::factory("item") ->where("parent_id", $parent->id) ->where("name", $photo->name) ->find()->id) { -- cgit v1.2.3 From d4e976cc53b32122c2047d9b697a9d5155e66937 Mon Sep 17 00:00:00 2001 From: Bharat Mediratta Date: Thu, 2 Jul 2009 05:06:17 -0700 Subject: Remove empty
    $file_info): ?>
  • "> -- cgit v1.2.3 From 495c76f729372e09f7511967c63948e36303e3d7 Mon Sep 17 00:00:00 2001 From: Bharat Mediratta Date: Thu, 2 Jul 2009 05:11:03 -0700 Subject: Eliminate temporary variables by passing the $item into the view and making API calls on the item. --- modules/server_add/controllers/server_add.php | 4 +--- modules/server_add/views/server_add_tree_dialog.html.php | 8 ++++---- 2 files changed, 5 insertions(+), 7 deletions(-) (limited to 'modules') diff --git a/modules/server_add/controllers/server_add.php b/modules/server_add/controllers/server_add.php index c92b4f7e..4cd3cec7 100644 --- a/modules/server_add/controllers/server_add.php +++ b/modules/server_add/controllers/server_add.php @@ -27,9 +27,7 @@ class Server_Add_Controller extends Controller { $item = ORM::factory("item", $id); $view = new View("server_add_tree_dialog.html"); - $view->action = url::abs_site("__ARGS__/{$id}__TASK_ID__?csrf=" . access::csrf_token()); - $view->parents = $item->parents(); - $view->album_title = $item->title; + $view->item = $item; $tree = new View("server_add_tree.html"); $tree->data = array(); diff --git a/modules/server_add/views/server_add_tree_dialog.html.php b/modules/server_add/views/server_add_tree_dialog.html.php index 8b296987..e2aa5d44 100644 --- a/modules/server_add/views/server_add_tree_dialog.html.php +++ b/modules/server_add/views/server_add_tree_dialog.html.php @@ -7,17 +7,17 @@ });
    -

    p::clean($album_title))) ?>

    +

    p::clean($item->title))) ?>

      - + parents() as $parent): ?>
    • title) ?>
    • -
    • +
    • title) ?>
    - "post")) ?> + "post")) ?>
    -- cgit v1.2.3 From e5b6193b26cf0f8509a98f7913a1d87fa354da05 Mon Sep 17 00:00:00 2001 From: Bharat Mediratta Date: Thu, 2 Jul 2009 11:23:40 -0700 Subject: Partial pass of server_add cleanup. It's broken at this stage since I've redone the browsing code but I have not implemented the adding code. 1) Rename index() to browse() since index is too generic. 2) Simplify the data that we pass to _dialog and _tree 3) Change _tree to return list items only, so that the outer dialog can be a
      for consistency. 4) Simplify the data structures so that we're not tracking checked vs. unchecked status in the PHP code, it's all done in jquery where we can do it with just a line or two of JS 5) use glob() which pretty much entirely replaces _get_children --- modules/server_add/controllers/server_add.php | 103 +++++++++------------ modules/server_add/helpers/server_add_menu.php | 2 +- modules/server_add/js/server_add.js | 52 +++++++++-- modules/server_add/views/server_add_tree.html.php | 29 +++--- .../views/server_add_tree_dialog.html.php | 25 ++--- 5 files changed, 117 insertions(+), 94 deletions(-) (limited to 'modules') diff --git a/modules/server_add/controllers/server_add.php b/modules/server_add/controllers/server_add.php index 4cd3cec7..45ec0a2e 100644 --- a/modules/server_add/controllers/server_add.php +++ b/modules/server_add/controllers/server_add.php @@ -18,26 +18,38 @@ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. */ class Server_Add_Controller extends Controller { - public function index($id) { - $paths = unserialize(module::get_var("server_add", "authorized_paths")); - + public function browse($id) { if (!user::active()->admin) { access::forbidden(); } + + $paths = unserialize(module::get_var("server_add", "authorized_paths")); + foreach (array_keys($paths) as $path) { + $files[$path] = basename($path); + } + $item = ORM::factory("item", $id); $view = new View("server_add_tree_dialog.html"); $view->item = $item; + $view->tree = new View("server_add_tree.html"); + $view->tree->files = $files; + print $view; + } - $tree = new View("server_add_tree.html"); - $tree->data = array(); - $tree->checked = false; - $tree->tree_id = "tree_$id"; - foreach (array_keys($paths) as $path) { - $tree->data[$path] = array("path" => $path, "is_dir" => true); + private function _validate_path($path) { + if (!is_readable($path) || is_link($path)) { + throw new Exception("@todo BAD_PATH"); } - $view->tree = $tree->__toString(); - print $view; + + $authorized_paths = unserialize(module::get_var("server_add", "authorized_paths")); + foreach (array_keys($authorized_paths) as $valid_path) { + if (strpos($path, $valid_path) === 0) { + return; + } + } + + throw new Exception("@todo BAD_PATH"); } public function children() { @@ -45,31 +57,32 @@ class Server_Add_Controller extends Controller { access::forbidden(); } - $paths = unserialize(module::get_var("server_add", "authorized_paths")); - $path_valid = false; - $path = $this->input->post("path"); - $checked = $this->input->post("checked") == "true"; + $path = $this->input->get("path"); + $this->_validate_path($path); - foreach (array_keys($paths) as $valid_path) { - if ($path_valid = strpos($path, $valid_path) === 0) { - break; + $tree = new View("server_add_tree.html"); + $tree->files = array(); + $tree->tree_id = substr(md5($path), 10); + + foreach (glob("$path/*") as $file) { + if (!is_readable($file)) { + continue; } - } - if (empty($path_valid)) { - throw new Exception("@todo BAD_PATH"); - } - if (!is_readable($path) || is_link($path)) { - kohana::show_404(); - } + if (!is_dir($file)) { + $ext = strtolower(pathinfo($file, PATHINFO_EXTENSION)); + if (!in_array($ext, array("gif", "jpeg", "jpg", "png", "flv", "mp4"))) { + continue; + } + } - $tree = new View("server_add_tree.html"); - $tree->data = $this->_get_children($path); - $tree->checked = $checked; - $tree->tree_id = "tree_" . md5($path); + $tree->files[$file] = basename($file); + } print $tree; } + /* ================================================================================ */ + function start($id) { if (!user::active()->admin) { access::forbidden(); @@ -237,36 +250,4 @@ class Server_Add_Controller extends Controller { return $count; } - - private function _get_children($path) { - $directory_list = $file_list = array(); - $files = new DirectoryIterator($path); - foreach ($files as $file) { - if ($file->isDot() || $file->isLink()) { - continue; - } - $filename = $file->getFilename(); - if ($filename[0] != ".") { - if ($file->isDir()) { - $directory_list[$filename] = array("path" => $file->getPathname(), "is_dir" => true); - } else { - $extension = strtolower(substr(strrchr($filename, '.'), 1)); - if ($file->isReadable() && - in_array($extension, array("gif", "jpeg", "jpg", "png", "flv", "mp4"))) { - $file_list[$filename] = array("path" => $file->getPathname(), "is_dir" => false); - } - } - } - } - - ksort($directory_list); - ksort($file_list); - - // We can't use array_merge here because if a file name is numeric, it will - // get renumbered, so lets do it ourselves - foreach ($file_list as $file => $fileinfo) { - $directory_list[$file] = $fileinfo; - } - return $directory_list; - } } \ No newline at end of file diff --git a/modules/server_add/helpers/server_add_menu.php b/modules/server_add/helpers/server_add_menu.php index 23878913..0f01eb64 100644 --- a/modules/server_add/helpers/server_add_menu.php +++ b/modules/server_add/helpers/server_add_menu.php @@ -38,7 +38,7 @@ class server_add_menu_Core { $server_add = Menu::factory("dialog") ->id("server_add") ->label(t("Add from server")) - ->url(url::site("server_add/index/$item->id")); + ->url(url::site("server_add/browse/$item->id")); $add_photos_item = $menu->get("add_photos_item"); $add_photos_menu = $menu->get("add_photos_menu"); diff --git a/modules/server_add/js/server_add.js b/modules/server_add/js/server_add.js index e2526dbe..47340c45 100644 --- a/modules/server_add/js/server_add.js +++ b/modules/server_add/js/server_add.js @@ -1,3 +1,44 @@ +function open_close_branch(path, id) { + var parent = $("#file_" + id); + var children = $("#tree_" + id); + var icon = parent.find(".ui-icon:first"); + + if (!children.html()) { + parent.addClass("gLoadingSmall"); + $.ajax({ + url: GET_CHILDREN_URL.replace("__PATH__", path), + success: function(data, textStatus) { + children.html(data); + parent.removeClass("gLoadingSmall"); + + // Propagate checkbox value + children.find("input[type=checkbox]").attr( + "checked", parent.find("input[type=checkbox]:first").attr("checked")); + }, + }); + } + + children.slideToggle("fast", function() { + if (children.is(":hidden")) { + icon.addClass("ui-icon-plus"); + icon.removeClass("ui-icon-minus"); + } else { + icon.addClass("ui-icon-minus"); + icon.removeClass("ui-icon-plus"); + parent.removeClass("gCollapsed"); + } + }); +} + +function click_node(checkbox) { + var parent = $(checkbox).parents("li").get(0); + var checked = $(checkbox).attr("checked"); + $(parent).find("input[type=checkbox]").attr("checked", checked); +} + +/* ================================================================================ */ + +/* var paused = false; var task = null; @@ -82,16 +123,6 @@ function get_url(uri, task_id) { return url; } -function checkbox_click(checkbox) { - var parent = $(checkbox).parents("li").get(0); - var checked = $(checkbox).attr("checked"); - if (!$(parent).hasClass("gCollapsed")) { - $(parent).find(".gCheckboxTree input[type=checkbox]").attr("checked", checked); - } - var checkboxes = $("#gServerAdd :checkbox[checked]"); - $("#gServerAdd form :submit").attr("disabled", checkboxes.length == 0); -} - function load_children(icon) { $("#gDialog").addClass("gDialogLoadingLarge"); var parent = icon.parentNode; @@ -203,3 +234,4 @@ function display_upload_error(error) { }); } +*/ diff --git a/modules/server_add/views/server_add_tree.html.php b/modules/server_add/views/server_add_tree.html.php index 44f6bfbc..f8205a8b 100644 --- a/modules/server_add/views/server_add_tree.html.php +++ b/modules/server_add/views/server_add_tree.html.php @@ -1,12 +1,19 @@ -
        - $file_info): ?> -
      • "> - - - - - -
      • - -
      + $name): ?> + +
    • "> + + + + + + + +
    • + + +
    • + diff --git a/modules/server_add/views/server_add_tree_dialog.html.php b/modules/server_add/views/server_add_tree_dialog.html.php index e2aa5d44..6e4db620 100644 --- a/modules/server_add/views/server_add_tree_dialog.html.php +++ b/modules/server_add/views/server_add_tree_dialog.html.php @@ -1,26 +1,29 @@ - +

      p::clean($item->title))) ?>

        parents() as $parent): ?> -
      • title) ?>
      • +
      • + title) ?> +
      • -
      • title) ?>
      • +
      • + title) ?> +
      - "post")) ?> -
      + "post")) ?> + +
        -
      +
    + "gServerPauseButton", "name" => "add", "disabled" => true, "class" => "submit", "style" => "display:none"), t("Pause")) ?> "gServerAddButton", "name" => "add", "disabled" => true, "class" => "submit"), t("Add")) ?> -- cgit v1.2.3 From a619bb81966102a9f684dd8d37f1127b4f16a390 Mon Sep 17 00:00:00 2001 From: Bharat Mediratta Date: Thu, 2 Jul 2009 11:30:21 -0700 Subject: Add some jsDoc. --- modules/server_add/js/server_add.js | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'modules') diff --git a/modules/server_add/js/server_add.js b/modules/server_add/js/server_add.js index 47340c45..918bd14b 100644 --- a/modules/server_add/js/server_add.js +++ b/modules/server_add/js/server_add.js @@ -1,3 +1,7 @@ +/** + * We've clicked the + icon next to a directory. Load up children of this + * directory from the server and display them. + */ function open_close_branch(path, id) { var parent = $("#file_" + id); var children = $("#tree_" + id); @@ -30,6 +34,9 @@ function open_close_branch(path, id) { }); } +/** + * We've clicked a checkbox. Propagate the value downwards as necessary. + */ function click_node(checkbox) { var parent = $(checkbox).parents("li").get(0); var checked = $(checkbox).attr("checked"); -- cgit v1.2.3 From d4075a4657eddcf1aca71de9996a421cd577ec36 Mon Sep 17 00:00:00 2001 From: Bharat Mediratta Date: Thu, 2 Jul 2009 11:49:45 -0700 Subject: Only enable the [add] button when boxes are checked. --- modules/server_add/js/server_add.js | 8 ++++++++ modules/server_add/views/server_add_tree_dialog.html.php | 6 ++++-- 2 files changed, 12 insertions(+), 2 deletions(-) (limited to 'modules') diff --git a/modules/server_add/js/server_add.js b/modules/server_add/js/server_add.js index 918bd14b..ada09d65 100644 --- a/modules/server_add/js/server_add.js +++ b/modules/server_add/js/server_add.js @@ -41,6 +41,14 @@ function click_node(checkbox) { var parent = $(checkbox).parents("li").get(0); var checked = $(checkbox).attr("checked"); $(parent).find("input[type=checkbox]").attr("checked", checked); + + if ($("#gServerAddTree").find("input[type=checkbox]").is(":checked")) { + $("#gServerAddAddButton").attr("disabled", true); + $("#gServerAddAddButton").removeClass("ui-state-disabled"); + } else { + $("#gServerAddAddButton").attr("disabled", false); + $("#gServerAddAddButton").addClass("ui-state-disabled"); + } } /* ================================================================================ */ diff --git a/modules/server_add/views/server_add_tree_dialog.html.php b/modules/server_add/views/server_add_tree_dialog.html.php index 6e4db620..723d388e 100644 --- a/modules/server_add/views/server_add_tree_dialog.html.php +++ b/modules/server_add/views/server_add_tree_dialog.html.php @@ -25,8 +25,10 @@
- "gServerPauseButton", "name" => "add", "disabled" => true, "class" => "submit", "style" => "display:none"), t("Pause")) ?> - "gServerAddButton", "name" => "add", "disabled" => true, "class" => "submit"), t("Add")) ?> + " style="display: none"> + "> -- cgit v1.2.3 From 1cd321901b7225e0297ee4b14a9f7234e09748e3 Mon Sep 17 00:00:00 2001 From: Bharat Mediratta Date: Thu, 2 Jul 2009 13:02:26 -0700 Subject: Properly manage disabled state for the [add] button. --- modules/server_add/js/server_add.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'modules') diff --git a/modules/server_add/js/server_add.js b/modules/server_add/js/server_add.js index ada09d65..32176108 100644 --- a/modules/server_add/js/server_add.js +++ b/modules/server_add/js/server_add.js @@ -43,10 +43,10 @@ function click_node(checkbox) { $(parent).find("input[type=checkbox]").attr("checked", checked); if ($("#gServerAddTree").find("input[type=checkbox]").is(":checked")) { - $("#gServerAddAddButton").attr("disabled", true); + $("#gServerAddAddButton").enable(true); $("#gServerAddAddButton").removeClass("ui-state-disabled"); } else { - $("#gServerAddAddButton").attr("disabled", false); + $("#gServerAddAddButton").enable(false); $("#gServerAddAddButton").addClass("ui-state-disabled"); } } -- cgit v1.2.3 From 9eee3b07a8abb1cd798372a20725290130ec183f Mon Sep 17 00:00:00 2001 From: Bharat Mediratta Date: Thu, 2 Jul 2009 17:54:22 -0700 Subject: @todo if we uncheck all the children for a parent, we should uncheck the parent itself, otherwise in the code we'll add the entire parent since if we find an album as a leaf, we assume that it's never been expanded in the UI. --- modules/server_add/js/server_add.js | 3 +++ 1 file changed, 3 insertions(+) (limited to 'modules') diff --git a/modules/server_add/js/server_add.js b/modules/server_add/js/server_add.js index 32176108..568ef91f 100644 --- a/modules/server_add/js/server_add.js +++ b/modules/server_add/js/server_add.js @@ -42,6 +42,9 @@ function click_node(checkbox) { var checked = $(checkbox).attr("checked"); $(parent).find("input[type=checkbox]").attr("checked", checked); + // @todo if we uncheck all the children for a parent, we should uncheck the + // parent itself, otherwise in the code we'll add the entire parent since if + // we find an album as a leaf, we assume that it's never been expanded in the UI. if ($("#gServerAddTree").find("input[type=checkbox]").is(":checked")) { $("#gServerAddAddButton").enable(true); $("#gServerAddAddButton").removeClass("ui-state-disabled"); -- cgit v1.2.3 From 1a5fe42b555d51d22bde1521100a31d2b434486b Mon Sep 17 00:00:00 2001 From: Bharat Mediratta Date: Thu, 2 Jul 2009 17:54:48 -0700 Subject: Temporary version add() that just dumps out the values for now. --- modules/server_add/controllers/server_add.php | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) (limited to 'modules') diff --git a/modules/server_add/controllers/server_add.php b/modules/server_add/controllers/server_add.php index 45ec0a2e..2204b338 100644 --- a/modules/server_add/controllers/server_add.php +++ b/modules/server_add/controllers/server_add.php @@ -81,6 +81,29 @@ class Server_Add_Controller extends Controller { print $tree; } + public function add() { + if (!user::active()->admin) { + access::forbidden(); + } + access::verify_csrf(); + + $authorized_paths = unserialize(module::get_var("server_add", "authorized_paths")); + + // The paths we receive are full pathnames. Convert that into a tree structure to save space + // in our task. + foreach (Input::instance()->post("path") as $path) { + if (is_dir($path)) { + $dirs[$path] = array(); + } else if (is_file($path)) { + $dir = dirname($path); + $file = basename($path); + $dirs[$dir][] = $file; + } + } + + Kohana::log("alert",print_r($dirs,1)); + } + /* ================================================================================ */ function start($id) { -- cgit v1.2.3 From faabae5dae712ebff656abe8ebc493d8e031d4a3 Mon Sep 17 00:00:00 2001 From: Bharat Mediratta Date: Tue, 7 Jul 2009 21:16:36 -0700 Subject: Rework server_add. It's smaller and leaner now, storing the list of files out in a separate model for scalability. Removed the "pause" functionality. - Server_Add_Controller extends Admin_Controller so that we don't have to check for admin every time. - Task completion time now factors in the time it takes to walk the arbitrarily deep trees - Moved checkbox management entirely into JS using jQuery - Simplified the JS considerably --- modules/server_add/controllers/server_add.php | 348 ++++++++++----------- modules/server_add/helpers/server_add.php | 15 + .../server_add/helpers/server_add_installer.php | 22 +- modules/server_add/helpers/server_add_task.php | 85 ----- modules/server_add/js/server_add.js | 222 ++----------- modules/server_add/models/server_add_file.php | 21 ++ modules/server_add/module.info | 2 +- .../views/server_add_tree_dialog.html.php | 23 +- 8 files changed, 266 insertions(+), 472 deletions(-) delete mode 100644 modules/server_add/helpers/server_add_task.php create mode 100644 modules/server_add/models/server_add_file.php (limited to 'modules') diff --git a/modules/server_add/controllers/server_add.php b/modules/server_add/controllers/server_add.php index e2b1b01a..288e6342 100644 --- a/modules/server_add/controllers/server_add.php +++ b/modules/server_add/controllers/server_add.php @@ -17,13 +17,8 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. */ -class Server_Add_Controller extends Controller { +class Server_Add_Controller extends Admin_Controller { public function browse($id) { - if (!user::active()->admin) { - access::forbidden(); - } - - $paths = unserialize(module::get_var("server_add", "authorized_paths")); foreach (array_keys($paths) as $path) { $files[$path] = basename($path); @@ -37,28 +32,11 @@ class Server_Add_Controller extends Controller { print $view; } - private function _validate_path($path) { - if (!is_readable($path) || is_link($path)) { - throw new Exception("@todo BAD_PATH"); - } - - $authorized_paths = unserialize(module::get_var("server_add", "authorized_paths")); - foreach (array_keys($authorized_paths) as $valid_path) { - if (strpos($path, $valid_path) === 0) { - return; - } - } - - throw new Exception("@todo BAD_PATH"); - } - public function children() { - if (!user::active()->admin) { - access::forbidden(); - } - $path = $this->input->get("path"); - $this->_validate_path($path); + if (!server_add::is_valid_path($path)) { + throw new Exception("@todo BAD_PATH"); + } $tree = new View("server_add_tree.html"); $tree->files = array(); @@ -81,196 +59,194 @@ class Server_Add_Controller extends Controller { print $tree; } - public function add() { - if (!user::active()->admin) { - access::forbidden(); - } + public function start() { access::verify_csrf(); - $authorized_paths = unserialize(module::get_var("server_add", "authorized_paths")); + $item = ORM::factory("item", Input::instance()->get("item_id")); + // We're an admin so this isn't necessary, but we'll eventually open this up to non-admins and + // this also verifies that the item was loaded properly. + access::required("edit", $item); - // The paths we receive are full pathnames. Convert that into a tree structure to save space - // in our task. - foreach (Input::instance()->post("path") as $path) { + // Gather up all the paths and associate them by directory, so that we can locate any empty + // directories for the next round. + foreach (Input::instance()->post("paths") as $path) { if (is_dir($path)) { - $dirs[$path] = array(); + $selections[$path] = array(); } else if (is_file($path)) { - $dir = dirname($path); - $file = basename($path); - $dirs[$dir][] = $file; + $selections[dirname($path)][] = $path; } } - Kohana::log("alert",print_r($dirs,1)); - } - - /* ================================================================================ */ - - function start($id) { - if (!user::active()->admin) { - access::forbidden(); - } - access::verify_csrf(); - - $item = ORM::factory("item", $id); - $paths = unserialize(module::get_var("server_add", "authorized_paths")); - $input_files = $this->input->post("path"); - $collapsed = $this->input->post("collapsed"); - $files = array(); - $total_count = 0; - foreach (array_keys($paths) as $valid_path) { - $path_length = strlen($valid_path); - foreach ($input_files as $key => $path) { - if (!empty($path)) { - if ($valid_path != $path && strpos($path, $valid_path) === 0) { - $relative_path = substr(dirname($path), $path_length); - $name = basename($path); - $files[$valid_path][] = array("path" => $relative_path, - "parent_id" => $id, "name" => basename($path), - "type" => is_dir($path) ? "album" : "file"); - $total_count++; - } - if ($collapsed[$key] === "true") { - $total_count += $this->_select_children($id, $valid_path, $path, $files[$valid_path]); - } - unset($input_files[$key]); - unset($collapsed[$key]); - } - } - } - - if ($total_count == 0) { - print json_encode(array("result" => "success", - "url" => "", - "task" => array( - "id" => -1, "done" => 1, "percent_complete" => 100, - "status" => t("No eligible files, import cancelled")))); - return; - } - $task_def = Task_Definition::factory() - ->callback("server_add_task::add_from_server") + ->callback("Server_Add_Controller::add") ->description(t("Add photos or movies from the local server")) ->name(t("Add from server")); - $task = task::create($task_def, array("item_id" => $id, "next_path" => 0, "files" => $files, - "counter" => 0, "position" => 0, "total" => $total_count)); + $task = task::create( + $task_def, array("item_id" => $item->id, "selections" => $selections)); - batch::start(); - print json_encode(array("result" => "started", - "url" => url::site("server_add/add_photo/{$task->id}?csrf=" . - access::csrf_token()), - "task" => array( - "id" => $task->id, - "percent_complete" => $task->percent_complete, - "status" => $task->status, - "done" => $task->done))); + print json_encode( + array("result" => "started", + "url" => url::site("server_add/run/$task->id?csrf=" . access::csrf_token()))); } - function add_photo($task_id) { - if (!user::active()->admin) { - access::forbidden(); - } + function run($task_id) { access::verify_csrf(); - $task = task::run($task_id); - // @todo the task is already run... its a little late to check the access - if (!$task->loaded || $task->owner_id != user::active()->id) { - access::forbidden(); - } - - if ($task->done) { - switch ($task->state) { - case "success": - message::success(t("Add from server completed")); - break; - - case "error": - message::warning(t("Add from server completed with errors")); - break; - } - print json_encode(array("result" => "success", - "task" => array( - "id" => $task->id, - "percent_complete" => $task->percent_complete, - "status" => $task->status, - "done" => $task->done))); - - } else { - print json_encode(array("result" => "in_progress", - "task" => array( - "id" => $task->id, - "percent_complete" => $task->percent_complete, - "status" => $task->status, - "done" => $task->done))); - } - } - - public function finish($id, $task_id) { - if (!user::active()->admin) { - access::forbidden(); - } - access::verify_csrf(); $task = ORM::factory("task", $task_id); - if (!$task->loaded || $task->owner_id != user::active()->id) { access::forbidden(); } - if (!$task->done) { - message::warning(t("Add from server was cancelled prior to completion")); - } - - batch::stop(); - print json_encode(array("result" => "success")); + $task = task::run($task_id); + print json_encode(array("done" => $task->done, + "percent_complete" => $task->percent_complete)); } - public function pause($id, $task_id) { - if (!user::active()->admin) { - access::forbidden(); - } - access::verify_csrf(); - $task = ORM::factory("task", $task_id); - if (!$task->loaded || $task->owner_id != user::active()->id) { - access::forbidden(); - } - - message::warning(t("Add from server was cancelled prior to completion")); - batch::stop(); - print json_encode(array("result" => "success")); - } + /** + * This is the task code that adds photos and albums. It first examines all the target files + * and creates a set of Server_Add_File_Models, then runs through the list of models and adds + * them one at a time. + */ + static function add($task) { + $selections = $task->get("selections"); + $mode = $task->get("mode", "init"); + $start = microtime(true); + $item_id = $task->get("item_id"); + + switch ($mode) { + case "init": + $task->set("mode", "build-file-list"); + $task->set("queue", array_keys($selections)); + $task->percent_complete = 0; + batch::start(); + break; + + case "build-file-list": /* 0% to 10% */ + // We can't fit an arbitrary number of paths in a task, so store them in a separate table. + // Don't use an iterator here because we can't get enough control over it when we're dealing + // with a deep hierarchy and we don't want to go over our time quota. + $queue = $task->get("queue"); + Kohana::log("alert",print_r($queue,1)); + while ($queue && microtime(true) - $start < 0.5) { + $file = array_shift($queue); + $entry = ORM::factory("server_add_file"); + $entry->task_id = $task->id; + $entry->file = $file; + $entry->save(); + + if (is_dir($file)) { + $queue = array_merge( + $queue, empty($selections[$file]) ? glob("$file/*") : $selections[$file]); + } + } + // We have no idea how long this can take because we have no idea how deep the tree + // hierarchy rabbit hole goes. Leave ourselves room here for 100 iterations and don't go + // over 10% in percent_complete. + $task->set("queue", $queue); + $task->percent_complete = min($task->percent_complete + 0.1, 10); + + if (!$queue) { + $task->set("mode", "add-files"); + $task->set( + "total_files", database::instance()->count_records( + "server_add_files", array("task_id" => $task->id))); + $task->set("albums", array()); + $task->set("completed", 0); + $task->percent_complete = 10; + } + break; + + case "add-files": /* 10% to 100% */ + $completed_files = $task->get("completed_files"); + $total_files = $task->get("total_files"); + $albums = $task->get("albums"); + + // Ordering by id ensures that we add them in the order that we created the entries, which + // will create albums first. + $entries = ORM::factory("server_add_file") + ->where("task_id", $task->id) + ->orderby("id", "ASC") + ->limit(10) + ->find_all(); + if ($entries->count() == 0) { + $task->set("mode", "done"); + } - private function _select_children($id, $valid_path, $path, &$files) { - $count = 0; - $children = new RecursiveIteratorIterator( - new RecursiveDirectoryIterator($path), - RecursiveIteratorIterator::SELF_FIRST); + $item = model_cache::get("item", $item_id); + foreach ($entries as $entry) { + if (microtime(true) - $start > 0.5) { + break; + } - $path_length = strlen($valid_path); - foreach($children as $name => $file){ - if ($file->isLink()) { - continue; - } - $filename = $file->getFilename(); - if ($filename[0] != ".") { - if ($file->isDir()) { - $relative_path = substr(dirname($file->getPathname()), $path_length); - $files[] = array("path" => $relative_path, - "parent_id" => $id, "name" => $filename, "type" => "album"); - $count++; + $relative_path = self::_relative_path($entry->file); + $name = basename($relative_path); + $title = item::convert_filename_to_title($name); + if (is_dir($entry->file)) { + if (isset($albums[$relative_path]) && $parent_id = $albums[$relative_path]) { + $parent = ORM::factory("item", $parent_id); + } else { + $album = album::create($item, $name, $title, null, user::active()->id); + $albums[$relative_path] = $album->id; + $task->set("albums", $albums); + } } else { - $extension = strtolower(substr(strrchr($filename, '.'), 1)); - if ($file->isReadable() && - in_array($extension, array("gif", "jpeg", "jpg", "png", "flv", "mp4"))) { - $relative_path = substr(dirname($file->getPathname()), $path_length); - $files[] = array("path" => $relative_path, - "parent_id" => $id, "name" => $filename, "type" => "file"); - $count++; + if (strpos($relative_path, "/") !== false) { + $parent = ORM::factory("item", $albums[dirname($relative_path)]); + } else { + $parent = $item; + } + + $extension = strtolower(pathinfo($name, PATHINFO_EXTENSION)); + if (in_array($extension, array("gif", "png", "jpg", "jpeg"))) { + photo::create($parent, $entry->file, $name, $title, null, user::active()->id); + } else if (in_array($extension, array("flv", "mp4"))) { + movie::create($parent, $entry->file, $name, $title, null, user::active()->id); + } else { + // Unsupported type + // @todo: $task->log this } } + + $completed_files++; + $entry->delete(); } + $task->set("completed_files", $completed_files); + $task->percent_complete = 10 + 100 * ($completed_files / $total_files); + Kohana::log("alert",print_r($task->as_array(),1)); + break; + + case "done": + batch::stop(); + $task->done = true; + $task->state = "success"; + $task->percent_complete = 100; + message::info(t2("Successfully added one photo", + "Successfully added %count photos", + $task->get("completed_files"))); + } + } + /** + * Given a path that's somewhere in our authorized_paths list, return just the part that's + * relative to the nearest authorized path. + */ + static function _relative_path($path) { + static $authorized_paths; + // @todo this doesn't deal well with overlapping authorized paths, it'll just use the first one + // that matches. If we sort $authorized_paths by length in descending order, that should take + // care of the problem. + if (!$authorized_paths) { + $authorized_paths = + array_keys(unserialize(module::get_var("server_add", "authorized_paths"))); + } + + foreach ($authorized_paths as $candidate) { + $candidate = dirname($candidate); + if (strpos($path, $candidate) === 0) { + return substr($path, strlen($candidate) + 1); + } } - return $count; + throw new Exception("@todo BAD_PATH"); } -} \ No newline at end of file +} diff --git a/modules/server_add/helpers/server_add.php b/modules/server_add/helpers/server_add.php index f75a09d2..74f51ad9 100644 --- a/modules/server_add/helpers/server_add.php +++ b/modules/server_add/helpers/server_add.php @@ -31,4 +31,19 @@ class server_add_Core { site_status::clear("server_add_configuration"); } } + + static function is_valid_path($path) { + if (!is_readable($path) || is_link($path)) { + return false; + } + + $authorized_paths = unserialize(module::get_var("server_add", "authorized_paths")); + foreach (array_keys($authorized_paths) as $valid_path) { + if (strpos($path, $valid_path) === 0) { + return true; + } + } + + return false; + } } diff --git a/modules/server_add/helpers/server_add_installer.php b/modules/server_add/helpers/server_add_installer.php index c9d92e69..6956a72c 100644 --- a/modules/server_add/helpers/server_add_installer.php +++ b/modules/server_add/helpers/server_add_installer.php @@ -19,10 +19,30 @@ */ class server_add_installer { static function install() { - module::set_version("server_add", 1); + $db = Database::instance(); + $db->query("CREATE TABLE {server_add_files} ( + `id` int(9) NOT NULL auto_increment, + `task_id` int(9) NOT NULL, + `file` varchar(255) NOT NULL, + PRIMARY KEY (`id`)) + ENGINE=InnoDB DEFAULT CHARSET=utf8;"); + module::set_version("server_add", 2); server_add::check_config(); } + static function upgrade($version) { + $db = Database::instance(); + if ($version == 1) { + $db->query("CREATE TABLE {server_add_files} ( + `id` int(9) NOT NULL auto_increment, + `task_id` int(9) NOT NULL, + `file` varchar(255) NOT NULL, + PRIMARY KEY (`id`)) + ENGINE=InnoDB DEFAULT CHARSET=utf8;"); + module::set_version("server_add", $version = 2); + } + } + static function deactivate() { site_status::clear("server_add_configuration"); } diff --git a/modules/server_add/helpers/server_add_task.php b/modules/server_add/helpers/server_add_task.php deleted file mode 100644 index 0482b47c..00000000 --- a/modules/server_add/helpers/server_add_task.php +++ /dev/null @@ -1,85 +0,0 @@ -context); - try { - $paths = array_keys(unserialize(module::get_var("server_add", "authorized_paths"))); - $path = $paths[$context["next_path"]]; - if (!empty($context["files"][$path])) { - $file = $context["files"][$path][$context["position"]]; - $parent = ORM::factory("item", $file["parent_id"]); - access::required("add", $parent); - if (!$parent->is_album()) { - throw new Exception("@todo BAD_ALBUM"); - } - - $name = $file["name"]; - if ($file["type"] == "album") { - $album = ORM::factory("item") - ->where("name", $name) - ->where("parent_id", $parent->id) - ->find(); - if (!$album->loaded) { - $album = album::create($parent, $name, $name, null, user::active()->id); - } - // Now that we have a new album. Go through the remaining files to import and change the - // parent_id of any file that has the same relative path as this album's path. - $album_path = "{$file['path']}/$name"; - for ($idx = $context["position"] + 1; $idx < count($context["files"][$path]); $idx++) { - if (strpos($context["files"][$path][$idx]["path"], $album_path) === 0) { - $context["files"][$path][$idx]["parent_id"] = $album->id; - } - } - } else { - $extension = strtolower(substr(strrchr($name, '.'), 1)); - $source_path = "$path{$file['path']}/$name"; - $title = item::convert_filename_to_title($name); - if (in_array($extension, array("flv", "mp4"))) { - $movie = movie::create($parent, $source_path, $name, $title, - null, user::active()->id); - } else { - $photo = photo::create($parent, $source_path, $name, $title, - null, user::active()->id); - } - } - - $context["counter"]++; - if (++$context["position"] >= count($context["files"][$path])) { - $context["next_path"]++; - $context["position"] = 0; - } - } else { - $context["next_path"]++; - } - } catch(Exception $e) { - $context["errors"][$path] = $e->getMessage(); - } - $task->context = serialize($context); - $task->state = "success"; - $task->percent_complete = ($context["counter"] / (float)$context["total"]) * 100; - $task->done = $context["counter"] == (float)$context["total"]; - } -} \ No newline at end of file diff --git a/modules/server_add/js/server_add.js b/modules/server_add/js/server_add.js index 568ef91f..cba8c9ce 100644 --- a/modules/server_add/js/server_add.js +++ b/modules/server_add/js/server_add.js @@ -12,13 +12,13 @@ function open_close_branch(path, id) { $.ajax({ url: GET_CHILDREN_URL.replace("__PATH__", path), success: function(data, textStatus) { - children.html(data); - parent.removeClass("gLoadingSmall"); + children.html(data); + parent.removeClass("gLoadingSmall"); - // Propagate checkbox value - children.find("input[type=checkbox]").attr( - "checked", parent.find("input[type=checkbox]:first").attr("checked")); - }, + // Propagate checkbox value + children.find("input[type=checkbox]").attr( + "checked", parent.find("input[type=checkbox]:first").attr("checked")); + } }); } @@ -54,202 +54,38 @@ function click_node(checkbox) { } } -/* ================================================================================ */ - -/* -var paused = false; -var task = null; - -$("#gServerAdd").ready(function() { - init_server_add_form(); -}); - -function init_server_add_form() { - $("#gServerAdd #gServerAddButton").click(function(event) { - do_add(this, event); - }); - $("#gServerAdd #gServerPauseButton").click(function(event) { - event.preventDefault(); - paused = true; - }); - $(".gProgressBar").progressbar(); - $("#gServerAddTree ul").css("display", "block"); - $("#gServerAdd form").bind("form_closing", function(target) { - if (task != null && !task.done) { - $.ajax({async: false, - success: function(data, textStatus) { - document.location.reload(); - }, - dataType: "json", - type: "POST", - url: get_url("server_add/pause", task.id) - }); - } else { - document.location.reload(); - } - }); - set_click_events(); -} - -function set_click_events() { - $(".ui-icon").unbind("click"); - $(":checkbox").unbind("click"); - $(".ui-icon").click(function(event) { - open_close_branch(this, event); - }); - - $("input[type=checkbox]").click(function(event) { - checkbox_click(this); +function start_add() { + var paths = []; + $.each($("#gServerAdd :checkbox[checked]"), function () { + paths.push(this.value); }); -} - -function open_close_branch(icon, event) { - var parent = icon.parentNode; - var closed = $(icon).hasClass("ui-icon-plus"); - var children = $(parent).find(".gCheckboxTree"); - - if (closed) { - if (children.length == 0) { - load_children(icon); - } else { - toggle_branch("open", icon); - } - } else { - toggle_branch("close", icon); - } -} - -function toggle_branch(direction, icon) { - var parent = icon.parentNode; - var branch = $(parent).children(".gServerAddChildren"); - $(branch).slideToggle("fast", function() { - if (direction == "open") { - $(icon).addClass("ui-icon-minus"); - $(icon).removeClass("ui-icon-plus"); - $(parent).removeClass("gCollapsed"); - } else { - $(icon).addClass("ui-icon-plus"); - $(icon).removeClass("ui-icon-minus"); + $.ajax({ + url: START_URL, + type: "POST", + async: false, + data: { "paths[]": paths }, + dataType: "json", + success: function(data, textStatus) { + $("#gServerAdd .gProgressBar").progressbar("value", data.percent_complete); + setTimeout(function() { run_add(data.url); }, 0); } }); + return false; } -function get_url(uri, task_id) { - var url = $("#gServerAdd form").attr("action"); - url = url.replace("__ARGS__", uri); - url = url.replace("__TASK_ID__", !task_id ? "" : "/" + task_id); - return url; -} - -function load_children(icon) { - $("#gDialog").addClass("gDialogLoadingLarge"); - var parent = icon.parentNode; - var checkbox = $(parent).find("input[type=checkbox]"); - var parms = "&path=" + $(checkbox).attr("value"); - parms += "&checked=" + $(checkbox).is(":checked"); - parms += "&collapsed=" + $(parent).hasClass("gCollapsed"); - - $.ajax({success: function(data, textStatus) { - $(parent).children(".gServerAddChildren").html(data); - set_click_events(); - $("#gDialog").removeClass("gDialogLoadingLarge"); - toggle_branch("open", icon); - }, - data: parms, - dataType: "html", - type: "POST", - url: get_url("server_add/children") - }); -} - -function do_add(submit, event) { - event.preventDefault(); - - $("#gServerAdd #gServerAddButton").hide(); - $("#gServerAdd #gServerPauseButton").show(); - - var parms = ""; - if (!paused) { - $(".gProgressBar").progressbar("value", 0); - $(".gProgressBar").css("visibility", "visible"); - var check_list = $("#gServerAdd :checkbox[checked]"); - - var paths = ""; - var collapsed = ""; - $.each(check_list, function () { - var parent = $(this).parents("li")[0]; - paths += "&path[]=" + this.value; - collapsed += "&collapsed[]=" + $(parent).hasClass("gCollapsed"); - }); - parms = paths + collapsed; - } - paused = false; - - $.ajax({async: false, - data: parms, +function run_add(url) { + $.ajax({ + url: url, + async: false, dataType: "json", success: function(data, textStatus) { - var done = data.task.done; - if (done) { - task = null; - $("body").append("
" + data.task.status + "
"); - - $("#gNoFilesDialog").dialog({modal: true, - autoOpen: true, - title: FILE_IMPORT_WARNING}); - $(".gProgressBar").css("visibility", "hidden"); - $("#gServerAdd #gServerAddButton").show(); - $("#gServerAdd #gServerPauseButton").hide(); - return; - } - task = data.task; - var url = data.url; - while (!done && !paused) { - $.ajax({async: false, - success: function(data, textStatus) { - $(".gProgressBar").progressbar("value", data.task.percent_complete); - done = data.task.done; - }, - error: function(XMLHttpRequest, textStatus, errorThrown) { - paused = true; - display_upload_error(XMLHttpRequest.responseText); - }, - dataType: "json", - type: "POST", - url: url - }); - } - if (!paused) { - $.ajax({async: false, - success: function(data, textStatus) { - document.location.reload(); - }, - dataType: "json", - type: "POST", - url: get_url("server_add/finish", task.id) - }); + $("#gServerAdd .gProgressBar").progressbar("value", data.percent_complete); + if (data.done) { + $("#gServerAdd .gProgressBar").slideUp(); } else { - $("#gServerAdd #gServerAddButton").show(); - $("#gServerAdd #gServerPauseButton").hide(); + setTimeout(function() { run_add(url); }, 0); } - }, - type: "POST", - url: get_url("server_add/start") + } }); - - return false; -} - -function display_upload_error(error) { - $("body").append("
" + error + "
"); - $("#gServerAddError").dialog({ - autoOpen: true, - autoResize: false, - modal: true, - resizable: true, - width: 610, - height: $("#gDialog").height() - }); } -*/ diff --git a/modules/server_add/models/server_add_file.php b/modules/server_add/models/server_add_file.php new file mode 100644 index 00000000..8b1ed924 --- /dev/null +++ b/modules/server_add/models/server_add_file.php @@ -0,0 +1,21 @@ +
@@ -18,18 +19,28 @@ - "post")) ?> + id"), array("method" => "post")) ?>
+ + - " style="display: none"> - "> + "> - +
-- cgit v1.2.3