You are here

background_batch.pages.inc in Background Process 6

Pages for background batch.

@todo Implement proper error page instead of just 404.

File

background_batch/background_batch.pages.inc
View source
<?php

/**
 * @file
 *
 * Pages for background batch.
 *
 * @todo Implement proper error page instead of just 404.
 */

/**
 * System settings page.
 */
function background_batch_settings_form() {
  $form = array();
  $form['background_batch_delay'] = array(
    '#type' => 'textfield',
    '#default_value' => variable_get('background_batch_delay', BACKGROUND_BATCH_DELAY),
    '#title' => 'Delay',
    '#description' => t('Time in microseconds for progress refresh'),
  );
  $form['background_batch_process_lifespan'] = array(
    '#type' => 'textfield',
    '#default_value' => variable_get('background_batch_process_lifespan', BACKGROUND_BATCH_PROCESS_LIFESPAN),
    '#title' => 'Process lifespan',
    '#description' => t('Time in milliseconds for progress lifespan'),
  );
  $form['background_batch_show_eta'] = array(
    '#type' => 'checkbox',
    '#default_value' => variable_get('background_batch_show_eta', BACKGROUND_BATCH_PROCESS_ETA),
    '#title' => 'Show ETA of batch process',
    '#description' => t('Whether ETA (estimated time of arrival) information should be shown'),
  );
  return system_settings_form($form);
}

/**
 * Overview of current and recent batch jobs.
 */
function background_batch_overview_page() {
  $data = array();
  $sql = "\nSELECT b.bid\nFROM {batch} b\nORDER BY b.bid\n";
  $result = db_query($sql);
  $bids = array();
  while ($row = db_fetch_object($result)) {
    $bids[] = $row->bid;
  }
  foreach ($bids as $bid) {
    $progress = progress_get_progress('_background_batch:' . $bid);
    $eta = progress_estimate_completion($progress);
    $data[] = array(
      $progress->end ? $bid : l($bid, 'batch', array(
        'query' => array(
          'op' => 'start',
          'id' => $bid,
        ),
      )),
      sprintf("%.2f%%", $progress->progress * 100),
      $progress->message,
      $progress->start ? format_date((int) $progress->start, 'small') : t('N/A'),
      $progress->end ? format_date((int) $progress->end, 'small') : ($eta ? format_date((int) $eta, 'small') : t('N/A')),
    );
  }
  $header = array(
    'Batch ID',
    'Progress',
    'Message',
    'Started',
    'Finished/ETA',
  );
  return theme('table', $header, $data);
}

/**
 * Default page callback for batches.
 */
function background_batch_page() {
  require_once 'includes/batch.inc';
  $output = _background_batch_page();
  if ($output === FALSE) {
    drupal_access_denied();
  }
  elseif (isset($output)) {

    // Force a page without blocks or messages to
    // display a list of collected messages later.
    print theme('page', $output, FALSE, FALSE);
  }
}

/**
 * State-based dispatcher for the batch processing page.
 */
function _background_batch_page() {
  $id = isset($_REQUEST['id']) ? $_REQUEST['id'] : FALSE;
  if (!$id) {
    return drupal_not_found();
  }

  // Retrieve the current state of batch from db.
  $data = db_result(db_query("SELECT batch FROM {batch} WHERE bid = %d", $id));
  if (!$data) {
    return drupal_not_found();
  }
  $batch =& batch_get();
  $batch = unserialize($data);

  // Manually call our alter if hook_batch_alter() is unsupported on this system.
  if (empty($batch['batch_altered'])) {
    background_batch_batch_alter($batch);

    // Save batch to DB.
    _batch_shutdown();
  }

  // Check if the current user owns (has access to) this batch.
  global $user;
  if ($batch['uid'] != $user->uid) {
    return drupal_access_denied();
  }
  $op = isset($_REQUEST['op']) ? $_REQUEST['op'] : '';
  switch ($op) {
    case 'start':
      return _background_batch_page_start();
    case 'do':
      return _background_batch_page_do_js();
    case 'do_nojs':
      return _background_batch_page_do_nojs();
    case 'finished':
      require_once 'includes/batch.inc';
      progress_remove_progress('_background_batch:' . $id);
      return _batch_finished();
    default:
      drupal_goto('admin/settings/batch/overview');
  }
}

