From cb274157a4ced81329418952956b74591b06bd0f Mon Sep 17 00:00:00 2001 From: Michael Hamann Date: Mon, 1 Apr 2013 01:04:04 +0200 Subject: [PATCH] Avoid reading and writing the whole media/page list for each move This makes sure that only the necessary parts of the media/page list are read and the file is efficiently truncated. In order to calculate the number of remaining items this number is stored and updated in the options. --- admin.php | 33 ++++++++------ helper.php | 124 +++++++++++++++++++++++++++++++++++++---------------- 2 files changed, 106 insertions(+), 51 deletions(-) diff --git a/admin.php b/admin.php index e8b8f7c..c065b7d 100644 --- a/admin.php +++ b/admin.php @@ -19,6 +19,8 @@ class admin_plugin_pagemove extends DokuWiki_Admin_Plugin { private $ns_opts = false; /** @var helper_plugin_pagemove $helper */ private $helper = null; + /** @var string $ns_move_state The state of the current namespace move (none, started, continued, error) */ + private $ns_move_state = 'none'; /** * Get the sort number that defines the position in the admin menu. @@ -70,30 +72,32 @@ function html() { ptln( $this->locale_xhtml('pagemove') ); ptln('
'); - if ($this->ns_opts !== false && isset($this->ns_opts['started'])) { - ptln('

'); - ptln(sprintf($this->getLang('pm_ns_move_started'), hsc($this->ns_opts['ns']), hsc($this->ns_opts['newns']), $this->ns_opts['num_pages'], $this->ns_opts['num_media'])); - ptln('

'); - ptln($this->helper->getNSMoveButton('continue')); - ptln($this->helper->getNSMoveButton('abort')); - } elseif ($this->ns_opts !== false && isset($this->ns_opts['remaining'])) { - if ($this->ns_opts['remaining'] === false) { + switch ($this->ns_move_state) { + case 'started': + ptln('

'); + ptln(sprintf($this->getLang('pm_ns_move_started'), hsc($this->ns_opts['ns']), hsc($this->ns_opts['newns']), $this->ns_opts['num_pages'], $this->ns_opts['num_media'])); + ptln('

'); + ptln($this->helper->getNSMoveButton('continue')); + ptln($this->helper->getNSMoveButton('abort')); + break; + case 'error': ptln('

'); ptln(sprintf($this->getLang('pm_ns_move_error'), $this->ns_opts['ns'], $this->ns_opts['newns'])); ptln('

'); ptln($this->helper->getNSMoveButton('tryagain')); ptln($this->helper->getNSMoveButton('skip')); ptln($this->helper->getNSMoveButton('abort')); - } else { + break; + case 'continued': ptln('

'); ptln(sprintf($this->getLang('pm_ns_move_continued'), $this->ns_opts['ns'], $this->ns_opts['newns'], $this->ns_opts['remaining'])); ptln('

'); ptln($this->helper->getNSMoveButton('continue')); ptln($this->helper->getNSMoveButton('abort')); - } - } else { - $this->printForm(); + break; + default: + $this->printForm(); } ptln('
'); ptln(''); @@ -124,7 +128,7 @@ function printForm() { $form->endFieldset(); $form->printForm(); - if ($this->ns_opts !== false && !isset($this->ns_opts['remaining'])) { + if ($this->ns_opts !== false) { ptln('
'); ptln(''); ptln($this->getLang('pm_movens')); @@ -212,6 +216,7 @@ function handle() { $this->helper->skip_namespace_move_item(); } $this->ns_opts['remaining'] = $this->helper->continue_namespace_move(); + $this->ns_move_state = ($this->ns_opts['remaining'] === false ? 'error': 'continued'); if ($this->ns_opts['remaining'] === 0) { $ID = $this->helper->getNewID($INFO['id'], $this->opts['ns'], $this->opts['newns']); $ACT = 'show'; @@ -246,7 +251,7 @@ function handle() { $started = $this->helper->start_namespace_move($this->opts); if ($started !== false) { $this->ns_opts = $this->helper->get_namespace_move_opts(); - $this->ns_opts['started'] = $started; + $this->ns_move_state = 'started'; } } else { // check that the pagename is valid diff --git a/helper.php b/helper.php index f81c8ea..65f71a2 100644 --- a/helper.php +++ b/helper.php @@ -86,6 +86,8 @@ public function start_namespace_move(&$opts) { io_saveFile($files['medialist'], implode("\n", $media_files)); + $opts['remaining'] = $opts['num_media'] + $opts['num_pages']; + // save the options io_saveFile($files['opts'], serialize($opts)); @@ -109,54 +111,68 @@ public function continue_namespace_move() { $opts = unserialize(file_get_contents($files['opts'])); if (@file_exists($files['pagelist'])) { - $pagelist = file($files['pagelist'], FILE_IGNORE_NEW_LINES); + $pagelist = fopen($files['pagelist'], 'a+');; - $limit = min(10, count($pagelist)); - for ($i = 0; $i < $limit; ++$i) { - $ID = array_pop($pagelist); + for ($i = 0; $i < 10; ++$i) { + $ID = $this->get_last_id($pagelist); + if ($ID === false) { + break; + } $newID = $this->getNewID($ID, $opts['ns'], $opts['newns']); $pageOpts = $opts; $pageOpts['ns'] = getNS($ID); $pageOpts['name'] = noNS($ID); $pageOpts['newname'] = noNS($ID); $pageOpts['newns'] = getNS($newID); - if (!$this->move_page($pageOpts)) return false; - - // save the list of pages after every move - if (empty($pagelist)) { - unlink($files['pagelist']); - } else { - io_saveFile($files['pagelist'], implode("\n", $pagelist)); + if (!$this->move_page($pageOpts)) { + fclose($pagelist); + return false; } + + // update the list of pages and the options after every move + ftruncate($pagelist, ftell($pagelist)); + $opts['remaining']--; + io_saveFile($files['opts'], serialize($opts)); } - return count($pagelist) + $opts['num_media']; + + fclose($pagelist); + if ($ID === false) unlink($files['pagelist']); } elseif (@file_exists($files['medialist'])) { - $medialist = file($files['medialist'], FILE_IGNORE_NEW_LINES); + $medialist = fopen($files['medialist'], 'a+'); - $limit = min(10, count($medialist)); - for ($i = 0; $i < $limit; ++$i) { - $ID = array_pop($medialist); + for ($i = 0; $i < 10; ++$i) { + $ID = $this->get_last_id($medialist); + if ($ID === false) { + break; + } $newID = $this->getNewID($ID, $opts['ns'], $opts['newns']); $pageOpts = $opts; $pageOpts['ns'] = getNS($ID); $pageOpts['name'] = noNS($ID); $pageOpts['newname'] = noNS($ID); $pageOpts['newns'] = getNS($newID); - if (!$this->move_media($pageOpts)) return false; - - // save the list of media files after every move - if (empty($medialist)) { - unlink($files['medialist']); - unlink($files['opts']); - } else { - io_saveFile($files['medialist'], implode("\n", $medialist)); + if (!$this->move_media($pageOpts)) { + fclose($medialist); + return false; } + + // update the list of media files and the options after every move + ftruncate($medialist, ftell($medialist)); + $opts['remaining']--; + io_saveFile($files['opts'], serialize($opts)); + } + + fclose($medialist); + if ($ID === false) { + unlink($files['medialist']); + unlink($files['opts']); } - return count($medialist); } else { unlink($files['opts']); return 0; } + + return $opts['remaining']; } /** @@ -176,33 +192,67 @@ public function skip_namespace_move_item() { $opts = unserialize(file_get_contents($files['opts'])); if (@file_exists($files['pagelist'])) { - $pagelist = file($files['pagelist'], FILE_IGNORE_NEW_LINES); + $pagelist = fopen($files['pagelist'], 'a+'); - $ID = array_pop($pagelist); + $ID = $this->get_last_id($pagelist); // save the list of pages after every move - if (empty($pagelist)) { + if ($ID === false || ftell($pagelist) == 0) { + fclose($pagelist); unlink($files['pagelist']); } else { - io_saveFile($files['pagelist'], implode("\n", $pagelist)); + ftruncate($pagelist, ftell($pagelist));; + fclose($pagelist); } - return count($pagelist) + $opts['num_media']; } elseif (@file_exists($files['medialist'])) { - $medialist = file($files['medialist'], FILE_IGNORE_NEW_LINES); + $medialist = fopen($files['medialist'], 'a+'); - $ID = array_pop($medialist); + $ID = $this->get_last_id($medialist);; // save the list of media files after every move - if (empty($medialist)) { + if ($ID === false || ftell($medialist) == 0) { + fclose($medialist); unlink($files['medialist']); unlink($files['opts']); } else { - io_saveFile($files['medialist'], implode("\n", $medialist)); + ftruncate($medialist, ftell($medialist)); } - - return count($medialist); } else { unlink($files['opts']); - return 0; } + if ($opts['remaining'] == 0) return 0; + else { + $opts['remaining']--; + // save the options + io_saveFile($files['opts'], serialize($opts)); + return $opts['remaining']; + } + } + + /** + * Get last file id from the list that is stored in the file that is referenced by the handle + * The handle is set to the newline before the file id + * + * @param resource $handle The file handle to read from + * @return string|bool the last id from the list or false if there is none + */ + private function get_last_id($handle) { + // begin the seek at the end of the file + fseek($handle, 0, SEEK_END); + $id = ''; + + // seek one backwards as long as it's possible + while (fseek($handle, -1, SEEK_CUR) >= 0) { + $c = fgetc($handle); + fseek($handle, -1, SEEK_CUR); // reset the position to the character that was read + + if ($c == "\n") { + break; + } + if ($c === false) return false; // EOF, i.e. the file is empty + $id = $c.$id; + } + + if ($id === '') return false; // nothing was read i.e. the file is empty + else return $id; } /**