You are here

cdn_cron.inc in CDN 5

Basic functions for CDN synchronization cron.

File

cdn_cron.inc
View source
<?php

/**
 * @file
 * Basic functions for CDN synchronization cron.
 */

/**
 * Executes a CDN synchronization cron run when called
 *
 * Graciously stolen from Drupal's includes/common.inc.
 */
function cdn_cron_run() {
  if (!ini_get('safe_mode')) {
    set_time_limit(240);
  }

  // Fetch the CDN synchronization cron semaphore
  $semaphore = variable_get('cdn_cron_semaphore', FALSE);
  _cdn_cron_delete_temporary_files();
  if ($semaphore) {
    if (time() - $semaphore > 3600) {

      // Either cron has been running for more than an hour or the semaphore
      // was not reset due to a database error.
      watchdog('cdn_cron', 'CDN synchronization cron has been running for more than an hour and is most likely stuck.', WATCHDOG_ERROR);

      // Release CDN synchronization cron semaphore
      variable_del('cdn_cron_semaphore');
    }
    else {

      // CDN synchronization cron is still running normally.
      watchdog('cdn_cron', 'Attempting to re-run CDN synchronization cron while it is already running.', WATCHDOG_WARNING);
    }
  }
  else {

    // Register shutdown callback
    register_shutdown_function('cdn_cron_cleanup');

    // Lock CDN synchronization cron semaphore
    variable_set('cdn_cron_semaphore', time());

    // Get the CDN integration configuration.
    $sync_method = variable_get('cdn_sync_method', 'ftp');
    $sync_filters = variable_get('cdn_sync_filters', array());
    $sync_method_settings = variable_get('cdn_sync_method_settings', FALSE);

    // Get the list of files, the unique settings for each file, and the
    // update settings for each file.
    list($files, $files_unique_settings, $files_update_setting) = _cdn_cron_get_files_to_sync($sync_filters);

    // Make all filenames unique.
    $files_unique = array_combine(array_keys($files), array_map('cdn_unique_filename', array_keys($files), $files_unique_settings));

    // Store the files that are being synced in a variable, these URLs will be
    // used while updating files.
    variable_set('cdn_cron_files_syncing', $files_unique);

    // Update files if necessary.
    $files_updated = array_combine(array_keys($files), _cdn_cron_update_files_wrapper(array_keys($files), $files_update_setting));

    // Perform the CDN synchronization using the configured method. Default to
    // the FTP  method.
    require_once drupal_get_path('module', 'cdn') . "/sync_plugins/{$sync_method}.inc";
    $sync_hook = $sync_method . '_cdn_cron_perform_sync';
    timer_start('cdn_sync');
    $stats = $sync_hook($files, $files_unique, $files_updated, _cdn_cron_init_stats(), $sync_method_settings);
    $timer = timer_stop('cdn_sync');
    _cdn_cron_delete_temporary_files();

    // Build message.
    $duration = round($timer['time'] / 1000, 1);
    extract($stats);
    $exists_kbytes = number_format($exists_bytes / 1024, 1);
    $uploaded_kbytes = number_format($uploaded_bytes / 1024, 1);
    $message = "<ul>";
    $message = "<li>Method: <em>{$sync_method}</em></li>";
    $message .= "<li>Duration: <em>{$duration}</em> seconds</li>";
    $message .= "<li>Deleted: <em>{$deletes}</em> files</li>";
    if ($exists_bytes) {
      $message .= "<li>No action: <em>{$exists}</em> files ({$exists_kbytes} KB)</li>";
    }
    else {
      $message .= "<li>No action: <em>{$exists}</em> files</li>";
    }
    if ($uploaded_bytes) {
      $message .= "<li>Uploaded: <em>{$uploads}</em> files ({$uploaded_kbytes} KB)</li>";
    }
    else {
      $message .= "<li>Uploaded: <em>{$uploads}</em> files</li>";
    }
    $message .= '</ul>';

    // Log to watchdog.
    watchdog('cdn_cron', "CDN synchronization cron run completed.<br />{$message}", WATCHDOG_NOTICE);
    if ($uploads_failed) {
      watchdog('cdn_cron', "{$uploads_failed} file uploads have failed.", WATCHDOG_ERROR);
    }

    // Record CDN synchronization cron time and statistics message for usage
    // in the status report at admin/logs/status.
    variable_set('cdn_cron_last', time());
    variable_set('cdn_cron_last_stats', $message);

    // Record which files have been synchronized. We have to know this to be
    // able to generate CDN URLs only for files that have been synchronized.
    variable_set('cdn_files_synced', $files_unique);

    // Delete the variable that contains an array of files that were being
    // synced, which /are/ synced by now.
    variable_del('cdn_cron_files_syncing');

    // Release CDN synchronization cron semaphore
    variable_del('cdn_cron_semaphore');

    // Return TRUE so other functions can check if it did run successfully
    return TRUE;
  }
}