/**
 * Start a batch job in the background
 */
function _background_batch_initiate($process = NULL) {
  require_once 'includes/batch.inc';
  $batch =& batch_get();
  $id = $batch['id'];
  $handle = 'background_batch:' . $id;
  if (!$process) {
    $process = background_process_get_process($handle);
  }
  if ($process) {

    // If batch is already in progress, goto to the status page instead of starting it.
    if ($process->exec_status == BACKGROUND_PROCESS_STATUS_RUNNING) {
      return $process;
    }

    // If process is locked and hasn't started for X seconds, then relaunch
    if ($process->exec_status == BACKGROUND_PROCESS_STATUS_LOCKED && $process->start_stamp + variable_get('background_process_redispatch_threshold', BACKGROUND_PROCESS_REDISPATCH_THRESHOLD) < time()) {
      $process = BackgroundProcess::load($process);
      $process
        ->dispatch();
    }
    return $process;
  }
  else {

    // Hasn't run yet or has stopped. (re)start batch job.
    $process = new BackgroundProcess($handle);
    $process->service_host = 'background_batch';
    if ($process
      ->lock()) {
      $message = $batch['sets'][0]['init_message'];
      progress_initialize_progress('_' . $handle, $message);
      if (function_exists('progress_set_progress_start')) {
        progress_set_progress_start('_' . $handle, $batch['timestamp']);
      }
      else {
        db_query("UPDATE {progress} SET start = :start WHERE name = :name", array(
          ':start' => $batch['timestamp'],
          ':name' => '_' . $handle,
        ));
      }
      $result = $process
        ->execute('_background_batch_process', array(
        $id,
      ));
      return $process;
    }
  }
}
function _background_batch_page_start() {
  _background_batch_initiate();
  if (isset($_COOKIE['has_js']) && $_COOKIE['has_js']) {
    return _background_batch_page_progress_js();
  }
  else {
    return _background_batch_page_do_nojs();
  }
}

/**
 * Batch processing page with JavaScript support.
 */
function _background_batch_page_progress_js() {
  $batch = batch_get();

  // The first batch set gets to set the page title
  // and the initialization and error messages.
  $current_set = _batch_current_set();
  drupal_set_title($current_set['title']);
  drupal_add_js('misc/progress.js', 'core', 'header', FALSE, FALSE);
  $js_setting['batch'] = array();
  $js_setting['batch']['errorMessage'] = $current_set['error_message'] . '<br />' . $batch['error_message'];

  // Check wether ETA information should be shown.
  if (variable_get('background_batch_show_eta', BACKGROUND_BATCH_PROCESS_ETA)) {
    $js_setting['batch']['initMessage'] = 'ETA: ' . t('N/A') . '<br/>' . $current_set['init_message'];
  }
  else {
    $js_setting['batch']['initMessage'] = $current_set['init_message'];
  }
  $js_setting['batch']['uri'] = url($batch['url'], array(
    'query' => array(
      'id' => $batch['id'],
    ),
  ));
  $js_setting['batch']['delay'] = variable_get('background_batch_delay', BACKGROUND_BATCH_DELAY);
  drupal_add_js($js_setting, 'setting');
  drupal_add_js(drupal_get_path('module', 'background_batch') . '/js/progress.js', 'module', 'header', FALSE, FALSE);
  drupal_add_js(drupal_get_path('module', 'background_batch') . '/js/batch.js', 'module', 'header', FALSE, FALSE);
  $output = '<div id="progress"></div>';
  return $output;
}

/**
 * Do one pass of execution and inform back the browser about progression
 * (used for JavaScript-mode only).
 */
