background_batch.pages.inc 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. <?php
  2. /**
  3. * @file
  4. *
  5. * Pages for background batch.
  6. *
  7. * @todo Implement proper error page instead of just 404.
  8. */
  9. /**
  10. * System settings page.
  11. */
  12. function background_batch_settings_form() {
  13. $form = array();
  14. $form['background_batch_delay'] = array(
  15. '#type' => 'textfield',
  16. '#default_value' => variable_get('background_batch_delay', BACKGROUND_BATCH_DELAY),
  17. '#title' => 'Delay',
  18. '#description' => t('Time in microseconds for progress refresh'),
  19. );
  20. $form['background_batch_process_lifespan'] = array(
  21. '#type' => 'textfield',
  22. '#default_value' => variable_get('background_batch_process_lifespan', BACKGROUND_BATCH_PROCESS_LIFESPAN),
  23. '#title' => 'Process lifespan',
  24. '#description' => t('Time in milliseconds for progress lifespan'),
  25. );
  26. $form['background_batch_show_eta'] = array(
  27. '#type' => 'checkbox',
  28. '#default_value' => variable_get('background_batch_show_eta', BACKGROUND_BATCH_PROCESS_ETA),
  29. '#title' => 'Show ETA of batch process',
  30. '#description' => t('Whether ETA (estimated time of arrival) information should be shown'),
  31. );
  32. return system_settings_form($form);
  33. }
  34. /**
  35. * Overview of current and recent batch jobs.
  36. */
  37. function background_batch_overview_page() {
  38. $data = array();
  39. $bids = db_select('batch', 'b')
  40. ->fields('b', array('bid'))
  41. ->orderBy('b.bid', 'ASC')
  42. ->execute()
  43. ->fetchAllKeyed(0, 0);
  44. foreach ($bids as $bid) {
  45. $progress = progress_get_progress('_background_batch:' . $bid);
  46. $eta = progress_estimate_completion($progress);
  47. $data[] = array(
  48. $progress->end ? $bid : l($bid, 'batch', array('query' => array('op' => 'start', 'id' => $bid))),
  49. sprintf("%.2f%%", $progress->progress * 100),
  50. $progress->message,
  51. $progress->start ? format_date((int)$progress->start, 'small') : t('N/A'),
  52. $progress->end ? format_date((int)$progress->end, 'small') : ($eta ? format_date((int)$eta, 'small') : t('N/A')),
  53. );
  54. }
  55. $header = array('Batch ID', 'Progress', 'Message', 'Started', 'Finished/ETA');
  56. return theme('table', array(
  57. 'header' => $header,
  58. 'rows' => $data
  59. ));
  60. }
  61. /**
  62. * State-based dispatcher for the batch processing page.
  63. */
  64. function background_batch_page() {
  65. $id = isset($_REQUEST['id']) ? $_REQUEST['id'] : FALSE;
  66. if (!$id) {
  67. return drupal_not_found();
  68. }
  69. // Retrieve the current state of batch from db.
  70. $data = db_query("SELECT batch FROM {batch} WHERE bid = :bid", array(':bid' => $id))->fetchColumn();
  71. if (!$data) {
  72. return drupal_not_found();
  73. }
  74. $batch =& batch_get();
  75. $batch = unserialize($data);
  76. // Check if the current user owns (has access to) this batch.
  77. global $user;
  78. if ($batch['uid'] != $user->uid) {
  79. return drupal_access_denied();
  80. }
  81. $op = isset($_REQUEST['op']) ? $_REQUEST['op'] : '';
  82. switch ($op) {
  83. case 'start':
  84. return _background_batch_page_start();
  85. case 'do':
  86. return _background_batch_page_do_js();
  87. case 'do_nojs':
  88. return _background_batch_page_do_nojs();
  89. case 'finished':
  90. progress_remove_progress('_background_batch:' . $id);
  91. return _batch_finished();
  92. default:
  93. drupal_goto('admin/config/system/batch/overview');
  94. }
  95. }
  96. /**
  97. * Start a batch job in the background
  98. */
  99. function _background_batch_initiate($process = NULL) {
  100. require_once('includes/batch.inc');
  101. $batch =& batch_get();
  102. $id = $batch['id'];
  103. $handle = 'background_batch:' . $id;
  104. if (!$process) {
  105. $process = background_process_get_process($handle);
  106. }
  107. if ($process) {
  108. // If batch is already in progress, goto to the status page instead of starting it.
  109. if ($process->exec_status == BACKGROUND_PROCESS_STATUS_RUNNING) {
  110. return $process;
  111. }
  112. // If process is locked and hasn't started for X seconds, then relaunch
  113. if (
  114. $process->exec_status == BACKGROUND_PROCESS_STATUS_LOCKED &&
  115. $process->start_stamp + variable_get('background_process_redispatch_threshold', BACKGROUND_PROCESS_REDISPATCH_THRESHOLD) < time()
  116. ) {
  117. $process = BackgroundProcess::load($process);
  118. $process->dispatch();
  119. }
  120. return $process;
  121. }
  122. else {
  123. // Hasn't run yet or has stopped. (re)start batch job.
  124. $process = new BackgroundProcess($handle);
  125. $process->service_host = 'background_batch';
  126. if ($process->lock()) {
  127. $message = $batch['sets'][0]['init_message'];
  128. progress_initialize_progress('_' . $handle, $message);
  129. if (function_exists('progress_set_progress_start')) {
  130. progress_set_progress_start('_' . $handle, $batch['timestamp']);
  131. }
  132. else {
  133. db_query("UPDATE {progress} SET start = :start WHERE name = :name", array(':start' => $batch['timestamp'], ':name' => '_' . $handle));
  134. }
  135. $result = $process->execute('_background_batch_process', array($id));
  136. return $process;
  137. }
  138. }
  139. }
  140. function _background_batch_page_start() {
  141. _background_batch_initiate();
  142. if (isset($_COOKIE['has_js']) && $_COOKIE['has_js']) {
  143. return _background_batch_page_progress_js();
  144. }
  145. else {
  146. return _background_batch_page_do_nojs();
  147. }
  148. }
  149. /**
  150. * Batch processing page with JavaScript support.
  151. */
  152. function _background_batch_page_progress_js() {
  153. require_once('includes/batch.inc');
  154. $batch = batch_get();
  155. $current_set = _batch_current_set();
  156. drupal_set_title($current_set['title'], PASS_THROUGH);
  157. // Merge required query parameters for batch processing into those provided by
  158. // batch_set() or hook_batch_alter().
  159. $batch['url_options']['query']['id'] = $batch['id'];
  160. $js_setting['batch'] = array();
  161. $js_setting['batch']['errorMessage'] = $current_set['error_message'] . '<br />' . $batch['error_message'];
  162. // Check wether ETA information should be shown.
  163. if (variable_get('background_batch_show_eta', BACKGROUND_BATCH_PROCESS_ETA)) {
  164. $js_setting['batch']['initMessage'] = 'ETA: ' . t('N/A') . '<br/>' . $current_set['init_message'];
  165. }
  166. else {
  167. $js_setting['batch']['initMessage'] = $current_set['init_message'];
  168. }
  169. $js_setting['batch']['uri'] = url($batch['url'], $batch['url_options']);
  170. $js_setting['batch']['delay'] = variable_get('background_batch_delay', BACKGROUND_BATCH_DELAY);
  171. drupal_add_js($js_setting, 'setting');
  172. drupal_add_library('background_batch', 'background-process.batch');
  173. return '<div id="progress"></div>';
  174. }
  175. /**
  176. * Do one pass of execution and inform back the browser about progression
  177. * (used for JavaScript-mode only).
  178. */
  179. function _background_batch_page_do_js() {
  180. // HTTP POST required.
  181. if ($_SERVER['REQUEST_METHOD'] != 'POST') {
  182. drupal_set_message(t('HTTP POST is required.'), 'error');
  183. drupal_set_title(t('Error'));
  184. return '';
  185. }
  186. $batch = &batch_get();
  187. $id = $batch['id'];
  188. drupal_save_session(FALSE);
  189. $percentage = t('N/A');
  190. $message = '';
  191. if ($progress = progress_get_progress('_background_batch:' . $id)) {
  192. $percentage = $progress->progress * 100;
  193. $message = $progress->message;
  194. progress_estimate_completion($progress);
  195. // Check wether ETA information should be shown.
  196. if (variable_get('background_batch_show_eta', BACKGROUND_BATCH_PROCESS_ETA)) {
  197. $message = "ETA: " . ($progress->estimate ? format_date((int)$progress->estimate, 'large') : t('N/A')) . "<br/>$message";
  198. }
  199. else {
  200. $js_setting['batch']['initMessage'] = $message;
  201. }
  202. }
  203. if ($batch['sets'][$batch['current_set']]['count'] == 0) {
  204. // The background process has self-destructed, and the batch job is done.
  205. $percentage = 100;
  206. $message = '';
  207. }
  208. elseif ($process = background_process_get_process('background_batch:' . $id)) {
  209. _background_batch_initiate($process);
  210. }
  211. else {
  212. // Not running ... and stale?
  213. _background_batch_initiate();
  214. }
  215. drupal_json_output(array('status' => TRUE, 'percentage' => sprintf("%.02f", $percentage), 'message' => $message));
  216. }
  217. /**
  218. * Output a batch processing page without JavaScript support.
  219. *
  220. * @see _batch_process()
  221. */
  222. function _background_batch_page_do_nojs() {
  223. $batch = &batch_get();
  224. $id = $batch['id'];
  225. _background_batch_initiate();
  226. $current_set = _batch_current_set();
  227. drupal_set_title($current_set['title'], PASS_THROUGH);
  228. $new_op = 'do_nojs';
  229. // This is one of the later requests; do some processing first.
  230. // Error handling: if PHP dies due to a fatal error (e.g. a nonexistent
  231. // function), it will output whatever is in the output buffer, followed by
  232. // the error message.
  233. ob_start();
  234. $fallback = $current_set['error_message'] . '<br />' . $batch['error_message'];
  235. $fallback = theme('maintenance_page', array('content' => $fallback, 'show_messages' => FALSE));
  236. // We strip the end of the page using a marker in the template, so any
  237. // additional HTML output by PHP shows up inside the page rather than below
  238. // it. While this causes invalid HTML, the same would be true if we didn't,
  239. // as content is not allowed to appear after </html> anyway.
  240. list($fallback) = explode('<!--partial-->', $fallback);
  241. print $fallback;
  242. $percentage = t('N/A');
  243. $message = '';
  244. // Get progress
  245. if ($progress = progress_get_progress('_background_batch:' . $id)) {
  246. $percentage = $progress->progress * 100;
  247. $message = $progress->message;
  248. progress_estimate_completion($progress);
  249. // Check wether ETA information should be shown.
  250. if (variable_get('background_batch_show_eta', BACKGROUND_BATCH_PROCESS_ETA)) {
  251. $message = "ETA: " . ($progress->estimate ? format_date((int)$progress->estimate, 'large') : t('N/A')) . "<br/>$message";
  252. }
  253. }
  254. if ($batch['sets'][$batch['current_set']]['count'] == 0) {
  255. // The background process has self-destructed, and the batch job is done.
  256. $percentage = 100;
  257. $message = '';
  258. }
  259. elseif ($process = background_process_get_process('background_batch:' . $id)) {
  260. _background_batch_initiate($process);
  261. }
  262. else {
  263. // Not running ... and stale?
  264. _background_batch_initiate();
  265. }
  266. if ($percentage == 100) {
  267. $new_op = 'finished';
  268. }
  269. // PHP did not die; remove the fallback output.
  270. ob_end_clean();
  271. // Merge required query parameters for batch processing into those provided by
  272. // batch_set() or hook_batch_alter().
  273. $batch['url_options']['query']['id'] = $batch['id'];
  274. $batch['url_options']['query']['op'] = $new_op;
  275. $url = url($batch['url'], $batch['url_options']);
  276. $element = array(
  277. '#tag' => 'meta',
  278. '#attributes' => array(
  279. 'http-equiv' => 'Refresh',
  280. 'content' => '0; URL=' . $url,
  281. ),
  282. );
  283. drupal_add_html_head($element, 'batch_progress_meta_refresh');
  284. return theme('progress_bar', array('percent' => sprintf("%.02f", $percentage), 'message' => $message));
  285. }