/**
 * Shutdown function for CDN cron cleanup.
 *
 * Also graciously stolen from Drupal's includes/common.inc.
 */
function cdn_cron_cleanup() {

  // See if the semaphore is still locked.
  if (variable_get('cdn_cron_semaphore', FALSE)) {
    watchdog('cdn_cron', 'CDN synchronization cron run exceeded the time limit and was aborted.', WATCHDOG_WARNING);

    // Release CDN synchronization cron semaphore
    variable_del('cdn_cron_semaphore');
    _cdn_cron_delete_temporary_files();
  }
}

//----------------------------------------------------------------------------

// "Global" functions used in the synchronization process.

/**
 * Simple logging function for sync plugin debugging purposes only.
 *
 * @param $message
 *   The message to log. The current time will be prepended automatically.
 */
function cdn_log($message) {
  $output = 'console';
  if (DEBUG_OUTPUT !== FALSE) {
    $output = DEBUG_OUTPUT;
  }
  if (DEBUG) {
    $time = '[' . date('H:i:s') . '] ';
    switch ($output) {
      case 'html':
        ob_start();
        print '<pre>' . $time . $message . "</pre>\n";
        ob_flush();
        break;
      case 'console':
      default:
        print $time . $message . "\n";
        break;
    }
  }
}

//----------------------------------------------------------------------------

// Private functions.

/**
 * Generates the list of files that has to be sync, based on a file name
 * pattern, a list of ignored directories, a list of directories of which any
 * file will be included, and an exclude pattern.
 *
 * @param $filters
 *   A set of filters, as specified in README.txt.
 * @return
 *   An array of which the keys are filepaths and the values are filesizes.
 */
function _cdn_cron_get_files_to_sync($filters) {
  $files = array();
  $files_unique_settings = array();
  $files_update_settings = array();
  foreach ($filters as $filter) {
    $newfiles = array();
    foreach ($filter['paths'] as $path) {
      $newfiles = _cdn_cron_scan_directory($path, $filter['pattern'], $filter['ignored_dirs']);
      if (count($newfiles)) {

        // Get relative filepaths.
        $newfiles = array_combine(array_map('cdn_clean_filepath', array_keys($newfiles)), array_values($newfiles));

        // For some unique methods, we have to store some extra parameters.
        $extra_params = FALSE;
        if ($filter['unique_method'] == 'md5 of mtimes') {
          $extra_params = array(
            'path' => $path,
            'files' => array_keys($newfiles),
          );
        }

        // Store the unique settings for each file.
        $unique_settings = array_fill(0, count($newfiles), array(
          'method' => $filter['unique_method'],
          'where' => $filter['unique'],
          'params' => $extra_params,
        ));
        $files_unique_settings += array_combine(array_keys($newfiles), $unique_settings);

        // If desired, update the URLs that are embedded in the files.
        $update_settings = array_fill(0, count($newfiles), isset($filter['update_urls_in_files']) && $filter['update_urls_in_files'] === TRUE ? TRUE : FALSE);
        $files_update_settings += array_combine(array_keys($newfiles), $update_settings);
        $files += $newfiles;
      }
    }
  }
  return array(
    $files,
    $files_unique_settings,
    $files_update_settings,
  );
}

