Index: includes/theme.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/theme.inc,v
retrieving revision 1.519
diff -u -u -p -r1.519 theme.inc
--- includes/theme.inc	31 Aug 2009 19:50:17 -0000	1.519
+++ includes/theme.inc	6 Sep 2009 00:56:05 -0000
@@ -1914,8 +1914,8 @@ function theme_username($object) {
  * @return
  *   A themed HTML string representing the progress bar.
  */
-function theme_progress_bar($percent, $message) {
-  $output = '<div id="progress" class="progress">';
+function theme_progress_bar($percent, $message, $class = '') {
+  $output = '<div id="progress" class="progress' . (empty($class) ? '' : ' ' . $class) . '">';
   $output .= '<div class="bar"><div class="filled" style="width: ' . $percent . '%"></div></div>';
   $output .= '<div class="percentage">' . $percent . '%</div>';
   $output .= '<div class="message">' . $message . '</div>';
Index: modules/search/search.admin.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/search/search.admin.inc,v
retrieving revision 1.11
diff -u -u -p -r1.11 search.admin.inc
--- modules/search/search.admin.inc	29 Aug 2009 21:05:16 -0000	1.11
+++ modules/search/search.admin.inc	6 Sep 2009 00:56:05 -0000
@@ -9,15 +9,32 @@
 /**
  * Menu callback: confirm wiping of the index.
  */
-function search_reindex_confirm() {
-  return confirm_form(array(), t('Are you sure you want to re-index the site?'),
-                  'admin/config/search/settings', t(' The search index is not cleared but systematically updated to reflect the new settings. Searching will continue to work but new content won\'t be indexed until all existing content has been re-indexed. This action cannot be undone.'), t('Re-index site'), t('Cancel'));
+function search_reset_confirm() {
+  // Create the index status form from the status metrics.
+  $metrics = _search_get_status_metrics('@done items will be affected.');
+  $form = array();
+  $form['status'] = array(
+    '#type' => 'fieldset',
+    '#description' =>  t('Search will still work while the remaining items are indexed.'),
+    'progress' => array(
+      '#type' => 'markup',
+      '#markup' => theme('progress_bar', $metrics['percentage'], $metrics['message'], 'inactive'),
+    ),
+  );
+  return confirm_form(
+    $form,
+    t('Are you sure you want to reset the search index?'),
+    'admin/config/search/settings',
+    '',
+    t('Reset index'),
+    t('Cancel')
+  );
 }
 
 /**
- * Handler for wipe confirmation
+ * Handler for wipe confirmation.
  */
-function search_reindex_confirm_submit(&$form, &$form_state) {
+function search_reset_confirm_submit(&$form, &$form_state) {
   if ($form['confirm']) {
     search_reindex();
     drupal_set_message(t('The index will be rebuilt.'));
@@ -30,7 +47,6 @@ function search_reindex_confirm_submit(&
  * Helper function to get real module names.
  */
 function _search_get_module_names() {
-
   $search_info = search_get_info();
   $modules = db_select('system', 's')
     ->fields('s', array('name', 'info'))
@@ -48,30 +64,74 @@ function _search_get_module_names() {
 }
 
 /**
- * Menu callback; displays the search module settings page.
- *
- * @ingroup forms
- * @see system_settings_form()
- * @see search_admin_settings_submit()
- * @see search_admin_reindex_submit()
+ * Return the search status metrics, which is used by several of the search forms.
  */
-function search_admin_settings() {
-  // Collect some stats
+function _search_get_status_metrics($message = array()) {
+  // Collect some stats.
   $remaining = 0;
   $total = 0;
-  foreach(variable_get('search_active_modules', array('node', 'user')) as $module) {
+  foreach (variable_get('search_active_modules', array('node', 'user')) as $module) {
     if ($status = module_invoke($module, 'search_status')) {
       $remaining += $status['remaining'];
       $total += $status['total'];
     }
   }
 
-  $count = format_plural($remaining, 'There is 1 item left to index.', 'There are @count items left to index.');
-  $percentage = ((int)min(100, 100 * ($total - $remaining) / max(1, $total))) . '%';
-  $status = '<p><strong>' . t('%percentage of the site has been indexed.', array('%percentage' => $percentage)) . ' ' . $count . '</strong></p>';
-  $form['status'] = array('#type' => 'fieldset', '#title' => t('Indexing status'));
-  $form['status']['status'] = array('#markup' => $status);
-  $form['status']['wipe'] = array('#type' => 'submit', '#value' => t('Re-index site'), '#submit' => array('search_admin_reindex_submit'));
+  // Create the count message to display.
+  if (empty($message)) {
+    $message = array(
+      'none' => 'No items indexed, @remaining items remaining.',
+      'partial' => '@done of @total items indexed.',
+      'all' => 'All @done items indexed.',
+    );
+  }
+  if (is_array($message)) {
+    $state = $remaining == 0 ? 'all' : (($remaining == $total) ? 'none' : 'partial');
+    $message = $message[$state];
+  }
+  $count = t($message, array('@remaining' => number_format($remaining), '@total' => number_format($total), '@done' => number_format($total - $remaining)));
+
+  // Return the metrics array.
+  $percentage = (int) min(100, 100 * ($total - $remaining) / max(1, $total));
+  return array('remaining' => $remaining, 'total' => $total, 'percentage' => $percentage, 'message' => $count);
+}
+
+/**
+ * Menu callback; displays the search module settings page.
+ *
+ * @ingroup forms
+ * @see system_settings_form()
+ * @see search_admin_settings_submit()
+ * @see search_admin_reset_submit()
+ * @see search_admin_index_submit()
+ */
+function search_admin_settings() {
+  $form = array();
+  // Create the index status form from the status metrics.
+  $metrics = _search_get_status_metrics();
+  $form = array();
+  $form['status'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Index Status'),
+    'progress' => array(
+      '#type' => 'markup',
+      '#markup' => theme('progress_bar', $metrics['percentage'], $metrics['message'], 'inactive'),
+    ),
+  );
+  if ($metrics['remaining'] != $metrics['total']) {
+    $form['status']['wipe'] = array(
+      '#type' => 'submit',
+      '#value' => t('Reset index'),
+      '#submit' => array('search_admin_reset_submit'),
+    );
+  }
+  if ($metrics['remaining'] > 0) {
+    $form['status']['build'] = array(
+      '#type' => 'submit',
+      '#value' => t('Index remaining items'),
+      '#submit' => array('search_admin_index_submit'),
+    );
+  }
 
   $items = drupal_map_assoc(array(10, 20, 50, 100, 200, 500));
 
@@ -156,9 +216,17 @@ function search_admin_settings_submit($f
 }
 
 /**
- * Submit callback.
+ * Submit callback to wipe the search index.
  */
-function search_admin_reindex_submit($form, &$form_state) {
+function search_admin_reset_submit($form, &$form_state) {
   // send the user to the confirmation page
-  $form_state['redirect'] = 'admin/config/search/settings/reindex';
-}
\ No newline at end of file
+  $form_state['redirect'] = 'admin/config/search/settings/reset';
+}
+
+/**
+ * Submit callback to index the search index.
+ */
+function search_admin_index_submit($form, &$form_state) {
+  // send the user to the confirmation page
+  $form_state['redirect'] = 'admin/config/search/settings/index';
+}
Index: modules/search/search.batch.inc
===================================================================
RCS file: modules/search/search.batch.inc
diff -N modules/search/search.batch.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/search/search.batch.inc	6 Sep 2009 00:56:05 -0000
@@ -0,0 +1,126 @@
+<?php
+// $Id: search.admin.inc,v 1.11 2009/08/29 21:05:16 dries Exp $
+
+/**
+ * @file
+ * Batch page callbacks for the search module.
+ */
+
+/**
+ * Menu callback: confirm wiping of the index.
+ */
+function search_batch_index_confirm() {
+  module_load_include('inc', 'search', 'search.admin');
+  // Create the index status form from the status metrics.
+  $metrics = _search_get_status_metrics('@done items will be affected.');
+  $form = array();
+  $form['status'] = array(
+    '#type' => 'fieldset',
+    '#description' =>  t('Search will still work while the remaining items are indexed. This action is resource intensive and may slow down your site. You do not need to do this. You typically let cron index your content slowly over time.'),
+    'progress' => array(
+      '#type' => 'markup',
+      '#markup' => theme('progress_bar', $metrics['percentage'], $metrics['message'], 'inactive'),
+    ),
+  );
+  return confirm_form(
+    $form,
+    t('Are you sure you want to build the search index now?'),
+    'admin/config/search/settings',
+    '',
+    t('Confirm, this may take a long time'),
+    t('Cancel')
+  );
+}
+
+/**
+ * Handler for build confirmation.
+ */
+function search_batch_index_confirm_submit(&$form, &$form_state) {
+  if ($form_state['values']['confirm']) {
+    $operations = array();
+    // Build nodes in chunks, just like cron does.
+    $results = db_query("SELECT n.nid FROM {node} n LEFT JOIN {search_dataset} d ON d.type = 'node' AND d.sid = n.nid WHERE d.sid IS NULL OR d.reindex <> 0 ORDER BY d.reindex ASC, n.nid ASC");
+    $nids = array();
+    $limit = (int) variable_get('search_cron_limit', 100);
+    while ($node = db_fetch_object($results)) {
+      $nids[] = $node->nid;
+      if (count($nids) >= $limit) {
+        $operations[] = array('search_batch_index', array($nids));
+        $nids = array();
+      }
+    }
+    if (count($nids)) {
+      $operations[] = array('search_batch_index', array($nids));
+    }
+    // When done with all the indexing, update the totals.
+    $operations[] = array('search_batch_done', array());
+
+    // when done redirect to the settings page.
+    $form_state['redirect'] = 'admin/config/search/settings';
+
+    // start the batch
+    $batch = array(
+      'title' => t('Building Search Index'),
+      'operations' => $operations,
+      'finished' => 'search_batch_finished',
+      'file' => drupal_get_path('module', 'search') . '/search.batch.inc',
+      'progress_message' => t('Completed @current of @total.') . ' '. l(t('Cancel'), $form_state['redirect']),
+    );
+    batch_set($batch);
+  }
+}
+
+/**
+ * Batch callback to index a set of nodes.
+ */
+function search_batch_index($nids, &$context) {
+  if (!isset($context['results'])) {
+    $context['results'] = array('attempted' => 0, 'success' => 0);
+  }
+
+  foreach ($nids as $nid) {
+    $context['results']['attempted'] ++;
+
+    // Build the node body.
+    $node = node_load($nid);
+    $node = node_build_content($node, 'search_index');
+    $node->rendered = drupal_render($node->content);
+
+    $text = '<h1>'. check_plain($node->title) .'</h1>'. $node->rendered;
+
+    // Fetch extra data normally not visible.
+    $extra = module_invoke_all('node_update_index', $node);
+    foreach ($extra as $t) {
+      $text .= $t;
+    }
+
+    search_index($node->nid, 'node', $text);
+    $context['results']['success'] ++;
+  }
+}
+
+/**
+ * Batch callback to update the search totals after the last search build is completed.
+ */
+function search_batch_done(&$context) {
+  // Update word IDF (Inverse Document Frequency) counts for all words
+  db_query("DELETE FROM {search_total}");
+  db_query("INSERT INTO {search_total} (word, count) SELECT word, LOG10(1+1/GREATEST(1, SUM(score))) FROM {search_index} GROUP BY word");
+}
+
+/**
+ * Batch callback when search building is finished.
+ */
+function search_batch_finished($success, $results, $operations) {
+  if ($success) {
+    if ($results['success'] == $results['attempted']) {
+      drupal_set_message(t('Finished indexing @success nodes successfully.', array('@success' => $results['success'])));
+    }
+    else {
+      drupal_set_message(t('Finished indexing @success of @attempted successfully.', array('@success' => $results['success'], '@attempt' => $results['attempted'])));
+    }
+  }
+  else {
+    drupal_set_message(t('Finished with an error.'));
+  }
+}
Index: modules/search/search.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/search/search.install,v
retrieving revision 1.22
diff -u -u -p -r1.22 search.install
--- modules/search/search.install	1 Jun 2009 22:07:09 -0000	1.22
+++ modules/search/search.install	6 Sep 2009 00:56:05 -0000
@@ -27,6 +27,34 @@ function search_uninstall() {
 }
 
 /**
+ * Implementation of hook_requirements.
+ */
+function search_requirements($phase) {
+  $requirements = array();
+
+  if ($phase == 'runtime') {
+    $t = get_t();
+    module_load_include('inc', 'search', 'search.admin');
+    $metrics = _search_get_status_metrics();
+    $requirements['search_index'] = array(
+      'title' => $t('Search index'),
+      'value' => $metrics['message'],
+    );
+    if ($metrics['remaining'] > 0) {
+      $requirements['search_index']['description'] =
+        $metrics['remaining'] == $metrics['total']
+            ? ($t('No items have been indexed yet. You can either wait for the automatic indexing process or')
+                . ' ' . l(t('index manually'), 'admin/config/search/settings/index') . '.')
+            : ($t('Search results are not complete, because some content has yet to be indexed.')
+                . ' ' . l(t('Index manually'), 'admin/config/search/settings/index'));
+      $requirements['search_index']['severity'] = REQUIREMENT_WARNING;
+    }
+  }
+
+  return $requirements;
+}
+
+/**
  * Implement hook_schema().
  */
 function search_schema() {
Index: modules/search/search.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/search/search.module,v
retrieving revision 1.314
diff -u -u -p -r1.314 search.module
--- modules/search/search.module	5 Sep 2009 10:53:09 -0000	1.314
+++ modules/search/search.module	6 Sep 2009 00:56:06 -0000
@@ -202,14 +202,22 @@ function search_menu() {
     'type' => MENU_NORMAL_ITEM,
     'file' => 'search.admin.inc',
   );
-  $items['admin/config/search/settings/reindex'] = array(
-    'title' => 'Clear index',
+  $items['admin/config/search/settings/reset'] = array(
+    'title' => 'Reset index',
     'page callback' => 'drupal_get_form',
-    'page arguments' => array('search_reindex_confirm'),
+    'page arguments' => array('search_reset_confirm'),
     'access arguments' => array('administer search'),
     'type' => MENU_CALLBACK,
     'file' => 'search.admin.inc',
   );
+  $items['admin/config/search/settings/index'] = array(
+    'title' => 'Build index',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('search_batch_index_confirm'),
+    'access arguments' => array('administer search'),
+    'type' => MENU_CALLBACK,
+    'file' => 'search.batch.inc',
+  );
   $items['admin/reports/search'] = array(
     'title' => 'Top search phrases',
     'description' => 'View most popular search phrases.',
Index: modules/system/system.css
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.css,v
retrieving revision 1.62
diff -u -u -p -r1.62 system.css
--- modules/system/system.css	5 Sep 2009 06:07:58 -0000	1.62
+++ modules/system/system.css	6 Sep 2009 00:56:06 -0000
@@ -436,6 +436,9 @@ html.js .no-js {
   height: 1.5em;
   margin: 0 0.2em;
 }
+.progress.inactive .bar {
+  background: none;
+}
 .progress .filled {
   background: #0072b9;
   height: 1em;
