--- includes/bootstrap.inc.orig 2006-02-05 15:58:25.000000000 -0500 +++ includes/bootstrap.inc 2006-02-05 17:18:12.000000000 -0500 @@ -16,6 +16,9 @@ define('WATCHDOG_WARNING', 1); define('WATCHDOG_ERROR', 2); +define('ALL', 0); +define('NONE', 1); + define('DRUPAL_BOOTSTRAP_DATABASE', 0); define('DRUPAL_BOOTSTRAP_SESSION', 1); define('DRUPAL_BOOTSTRAP_PAGE_CACHE', 2); @@ -187,7 +190,7 @@ * file. */ function variable_init($conf = array()) { - // NOTE: caching the variables improves performance with 20% when serving cached pages. + // NOTE: caching variables improves performance 20% when serving cached pages. if ($cached = cache_get('variables')) { $variables = unserialize($cached->data); } @@ -266,22 +269,29 @@ * The cache ID of the data to retrieve. */ function cache_get($key) { - global $user; + global $user, $file_cache, $cache_lifetime; - // Garbage collection necessary when enforcing a minimum cache lifetime - $cache_flush = variable_get('cache_flush', 0); - if ($cache_flush && ($cache_flush + variable_get('cache_lifetime', 0) <= time())) { - // Time to flush old cache data - db_query("DELETE FROM {cache} WHERE expire != %d AND expire <= %d", CACHE_PERMANENT, $cache_flush); - variable_set('cache_flush', 0); + if ($file_cache && is_dir($file_cache)) { + $cache = cache_get_file($key); + } + else { + // garbage collection necessary when enforcing a minimum cache lifetime + $cache_flush = variable_get('cache_flush', 0); + if ($cache_flush && ($cache_flush + variable_get('cache_lifetime', 0) <= time())) { + // Time to flush old cache data + db_query("DELETE FROM {cache} WHERE expire != %d AND expire <= %d", CACHE_PERMANENT, $cache_flush); + variable_set('cache_flush', 0); + } + $cache = db_fetch_object(db_query("SELECT data, created, headers, expire FROM {cache} WHERE cid = '%s'", $key)); } - $cache = db_fetch_object(db_query("SELECT data, created, headers, expire FROM {cache} WHERE cid = '%s'", $key)); if (isset($cache->data)) { // If the data is permanent or we're not enforcing a minimum cache lifetime // always return the cached data. - if ($cache->expire == CACHE_PERMANENT || !variable_get('cache_lifetime', 0)) { - $cache->data = db_decode_blob($cache->data); + if ($cache->expire == CACHE_PERMANENT || !$cache_lifetime) { + if (!$file_cache || !is_dir($file_cache)) { + $cache->data = db_decode_blob($cache->data); + } } // If enforcing a minimum cache lifetime, validate that the data is // currenly valid for this user before we return it by making sure the @@ -293,7 +303,7 @@ // This cache data is too old and thus not valid for us, ignore it. return 0; } - else { + elseif (!$file_cache || !is_dir($file_cache)) { $cache->data = db_decode_blob($cache->data); } } @@ -302,6 +312,29 @@ return 0; } +function cache_get_file($key) { + global $cache_lifetime; + + $cache = NULL; + $cache_flush = variable_get('cache_flush', 0); + $cache_file = cache_filename($key); + + if (file_exists($cache_file)) { + if (!$cache_flush || filemtime($cache_file) > ($cache_flush - $cache_lifetime)) { + if ($fp = fopen($cache_file, 'r')) { + if (flock($fp, LOCK_SH)) { + $data = fread($fp, filesize($cache_file)); + flock($fp, LOCK_UN); + $cache = unserialize($data); + } + fclose($fp); + } + } + } + + return $cache; +} + /** * Store data in the persistent cache. * @@ -321,12 +354,37 @@ * A string containing HTTP header information for cached pages. */ function cache_set($cid, $data, $expire = CACHE_PERMANENT, $headers = NULL) { - db_lock_table('cache'); - db_query("UPDATE {cache} SET data = %b, created = %d, expire = %d, headers = '%s' WHERE cid = '%s'", $data, time(), $expire, $headers, $cid); - if (!db_affected_rows()) { - @db_query("INSERT INTO {cache} (cid, data, created, expire, headers) VALUES ('%s', %b, %d, %d, '%s')", $cid, $data, time(), $expire, $headers); + global $file_cache; + + if (is_dir($file_cache)) { + // prepare the cache before grabbing the file lock + $cache->cid = $cid; + $cache->data = $data; + $cache->created = time(); + $cache->expire = $expire; + $cache->headers = $headers; + $data = serialize($cache); + + if ($fp = fopen(cache_filename($cid), 'w')) { + // only write to the cache file if we can obtain an exclusive lock + if (flock($fp, LOCK_EX)) { + fwrite($fp, $data); + flock($fp, LOCK_UN); + } + fclose($fp); + } + else { + drupal_set_message(t('Cache error, failed to open file "%file"', array('%file' => cache_filename($cid))), 'error'); + } + } + else { + db_lock_table('cache'); + db_query("UPDATE {cache} SET data = %b, created = %d, expire = %d, headers = '%s' WHERE cid = '%s'", $data, time(), $expire, $headers, $cid); + if (!db_affected_rows()) { + @db_query("INSERT INTO {cache} (cid, data, created, expire, headers) VALUES ('%s', %b, %d, %d, '%s')", $cid, $data, time(), $expire, $headers); + } + db_unlock_tables(); } - db_unlock_tables(); } /** @@ -341,10 +399,10 @@ * complete ID. */ function cache_clear_all($cid = NULL, $wildcard = false) { - global $user; + global $user, $cache_lifetime, $file_cache; if (empty($cid)) { - if (variable_get('cache_lifetime', 0)) { + if ($cache_lifetime) { // We store the time in the current user's $user->cache variable which // will be saved into the sessions table by sess_write(). We then // simulate that the cache was flushed for this user by not returning @@ -352,15 +410,31 @@ $user->cache = time(); $cache_flush = variable_get('cache_flush', 0); - if ($cache_flush == 0) { - // This is the first request to clear the cache, start a timer. - variable_set('cache_flush', time()); - } - else if (time() > ($cache_flush + variable_get('cache_lifetime', 0))) { - // Clear the cache for everyone, cache_flush_delay seconds have - // passed since the first request to clear the cache. - db_query("DELETE FROM {cache} WHERE expire != %d AND expire < %d", CACHE_PERMANENT, time()); - variable_set('cache_flush', 0); + $cache_files_expired = variable_get('cache_files_expired', 0); + + if (is_dir($file_cache)) { + if ($cache_files_expired == 0) { + // updating cache_flush invalidates all data cached before this time + variable_set('cache_flush', time()); + // updating cache_files_expired so cron can delete expired files + variable_set('cache_files_expired', time()); + } + + if (time() > ($cache_flush + $cache_lifetime)) { + variable_set('cache_flush', time()); + } + } + else { + if ($cache_flush == 0) { + // This is the first request to clear the cache, start a timer. + variable_set('cache_flush', time()); + } + else if (time() > ($cache_flush + $cache_lifetime)) { + // Clear the cache for everyone, cache_flush_delay seconds have + // passed since the first request to clear the cache. + db_query("DELETE FROM {cache} WHERE expire != %d AND expire < %d", CACHE_PERMANENT, time()); + variable_set('cache_flush', 0); + } } } else { @@ -368,6 +442,17 @@ db_query("DELETE FROM {cache} WHERE expire != %d AND expire < %d", CACHE_PERMANENT, time()); } } + elseif (is_dir($file_cache)) { + // TODO: what about wildcards? Won't work with md5's... + $file = cache_filename($cid); + if ($fp = fopen($file, 'w')) { + // only delete the cache file if we obtain an exclusive lock to prevent + // deleteing a cache file that is currently being read. + if (flock($fp, LOCK_EX)) { + unlink($file); + } + } + } else { if ($wildcard) { db_query("DELETE FROM {cache} WHERE cid LIKE '%%%s%%'", $cid); @@ -379,6 +464,31 @@ } /** + * Sanitize the cache ID for use as a filename when caching to the filesystem. + * + * @param $cid + * The unique ID of the cache data to store. + */ +function cache_filename($cid) { + global $file_cache; + return $file_cache .'/'. md5($cid); +} + +function drupal_file_cache() { + global $file_cache, $base_url; + + if ($file_cache && is_dir($file_cache)) { + $cache = cache_get($base_url . request_uri()); + if (!empty($cache)) { + // display cached page and exit + drupal_page_header($cache); + } + } + + return; +} + +/** * Retrieve the current page from the cache. * * Note, we do not serve cached pages when status messages are waiting (from @@ -447,14 +557,59 @@ } /** + * Determine if the specified hook should be displayed to the current class of + * users. + */ +function drupal_should_invoke($hook) { + global $user, $_init, $_exit; + + if ($hook == 'init') { + $roles = $_init; + } + else { + $roles = $_exit; + } + + switch ($roles) { + case ALL: + $display = TRUE; + break; + + case NONE: + $display = FALSE; + break; + + default: + $roles = explode(',', $roles); + foreach ($roles as $rid) { + if ($user->roles[$rid]) { + $display = TRUE; + break; + } + } + $display = FALSE; + break; + + } + + return $display; +} + +/** * Set HTTP headers in preparation for a page response. * * @see page_set_cache */ -function drupal_page_header() { - if (variable_get('cache', 0)) { - if ($cache = page_get_cache()) { - bootstrap_invoke_all('init'); +function drupal_page_header($cache = NULL) { + global $file_cache; + if ($cache || $file_cache || variable_get('cache', 0)) { + // $cache will only be passed in if the file_cache is enabled and + // there was a failure to connect to the database. + $db_failed = $cache ? TRUE : FALSE; + if ($cache || $cache = page_get_cache()) { + if (!$db_failed && drupal_should_invoke('init')) { + bootstrap_invoke_all('init'); + } // Set default values: $date = gmdate('D, d M Y H:i:s', $cache->created) .' GMT'; $etag = '"'. md5($date) .'"'; @@ -497,7 +652,9 @@ } print $cache->data; - bootstrap_invoke_all('exit'); + if (!$db_failed && drupal_should_invoke('exit')) { + bootstrap_invoke_all('exit'); + } exit(); } else { @@ -674,7 +831,7 @@ } function _drupal_bootstrap($phase) { - global $conf; + global $conf, $file_cache, $cache_lifetime, $_init, $_exit; switch ($phase) { case DRUPAL_BOOTSTRAP_DATABASE: --- includes/database.mysql.inc.orig 2006-02-05 15:58:45.000000000 -0500 +++ includes/database.mysql.inc 2006-02-05 15:29:52.000000000 -0500 @@ -51,6 +51,7 @@ // (matched) rows, not the number of affected rows. $connection = @mysql_connect($url['host'], $url['user'], $url['pass'], TRUE, 2); if (!$connection) { + drupal_file_cache(); drupal_maintenance_theme(); drupal_set_title('Unable to connect to database server'); print theme('maintenance_page', '
This either means that the username and password information in your settings.php
file is incorrect or we can\'t contact the MySQL database server. This could mean your hosting provider\'s database server is down.
We were able to connect to the MySQL database server (which means your username and password are okay) but not able to select the database.
--- includes/database.pgsql.inc.orig 2006-02-05 15:58:55.000000000 -0500 +++ includes/database.pgsql.inc 2006-02-05 16:10:32.000000000 -0500 @@ -49,6 +49,7 @@ $connection = @pg_connect($conn_string); if (!$connection) { + drupal_file_cache(); drupal_maintenance_theme(); drupal_set_title('Unable to connect to database'); print theme('maintenance_page', 'This either means that the database information in your settings.php
file is incorrect or we can\'t contact the PostgreSQL database server. This could mean your hosting provider\'s database server is down.