/**
 * Stolen from Drupal core. Simplified for CDN cron usage. Parameter order is
 * unchanged, so you can look at the official file_scan_directory()
 * documentation but think away the last parameters.
 *
 * @see file_scan_directory
 */
function _cdn_cron_scan_directory($dir, $pattern, $ignored_dirs = array(
  'CVS',
)) {
  $files = array();
  if (is_dir($dir) && ($handle = opendir($dir))) {
    while ($file = readdir($handle)) {
      if (!in_array($file, $ignored_dirs) && $file[0] != '.') {
        if (is_dir("{$dir}/{$file}")) {
          $files += _cdn_cron_scan_directory("{$dir}/{$file}", $pattern, $ignored_dirs);
        }
        elseif (ereg($pattern, $file)) {
          $files["{$dir}/{$file}"] = filesize("{$dir}/{$file}");
        }
      }
    }
    closedir($handle);
  }
  return $files;
}

/**
 * Initializes the stats array.
 *
 * @return
 *   The stats array.
 */
function _cdn_cron_init_stats() {
  $stats = array(
    'deletes' => 0,
    'exists' => 0,
    'exists_bytes' => 0,
    'uploads' => 0,
    'uploads_failed' => 0,
    'uploaded_bytes' => 0,
  );
  return $stats;
}

/**
 * Wrapper of cdn_update_file() for use only within cron mode. This function
 * simply calls cdn_update_file(), but also sets the $files_synced parameter.
 * @see cdn_update_file()
 *
 * @param $files
 *   The list of files to be synchronized.
 * @param $update_setting
 *   The update setting for each file. The key is the file path.
 * @return
 *   The updated $files array, as updated by cdn_update_file().
 */
function _cdn_cron_update_files_wrapper($files, $update_setting) {
  static $files_syncing = array();
  if (empty($files_syncing)) {
    $files_syncing = variable_get('cdn_cron_files_syncing', array());
  }
  $files_updated = array();
  foreach ($files as $file) {
    if ($update_setting[$file]) {
      $files_updated[] = cdn_update_file($file, $files_syncing);
    }
    else {
      $files_updated[] = $file;
    }
  }
  return $files_updated;
}

/**
 * Helper function to clean up temporary files - i.e. copies of files that
 * were being synchronized, but needed the URLs embedded to be updated.
 */
function _cdn_cron_delete_temporary_files() {
  $list = file_scan_directory(file_directory_path() . '/cdn', '.*');
  array_map(create_function('$file', 'return file_delete($file->filename);'), $list);
}

Functions

Namesort descending Description
cdn_cron_cleanup Shutdown function for CDN cron cleanup.
cdn_cron_run Executes a CDN synchronization cron run when called
cdn_log Simple logging function for sync plugin debugging purposes only.
_cdn_cron_delete_temporary_files Helper function to clean up temporary files - i.e. copies of files that were being synchronized, but needed the URLs embedded to be updated.
_cdn_cron_get_files_to_sync Generates the list of files that has to be sync, based on a file name pattern, a list of ignored directories, a list of directories of which any file will be included, and an exclude pattern.
_cdn_cron_init_stats Initializes the stats array.
_cdn_cron_scan_directory Stolen from Drupal core. Simplified for CDN cron usage. Parameter order is unchanged, so you can look at the official file_scan_directory() documentation but think away the last parameters.
_cdn_cron_update_files_wrapper Wrapper of cdn_update_file() for use only within cron mode. This function simply calls cdn_update_file(), but also sets the $files_synced parameter.