? modules/image/t.patch Index: includes/common.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/common.inc,v retrieving revision 1.1255 diff -u -p -r1.1255 common.inc --- includes/common.inc 17 Nov 2010 04:15:37 -0000 1.1255 +++ includes/common.inc 18 Nov 2010 19:37:39 -0000 @@ -3060,7 +3060,12 @@ function drupal_aggregate_css(&$css_grou // the group's data property to the file path of the aggregate file. case 'file': if ($group['preprocess'] && $preprocess_css) { - $css_groups[$key]['data'] = drupal_build_css_cache($group['items']); + $uri = drupal_build_css_cache($group['items']); + // Only include the file if was written successfully. Errors are logged + // using watchdog. + if ($uri) { + $css_groups[$key]['data'] = $uri; + } } break; // Aggregate all inline CSS content into the group's data property. @@ -3316,69 +3321,100 @@ function drupal_build_css_cache($css) { $data = ''; $uri = ''; $map = variable_get('drupal_css_cache_files', array()); - $key = hash('sha256', serialize($css)); + $version = variable_get('drupal_css_cache_version', 1); + $key = hash('sha256', $version . serialize($css)); if (isset($map[$key])) { $uri = $map[$key]; } - if (empty($uri) || !file_exists($uri)) { - // Build aggregate CSS file. - foreach ($css as $stylesheet) { - // Only 'file' stylesheets can be aggregated. - if ($stylesheet['type'] == 'file') { - $contents = drupal_load_stylesheet($stylesheet['data'], TRUE); - - // Build the base URL of this CSS file: start with the full URL. - $css_base_url = file_create_url($stylesheet['data']); - // Move to the parent. - $css_base_url = substr($css_base_url, 0, strrpos($css_base_url, '/')); - // Simplify to a relative URL if the stylesheet URL starts with the - // base URL of the website. - if (substr($css_base_url, 0, strlen($GLOBALS['base_root'])) == $GLOBALS['base_root']) { - $css_base_url = substr($css_base_url, strlen($GLOBALS['base_root'])); + $lock_acquire_attempts = 0; + $lock_name = 'css_cache:' . $key; + $mtime = $uri ? filemtime($uri) : 0; + do { + if (empty($uri) || !$mtime || (isset($map['clear']) && $mtime < $map['clear'])) { + if ($lock_acquired = lock_acquire($lock_name)) { + // Build aggregate CSS file. + foreach ($css as $stylesheet) { + // Only 'file' stylesheets can be aggregated. + if ($stylesheet['type'] == 'file') { + $contents = drupal_load_stylesheet($stylesheet['data'], TRUE); + + // Build the base URL of this CSS file: start with the full URL. + $css_base_url = file_create_url($stylesheet['data']); + // Move to the parent. + $css_base_url = substr($css_base_url, 0, strrpos($css_base_url, '/')); + // Simplify to a relative URL if the stylesheet URL starts with the + // base URL of the website. + if (substr($css_base_url, 0, strlen($GLOBALS['base_root'])) == $GLOBALS['base_root']) { + $css_base_url = substr($css_base_url, strlen($GLOBALS['base_root'])); + } + + _drupal_build_css_path(NULL, $css_base_url . '/'); + // Anchor all paths in the CSS with its base URL, ignoring external and absolute paths. + $data .= preg_replace_callback('/url\(\s*[\'"]?(?![a-z]+:|\/+)([^\'")]+)[\'"]?\s*\)/i', '_drupal_build_css_path', $contents); + } } - _drupal_build_css_path(NULL, $css_base_url . '/'); - // Anchor all paths in the CSS with its base URL, ignoring external and absolute paths. - $data .= preg_replace_callback('/url\(\s*[\'"]?(?![a-z]+:|\/+)([^\'")]+)[\'"]?\s*\)/i', '_drupal_build_css_path', $contents); + // Per the W3C specification at http://www.w3.org/TR/REC-CSS2/cascade.html#at-import, + // @import rules must proceed any other style, so we move those to the top. + $regexp = '/@import[^;]+;/i'; + preg_match_all($regexp, $data, $matches); + $data = preg_replace($regexp, '', $data); + $data = implode('', $matches[0]) . $data; + + // Prefix filename to prevent blocking by firewalls which reject files + // starting with "ad*". + $filename = 'css_' . drupal_hash_base64($data) . '.css'; + // Create the css/ within the files folder. + $csspath = 'public://css'; + $uri = $csspath . '/' . $filename; + // Create the CSS file. + file_prepare_directory($csspath, FILE_CREATE_DIRECTORY); + if (!file_unmanaged_save_data($data, $uri, FILE_EXISTS_REPLACE)) { + lock_release($lock_name); + return $mtime ? $uri : FALSE; + } + // Update the map atomically. + _drupal_update_css_js_map('css', $key, $uri); + lock_release($lock_name); } - } - - // Per the W3C specification at http://www.w3.org/TR/REC-CSS2/cascade.html#at-import, - // @import rules must proceed any other style, so we move those to the top. - $regexp = '/@import[^;]+;/i'; - preg_match_all($regexp, $data, $matches); - $data = preg_replace($regexp, '', $data); - $data = implode('', $matches[0]) . $data; - - // Prefix filename to prevent blocking by firewalls which reject files - // starting with "ad*". - $filename = 'css_' . drupal_hash_base64($data) . '.css'; - // Create the css/ within the files folder. - $csspath = 'public://css'; - $uri = $csspath . '/' . $filename; - // Create the CSS file. - file_prepare_directory($csspath, FILE_CREATE_DIRECTORY); - if (!file_exists($uri) && !file_unmanaged_save_data($data, $uri, FILE_EXISTS_REPLACE)) { - return FALSE; - } - // If CSS gzip compression is enabled, clean URLs are enabled (which means - // that rewrite rules are working) and the zlib extension is available then - // create a gzipped version of this file. This file is served conditionally - // to browsers that accept gzip using .htaccess rules. - if (variable_get('css_gzip_compression', TRUE) && variable_get('clean_url', 0) && extension_loaded('zlib')) { - if (!file_exists($uri . '.gz') && !file_unmanaged_save_data(gzencode($data, 9, FORCE_GZIP), $uri . '.gz', FILE_EXISTS_REPLACE)) { - return FALSE; + else { + if ($mtime) { + break; + } + if ($lock_acquire_attempts == 0) { + $prev_key = hash('sha256', ($version - 1) . serialize($css)); + if (isset($map[$prev_key]) && filemtime($map[$prev_key])) { + return $map[$prev_key]; + } + } + lock_wait($lock_name); } + $mtime = $uri ? filemtime($uri) : 0; } - // Save the updated map. - $map[$key] = $uri; - variable_set('drupal_css_cache_files', $map); } + while (!$mtime && $lock_acquired === FALSE && ++$lock_acquire_attempts < 5); return $uri; } /** + * Helper function to atomically set the css and js cache maps. + */ +function _drupal_update_css_js_map($type, $key, $uri) { + $lock_name = $type . '_cache'; + $var_name = 'drupal_' . $type . '_cache_files'; + $lock_acquire_attempts = 0; + while (!lock_acquire($lock_name) && ++$lock_acquire_attempts < 5) { + lock_wait($lock_name); + } + $vars = variable_initialize(array()); + $map = isset($vars[$var_name]) ? $vars[$var_name] : array(); + $map[$key] = $uri; + variable_set($var_name, $map); + lock_release($lock_name); +} + +/** * Helper function for drupal_build_css_cache(). * * This function will prefix all paths within a CSS file. @@ -4653,42 +4689,67 @@ function drupal_build_js_cache($files) { $contents = ''; $uri = ''; $map = variable_get('drupal_js_cache_files', array()); - $key = hash('sha256', serialize($files)); + $version = variable_get('drupal_js_cache_version', 1); + $key = hash('sha256', $version . serialize($files)); if (isset($map[$key])) { $uri = $map[$key]; } - if (empty($uri) || !file_exists($uri)) { - // Build aggregate JS file. - foreach ($files as $path => $info) { - if ($info['preprocess']) { - // Append a ';' and a newline after each JS file to prevent them from running together. - $contents .= file_get_contents($path) . ";\n"; + $lock_acquire_attempts = 0; + $lock_name = 'js_cache:' . $uri; + $mtime = $uri ? filemtime($uri) : 0; + do { + if (empty($uri) || !$mtime || (isset($map['clear']) && $mtime < $map['clear'])) { + if ($lock_acquired = lock_acquire($lock_name)) { + // Build aggregate JS file. + foreach ($files as $path => $info) { + if ($info['preprocess']) { + // Append a ';' and a newline after each JS file to prevent them from running together. + $contents .= file_get_contents($path) . ";\n"; + } + } + // Prefix filename to prevent blocking by firewalls which reject files + // starting with "ad*". + $filename = 'js_' . drupal_hash_base64($contents) . '.js'; + // Create the js/ within the files folder. + $jspath = 'public://js'; + $uri = $jspath . '/' . $filename; + // Create the JS file. + file_prepare_directory($jspath, FILE_CREATE_DIRECTORY); + if (!file_unmanaged_save_data($contents, $uri, FILE_EXISTS_REPLACE)) { + lock_release($lock_name); + return $mtime ? $uri : FALSE; + } + // If JS gzip compression is enabled, clean URLs are enabled (which means + // that rewrite rules are working) and the zlib extension is available then + // create a gzipped version of this file. This file is served conditionally + // to browsers that accept gzip using .htaccess rules. + if (variable_get('js_gzip_compression', TRUE) && variable_get('clean_url', 0) && extension_loaded('zlib')) { + if (!file_exists($uri . '.gz') && !file_unmanaged_save_data(gzencode($contents, 9, FORCE_GZIP), $uri . '.gz', FILE_EXISTS_REPLACE)) { + lock_release($lock_name); + return $mtime ? $uri : FALSE; + } + } + // Update the map atomically. + _drupal_update_css_js_map('js', $key, $uri); + lock_release($lock_name); } - } - // Prefix filename to prevent blocking by firewalls which reject files - // starting with "ad*". - $filename = 'js_' . drupal_hash_base64($contents) . '.js'; - // Create the js/ within the files folder. - $jspath = 'public://js'; - $uri = $jspath . '/' . $filename; - // Create the JS file. - file_prepare_directory($jspath, FILE_CREATE_DIRECTORY); - if (!file_exists($uri) && !file_unmanaged_save_data($contents, $uri, FILE_EXISTS_REPLACE)) { - return FALSE; - } - // If JS gzip compression is enabled, clean URLs are enabled (which means - // that rewrite rules are working) and the zlib extension is available then - // create a gzipped version of this file. This file is served conditionally - // to browsers that accept gzip using .htaccess rules. - if (variable_get('js_gzip_compression', TRUE) && variable_get('clean_url', 0) && extension_loaded('zlib')) { - if (!file_exists($uri . '.gz') && !file_unmanaged_save_data(gzencode($contents, 9, FORCE_GZIP), $uri . '.gz', FILE_EXISTS_REPLACE)) { - return FALSE; + else { + if ($mtime) { + break; + } + if ($lock_acquire_attempts == 0) { + $prev_key = hash('sha256', ($version - 1) . serialize($css)); + if (isset($map[$prev_key]) && filemtime($map[$prev_key])) { + return $map[$prev_key]; + } + } + lock_wait($lock_name); } + $mtime = $uri ? filemtime($uri) : 0; } - $map[$key] = $uri; - variable_set('drupal_js_cache_files', $map); } + while (!$mtime && $lock_acquired === FALSE && ++$lock_acquire_attempts < 5); return $uri; } Index: modules/system/system.module =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.module,v retrieving revision 1.988 diff -u -p -r1.988 system.module --- modules/system/system.module 12 Nov 2010 01:36:08 -0000 1.988 +++ modules/system/system.module 18 Nov 2010 19:37:40 -0000 @@ -2949,6 +2949,8 @@ function system_cron() { foreach ($cache_tables as $table) { cache_clear_all(NULL, $table); } + system_cleanup_cache_map('css'); + system_cleanup_cache_map('js'); // Cleanup the batch table and the queue for failed batches. db_delete('batch') @@ -2980,6 +2982,34 @@ function system_flush_caches() { } /** + * Helper function to remove stale css and js from the cache maps. + * + * @param $type + * 'css' or 'js' + */ +function system_cleanup_cache_map($type) { + $var = 'drupal_' . $type . '_cache_files'; + $map = variable_get($var, array()); + if (!empty($map['clear'])) { + $clear = $map['clear']; + foreach ($map as $key => $uri) { + if ($key == 'clear') { + continue; + } + $mtime = filemtime($uri); + if ($mtime && $mtime < $clear && $mtime < REQUEST_TIME - variable_get('drupal_stale_map_threshold', 3600)) { + unset($map[$key]); + $changed = TRUE; + } + } + if (!isset($changed)) { + unset($map['clear']); + } + variable_set($var, $map); + } +} + +/** * Implements hook_action_info(). */ function system_action_info() {