Подчистил и прокомментировал. Надеюсь, будет понятно, что хотелось и что получилось. Возможно файловая составляющая избыточна.
public static function outputCSS()
{
global $modSettings, $vu_temp_dir, $scripturl, $boarddir, $sourcedir, $settings;
// Хранимые данные состоят из следующих частей: блок переменных состояния (vars),
// блок ссылок на части (head) и набор склеенных частей (part).
$keyBase = __CLASS__ . '-css-';
$fileBase = $vu_temp_dir . __CLASS__ . '-css-';
$key = array(
'vars' => $keyBase . 'vars',
'head' => $keyBase . 'head',
'part' => $keyBase . 'part_',
// Многие проверка производится только в момент устаревания кеша.
// По этому его TTL должен быть умеренной продолжительности.
'ttl' => 120,
);
$file = array(
'vars' => $fileBase . 'vars',
'head' => $fileBase . 'head',
'part' => $fileBase . 'part_',
// Используется для заголовка Expires.
'ttl' => 86400,
);
$path = array(
'lock' => $fileBase . 'lock',
'css' => $boarddir . '/Themes/default/geshi/',
'url' => $scripturl . '?action=geshi-css',
);
// Максимальный размер отдаваемой части CSS-данных.
$maxPartSize = 32767;
$data = null;
$content = array();
$cacheSupported = (!empty($modSettings) && !empty($modSettings['cache_enable']));
// Блокировка, чтобы не было конфликта при устаревании кеша.
$lock = fopen($path['lock'], 'w+');
flock($lock, LOCK_EX);
if ($cacheSupported)
{
$data = cache_get_data($key['vars']);
$source = 'cache';
}
// Если данные в кеше есть, то никаких проверок не производим.
if ($data === null)
{
// Попытка считать те же данные из файла.
$data = @file_get_contents($file['vars']);
$source = 'none';
if ($data !== false)
{
$data = @unserialize($data);
// Как минимум, данные должны быть массивом.
// Также проверяем соответствие количества исходных CSS-файлов в сохраненном списке и в GeSHi.
if (is_array($data) && count($data['cssFiles']) == count(array_unique(self::$langs)))
{
$source = 'file';
// Проверка на изменение состава исходных CSS-файлов, их mtime и размеров.
foreach ($data['cssFiles'] as $css)
{
if (file_exists($css['file']) == $css['exists'])
{
if ($css['exists'] === true)
{
$stat = stat($css['file']);
if ($stat[7] != $css['size'] || $stat[9] != $css['mtime'])
{
$source = 'none';
break;
}
}
}
else
{
$source = 'none';
break;
}
}
}
}
}
// Типовой хак для сброса файлового кеша: mtime текущего файла класса.
if ($source == 'file')
{
if (filemtime(__FILE__) > $data['lastModified'])
$source = 'none';
}
// Переходим к обработке исходных файлов.
if ($source == 'none')
{
// Принудительно перечитываем список
self::init(true);
// Удаляем все вспомогательные файлы.
foreach (glob($fileBase . '*') as $filename)
@unlink($filename);
// Создаем их заново, но пока в переменных.
$data = array(
'cssFiles' => array(),
'partFiles' => array(),
);
$langs = array_unique(self::$langs);
$partNumber = 1;
$partSize = 0;
$partContent = '';
foreach ($langs as $lang)
{
$css = array(
'lang' => $lang,
'file' => $path['css'] . $lang . '.css',
'exists' => false,
);
if (file_exists($css['file']))
{
$stat = stat($css['file']);
$css['exists'] = true;
$css['size'] = $stat[7];
$css['mtime'] = $stat[9];
if ($css['size'] + $partSize > $maxPartSize)
{
$content[$partNumber] = $partContent;
$partNumber++;
$partSize = 0;
$partContent = '';
}
$partContent .= file_get_contents($css['file']);
$partSize += $css['size'];
}
$data['cssFiles'][$lang] = $css;
}
if ($partSize > 0)
$content[$partNumber] = $partContent;
else
$partNumber--;
$data['partsCount'] = $partNumber;
$content['head'] = '';
for ($part = 1; $part <= $data['partsCount']; $part++)
{
$content['head'] .= '@import url("' . $path['url'] . ';part=' . $part . '") screen;' . "\n";
$data['partFiles'][$part] = array(
'file' => $file['part'] . $part,
'size' => strlen($content[$part]),
'md5' => md5($content[$part]),
);
}
$data['partFiles']['head'] = array(
'file' => $file['head'],
'size' => strlen($content['head']),
'md5' => md5($content['head']),
);
$data['lastModified'] = time();
// СБрос переменных и блоков данных в файлы.
for ($part = 1; $part <= $data['partsCount']; $part++)
file_put_contents($file['part'] . $part, $content[$part]);
file_put_contents($file['head'], $content['head']);
file_put_contents($file['vars'], serialize($data));
$source = 'file';
}
// Если кеш разрешен, то копируем туда данные из переменных.
// Данный код используется при создании данных из исходных файлов,
// а также при устаревании кеша.
if ($source == 'file' && $cacheSupported)
{
$valid = true;
if (!isset($content['head']))
{
$content['head'] = file_get_contents($file['head']);
$valid &= ($content['head'] !== false);
}
for ($part = 1; $part <= $data['partsCount']; $part++)
if (!isset($content[$part]))
{
$content[$part] = file_get_contents($file['part'] . $part);
$valid &= ($content[$part] !== false);
}
// Если хотя бы один блок данных не существует, то в кеш ничего не помещаем.
if ($valid)
{
for ($part = 1; $part <= $data['partsCount']; $part++)
cache_put_data($key['part'] . $part, $content[$part], $key['ttl']);
cache_put_data($key['head'], $content['head'], $key['ttl']);
cache_put_data($key['vars'], $data, $key['ttl'] - 1);
$source = 'cache';
}
else
{
// TODO: ситуация требует разбора. Вероятно кто-то удалил файл.
// Пока пусть ошибка всплывет, но делать ничего не будем.
// Тем более, что отсутсвие блока данных обрабатывается ниже.
log_error("VUHighLighter::outputCSS(): набор файлов испорчен. Не хватает одного или более.", __FILE__, __LINE__);
}
}
// Отпускаем блокировку.
// Если данные были в кеше, то время блокировки очень мало.
fclose($lock);
// Проверка единственного параметра.
$part = isset($_REQUEST['part']) ?
is_numeric($_REQUEST['part']) &&
$_REQUEST['part'] >= 1 &&
$_REQUEST['part'] <= $data['partsCount'] ?
(int)$_REQUEST['part']
: null
: 'head';
// TODO: хорошо бы проверять и общий состав параметров, но для CSS-фйалов это не имеет смысла.
// Ошибочные значения отвергаем.
if ($part === null)
{
header('HTTP/1.1 404 Not Found');
exit;
}
$partFile = $data['partFiles'][$part];
$notModified = false;
// Если запрошенный блок данных еще не подгружен.
if (!isset($content[$part]))
{
$partKey = ($part == 'head') ? $key['head'] : ($key['part'] . $part);
// Пробуем загрузить из кеша.
if ($source == 'cache')
$content[$part] = cache_get_data($partKey);
// Пробуем загрузить из файла.
if ($content[$part] === null)
$content[$part] = file_get_contents($partFile['file']);
// Данных нет. Аварийная ситуация.
if ($content[$part] === false)
{
// Удалим файлы.
foreach (glob($fileBase . '*') as $filename)
@unlink($filename);
// Затрем данные в кеше.
cache_put_data($key['vars'], null, 1);
// Попробуем дать понять клиенту, что нужен повторный запрос.
header('Connection: close');
header('Cache-Control: no-cache');
header('Expires: ' . gmdate('D, d M Y H:i:s', time() - 365 * 86400) . ' GMT');
header('Refresh: 0; url=' . $path['url']);
header('Content-Type: text/css; charset=utf-8');
echo "/* File expired. */\n";
exit;
}
}
// Если время lastModified не больше If-Modified-Since клиента, то считаем, что данные не менялись.
if (!empty($_SERVER['HTTP_IF_MODIFIED_SINCE']))
{
list($modifiedSince) = explode(';', $_SERVER['HTTP_IF_MODIFIED_SINCE']);
$notModified = ($data['lastModified'] <= strtotime($modifiedSince));
}
// Проверку MD5 считаем более авторитетной.
if (!empty($_SERVER['HTTP_IF_NONE_MATCH']))
$notModified = ($_SERVER['HTTP_IF_NONE_MATCH'] == $partFile['md5']);
// Если данные не менялись, то отвечаем 304.
if ($notModified)
{
header('HTTP/1.1 304 Not Modified');
exit;
}
// Передаем данные запрошенной части.
header('Pragma: ');
header('Cache-Control: public');
header('Expires: ' . gmdate('D, d M Y H:i:s', time() + $file['ttl']) . ' GMT');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $data['lastModified']) . ' GMT');
header('Set-Cookie:');
header('ETag: ' . $partFile['md5']);
header('Content-Type: text/css; charset=utf-8');
header('Content-Length: ' . $partFile['size']);
echo $content[$part];
exit;
}
Вспомогательные файлы:
$ ls -l VUHighLighter-css-*
-rw-r--r-- 1 apache apache 240 Янв 3 14:27 VUHighLighter-css-head
-rw-r--r-- 1 apache apache 0 Янв 3 14:44 VUHighLighter-css-lock
-rw-r--r-- 1 apache apache 32319 Янв 3 14:27 VUHighLighter-css-part_1
-rw-r--r-- 1 apache apache 31518 Янв 3 14:27 VUHighLighter-css-part_2
-rw-r--r-- 1 apache apache 25242 Янв 3 14:27 VUHighLighter-css-part_3
-rw-r--r-- 1 apache apache 13752 Янв 3 14:27 VUHighLighter-css-vars
Прошу заметить, что полный набор CSS-файлов GeSHi весит примерно 230 кБ. Мой код работает только с теми типами, которые "одобрены" другой частью кода, здесь не показанной. Список одобренных типов в переменной self::$langs и создается методом self::init().
Добавлено через 41 минуту и 48 секунд:И прошу критику. Желательно конструктивную.
|
Диаграмма загрузки. |
|
Тоже, с принудительным обновлением кеша. |