diff --git a/classes/db_search.php b/classes/db_search.php index 0738112..aec14c6 100644 --- a/classes/db_search.php +++ b/classes/db_search.php @@ -55,10 +55,6 @@ class db_search extends search { /** @var array excludetables from config. */ protected $excludetables = null; - /** - * @var \stdClass tracking of current status */ - protected $status = null; - /** * Return the definition of the properties of this model. * @@ -173,88 +169,4 @@ public function get_min_search_length(): int { $minsearch = empty($this->get('regex')) ? $this->get('search') : $this->get('prematch'); return strlen($minsearch); } - - /** - * Updates a progress bar using the current status. - * @param string $table table being searched - * @param string $colname column being searched - * @return void - */ - public function update_progress_bar(string $table, string $colname): void { - if (isset($this->status) && isset($this->status->progressbar)) { - $message = "Searching in $table:$colname"; - $this->status->progressbar->update($this->status->rowcount, $this->status->totalrows, $message); - } - } - - /** - * Updates the tracking status of a search. - * @param int $rowcount number of rows that have been searched - * @param int $matches matches found - * @throws \coding_exception - * @return void - */ - public function update_status(int $rowcount, int $matches): void { - if (!isset($this->status)) { - throw new \coding_exception('Status has not been initalised'); - } - - // Update row count. - $this->status->rowcount = $rowcount; - - // Only save update search progress every 10 seconds or 5 percent. - $time = time(); - $percent = round(100 * $rowcount / $this->status->totalrows, 2); - if ($time > $this->status->prevtime + 10 || $percent > $this->status->prevpercent + 5) { - $this->set('progress', $percent); - $this->set('matches', $matches); - $this->save(); - $this->status->prevtime = $time; - $this->status->prevpercent = $percent; - } - } - - /** - * Marks a search as having started and initialises tracking. - * @param int $totalrows estimate of total rows being searched - * @return void - */ - public function mark_started(int $totalrows): void { - $this->set('timestart', time()); - $this->save(); - - // Setup tracking. - $status = new \stdClass(); - $status->prevtime = time(); - $status->prevpercent = 0; - $status->rowcount = 0; - $status->totalrows = $totalrows; - $status->progressbar = null; - - // If called from CLI, add a progress bar. - if ($this->get('origin') === 'cli') { - $status->progressbar = new \progress_bar(); - $status->progressbar->create(); - } - $this->status = $status; - } - - /** - * Saves the final values and marks a search as finished. - * - * @param int $matches matches found - * @param string $output - * @return void - */ - public function mark_finished(int $matches, string $output = ''): void { - // Update progress bar. - if (isset($this->status) && isset($this->status->progress)) { - $this->status->progress->update_full(100, "Finished saving searches into $output"); - } - - $this->set('timeend', time()); - $this->set('progress', 100); - $this->set('matches', $matches); - $this->save(); - } } diff --git a/classes/file_search.php b/classes/file_search.php index aaa2e91..0add542 100644 --- a/classes/file_search.php +++ b/classes/file_search.php @@ -158,9 +158,7 @@ public static function files(files $record, string $output = '', int $limitfrom raise_memory_limit(MEMORY_HUGE); $criteria = self::get_criteria($record); - $id = $record->get('id'); $shard = $record->is_shard(); - $filename = $record->get_filename(); // Create temp output directory. if (!$output) { $tempfile = true; @@ -177,14 +175,13 @@ public static function files(files $record, string $output = '', int $limitfrom [$whereclause, $params] = self::make_where_clause($criteria); $record->set('timestart', time()); - $updatetime = time(); - $updatepercent = 0; $matchcount = 0; $filecount = 0; $totalfiles = $DB->count_records_select('files', $whereclause, $params); if (!empty($limitnum)) { $totalfiles = min($totalfiles, $limitnum); } + $record->mark_started($totalfiles); $sql = " SELECT f.id, f.component, f.filearea, f.contextid, f.itemid, f.filename, f.filepath, f.mimetype, @@ -201,45 +198,21 @@ public static function files(files $record, string $output = '', int $limitfrom "; $fileset = $DB->get_recordset_sql($sql, $params, $limitfrom, $limitnum); foreach ($fileset as $filerecord) { + $record->update_progress_bar("Searching in $filerecord->component:$filerecord->filename"); $matchcount += self::search_file($filerecord, $criteria, $stream); $filecount ++; - $time = time(); - $percent = round(100 * $filecount / $totalfiles, 2); - if ($time > $updatetime + 10 || $percent > $updatepercent + 5) { - // Update progress bar after 5 percent or 10 seconds. - $record->set('progress', $percent); - $record->set('matches', $matchcount); - if ( ! \tool_advancedreplace\files::record_exists($id) ) { - // If record has gone, exit the job. - break; - } - $record->update(); - $updatetime = $time; - $updatepercent = $percent; + // Update status. If this returns false, the record is gone so stop searching. + if (!$record->update_status($filecount, $matchcount)) { + break; } + } $fileset->close(); fclose($stream); - if (\tool_advancedreplace\files::record_exists($id) ) { - $record->set('timeend', time()); - $record->set('progress', 100); - $record->set('matches', $matchcount); - $record->update(); - - // Save as pluginfile. - if (!empty($matchcount) && !$shard) { - $fs = get_file_storage(); - $fileinfo = [ - 'contextid' => \context_system::instance()->id, - 'component' => 'tool_advancedreplace', - 'filearea' => 'files', - 'itemid' => $id, - 'filepath' => '/', - 'filename' => $filename, - ]; - $fs->create_file_from_pathname($fileinfo, $output); - } + if ($record->search_exists()) { + $record->mark_finished($matchcount); + $record->save_pluginfile($output); } // Remove temp file. if (isset($tempfile) && file_exists($output) && !$shard) { @@ -270,10 +243,7 @@ public static function combine_shard_output(files $parent): void { } // Update parent. - $parent->set('timeend', time()); - $parent->set('matches', $matches); - $parent->set('progress', 100); - $parent->update(); + $parent->mark_finished($matches); // Copy data into one csv. $dir = make_request_directory(); @@ -297,16 +267,7 @@ public static function combine_shard_output(files $parent): void { } // Create new pluginfile. - $fs = get_file_storage(); - $fileinfo = [ - 'contextid' => \context_system::instance()->id, - 'component' => 'tool_advancedreplace', - 'filearea' => 'files', - 'itemid' => $parent->get('id'), - 'filepath' => '/', - 'filename' => $parent->get_filename(), - ]; - $fs->create_file_from_pathname($fileinfo, $output); + $parent->save_pluginfile($output); // Remove old temp files. foreach ($files as $file) { diff --git a/classes/helper.php b/classes/helper.php index 5b68b90..40fe869 100644 --- a/classes/helper.php +++ b/classes/helper.php @@ -472,7 +472,7 @@ public static function search_db(db_search $search, string $output = ''): void { $colstart = time(); // Show the table and column being searched. - $search->update_progress_bar($table, $colname); + $search->update_progress_bar("Searching in $table:$colname"); // Perform the search. $results = self::search_column($search, $table, $column, $fp); @@ -494,11 +494,10 @@ public static function search_db(db_search $search, string $output = ''): void { ]; } - if ( ! \tool_advancedreplace\db_search::record_exists($searchid) ) { - // The control row has been deleted, so we should exit. + // Update status. If this returns false, the record is gone so stop searching. + if (!$search->update_status($rowcount, $matches)) { break 2; } - $search->update_status($rowcount, $matches); } } @@ -512,21 +511,10 @@ public static function search_db(db_search $search, string $output = ''): void { mtrace(sprintf($format, $log->table, $log->column, $log->rows, $log->matches, $log->time)); } } - if (\tool_advancedreplace\db_search::record_exists($searchid) ) { + + if ($search->search_exists()) { $search->mark_finished($matches, $output); - // Save as pluginfile. - if (!empty($matches)) { - $fs = get_file_storage(); - $fileinfo = [ - 'contextid' => \context_system::instance()->id, - 'component' => 'tool_advancedreplace', - 'filearea' => 'search', - 'itemid' => $search->get('id'), - 'filepath' => '/', - 'filename' => $search->get_filename(), - ]; - $fs->create_file_from_pathname($fileinfo, $output); - } + $search->save_pluginfile($output); } // Remove temp file. if (isset($tempfile) && file_exists($output)) { diff --git a/classes/search.php b/classes/search.php index 19a23fd..99c247e 100644 --- a/classes/search.php +++ b/classes/search.php @@ -37,18 +37,13 @@ abstract class search extends \core\persistent { /** @var string Class for the adhoc task */ protected $adhoctask = ''; + /** + * @var \stdClass tracking of current status */ + protected $status = null; + /** @var array Records of child shards */ protected $shards = null; - /** - * Hook to execute before a delete. - * - * @return void - */ - protected function before_delete(): void { - // TODO: Clean up any remaining adhoc tasks. - } - /** * Hook to execute after a delete * @@ -99,7 +94,7 @@ public function queue_task(int $limitfrom = 0, int $limitnum = 0): bool { * @param int $shard if the copied data will be used for a shard. * @return \stdClass */ - public function copy_data($shard = false): \stdClass { + public function copy_data(int $shard = 0): \stdClass { $data = new \stdClass(); foreach (static::COPY_COLUMNS as $column) { $data->$column = $this->get($column); @@ -112,6 +107,105 @@ public function copy_data($shard = false): \stdClass { return $data; } + /** + * Check if the search still exists + * @return bool if the search still exists + */ + public function search_exists(): bool { + if (!static::record_exists($this->get('id'))) { + return false; + } + return true; + } + + /** + * Updates a progress bar using the current status. + * @param string $message progress message + * @return void + */ + public function update_progress_bar(string $message): void { + if (isset($this->status) && isset($this->status->progressbar)) { + $this->status->progressbar->update($this->status->itemcount, $this->status->totalcount, $message); + } + } + + /** + * Updates the tracking status of a search. + * @param int $itemcount number of items that have been searched + * @param int $matches matches found + * @throws \coding_exception + * @return bool whether we should continue searching + */ + public function update_status(int $itemcount, int $matches): bool { + if (!isset($this->status)) { + throw new \coding_exception('Status has not been initalised'); + } + + // Update item count. + $this->status->itemcount = $itemcount; + + // Only update update search progress every 10 seconds or 5 percent. + $time = time(); + $percent = round(100 * $itemcount / $this->status->totalcount, 2); + if ($time > $this->status->prevtime + 10 || $percent > $this->status->prevpercent + 5) { + // If record has gone, exit the job. + if (!$this->search_exists()) { + return false; + } + + $this->set('progress', $percent); + $this->set('matches', $matches); + $this->update(); + $this->status->prevtime = $time; + $this->status->prevpercent = $percent; + } + return true; + } + + /** + * Marks a search as having started and initialises tracking. + * @param int $totalcount estimate of total being searched + * @return void + */ + public function mark_started(int $totalcount): void { + $this->set('timestart', time()); + $this->update(); + + // Setup tracking. + $status = new \stdClass(); + $status->prevtime = time(); + $status->prevpercent = 0; + $status->itemcount = 0; + $status->totalcount = $totalcount; + $status->progressbar = null; + + // If called from CLI, add a progress bar. + if ($this->get('origin') === 'cli') { + $status->progressbar = new \progress_bar(); + $status->progressbar->create(); + } + $this->status = $status; + } + + /** + * Saves the final values and marks a search as finished. + * + * @param int $matches matches found + * @param string $output + * @return void + */ + public function mark_finished(int $matches, string $output = ''): void { + // Update progress bar. + if (isset($this->status) && isset($this->status->progressbar)) { + $this->status->progressbar->update_full(100, "Finished saving searches into $output"); + } + + $this->set('timeend', time()); + $this->set('progress', 100); + $this->set('matches', $matches); + $this->update(); + } + /** * Gets the duration of a search * @return int duration @@ -152,7 +246,7 @@ public function get_temp_filepath(): string { * @param bool $temp add temp identifier to pluginfile * @return string pluginfile url */ - public function get_pluginfile_url($temp = false) { + public function get_pluginfile_url(bool $temp = false): string { $pathname = $temp ? '/temp/' : '/'; return \moodle_url::make_pluginfile_url( \context_system::instance()->id, @@ -164,6 +258,28 @@ public function get_pluginfile_url($temp = false) { )->out(); } + /** + * Saves search output as a pluginfile + * @param string $output path + * @return void + */ + public function save_pluginfile(string $output): void { + if (empty($this->get('matches')) || $this->is_shard()) { + return; + } + + $fs = get_file_storage(); + $fileinfo = [ + 'contextid' => \context_system::instance()->id, + 'component' => 'tool_advancedreplace', + 'filearea' => $this->filearea, + 'itemid' => $this->get('id'), + 'filepath' => '/', + 'filename' => $this->get_filename(), + ]; + $fs->create_file_from_pathname($fileinfo, $output); + } + /** * Gets the file for the search output * @return bool|\stored_file @@ -185,7 +301,7 @@ public function get_file() { * @param bool $temp add temp identifier to filename * @return string filename */ - public function get_filename($temp = false): string { + public function get_filename(bool $temp = false): string { // The hardcoded default filename should not be changed. $name = $this->get('name'); $shard = $this->is_shard();