function _background_batch_page_do_js() {

  // HTTP POST required.
  if ($_SERVER['REQUEST_METHOD'] != 'POST') {
    drupal_set_message(t('HTTP POST is required.'), 'error');
    drupal_set_title(t('Error'));
    return '';
  }
  $batch =& batch_get();
  $id = $batch['id'];
  session_save_session(FALSE);
  $percentage = t('N/A');
  $message = '';
  if ($progress = progress_get_progress('_background_batch:' . $id)) {
    $percentage = $progress->progress * 100;
    $message = $progress->message;
    progress_estimate_completion($progress);

    // Check wether ETA information should be shown.
    if (variable_get('background_batch_show_eta', BACKGROUND_BATCH_PROCESS_ETA)) {
      $message = "ETA: " . ($progress->estimate ? format_date((int) $progress->estimate, 'large') : t('N/A')) . "<br/>{$message}";
    }
    else {
      $js_setting['batch']['initMessage'] = $message;
    }
  }
  if (count($batch['sets'][$batch['current_set']]['operations']) == 0) {

    // The background process has self-destructed, and the batch job is done.
    $percentage = 100;
    $message = '';
  }
  elseif ($process = background_process_get_process('background_batch:' . $id)) {
    _background_batch_initiate($process);
  }
  else {

    // Not running ... and stale?
    _background_batch_initiate();
  }
  drupal_json(array(
    'status' => TRUE,
    'percentage' => sprintf("%.02f", $percentage),
    'message' => $message,
  ));
}

/**
 * Output a batch processing page without JavaScript support.
 *
 * @see _batch_process()
 */
function _background_batch_page_do_nojs() {
  $batch =& batch_get();
  $id = $batch['id'];
  _background_batch_initiate();
  $current_set = _batch_current_set();
  drupal_set_title($current_set['title']);
  $new_op = 'do_nojs';

  // This is one of the later requests: do some processing first.
  // Error handling: if PHP dies due to a fatal error (e.g. non-existant
  // function), it will output whatever is in the output buffer,
  // followed by the error message.
  ob_start();
  $fallback = $current_set['error_message'] . '<br/>' . $batch['error_message'];
  drupal_maintenance_theme();
  $fallback = theme('maintenance_page', $fallback, FALSE, FALSE);

  // We strip the end of the page using a marker in the template, so any
  // additional HTML output by PHP shows up inside the page rather than
  // below it. While this causes invalid HTML, the same would be true if
  // we didn't, as content is not allowed to appear after </html> anyway.
  list($fallback) = explode('<!--partial-->', $fallback);
  print $fallback;
  $percentage = t('N/A');
  $message = '';

  // Get progress
  if ($progress = progress_get_progress('_background_batch:' . $id)) {
    $percentage = $progress->progress * 100;
    $message = $progress->message;
    progress_estimate_completion($progress);

    // Check wether ETA information should be shown.
    if (variable_get('background_batch_show_eta', BACKGROUND_BATCH_PROCESS_ETA)) {
      $message = "ETA: " . ($progress->estimate ? format_date((int) $progress->estimate, 'large') : t('N/A')) . "<br/>{$message}";
    }
  }
  if (count($batch['sets'][$batch['current_set']]['operations']) == 0) {

    // The background process has self-destructed, and the batch job is done.
    $percentage = 100;
    $message = '';
  }
  elseif ($process = background_process_get_process('background_batch:' . $id)) {
    _background_batch_initiate($process);
  }
  else {

    // Not running ... and stale?
    _background_batch_initiate();
  }
  if ($percentage == 100) {
    $new_op = 'finished';
  }

  // PHP did not die : remove the fallback output.
  ob_end_clean();
  $url = url($batch['url'], array(
    'query' => array(
      'id' => $batch['id'],
      'op' => $new_op,
    ),
  ));
  drupal_set_html_head('<meta http-equiv="Refresh" content="0; URL=' . $url . '">');
  $output = theme('progress_bar', sprintf("%.02f", $percentage), $message);
  return $output;
}

Functions

Namesort descending Description
background_batch_overview_page Overview of current and recent batch jobs.
background_batch_page Default page callback for batches.
background_batch_settings_form System settings page.
_background_batch_initiate Start a batch job in the background
_background_batch_page State-based dispatcher for the batch processing page.
_background_batch_page_do_js Do one pass of execution and inform back the browser about progression (used for JavaScript-mode only).
_background_batch_page_do_nojs Output a batch processing page without JavaScript support.
_background_batch_page_progress_js Batch processing page with JavaScript support.
_background_batch_page_start