Вот собственно класс:
PHP Code:
// #############################################################################
// datastore using FILES instead of database for storage
/**
* Class for fetching and initializing the vBulletin datastore from files
*
* @package vBulletin
* @version $Revision: 26074 $
* @date $Date: 2008-03-13 10:44:45 -0500 (Thu, 13 Mar 2008) $
*/
class vB_Datastore_Filecache extends vB_Datastore
{
/**
* Default items that are always loaded by fetch() when using the file method;
*
* @var array
*/
var $cacheableitems = array(
'options',
'bitfields',
'forumcache',
'usergroupcache',
'stylecache',
'languagecache',
'products',
'pluginlist',
);
/**
* Constructor - establishes the database object to use for datastore queries
*
* @param vB_Registry The registry object
* @param vB_Database The database object
*/
function vB_Datastore_Filecache(&$registry, &$dbobject)
{
parent::vB_Datastore($registry, $dbobject);
if (defined('SKIP_DEFAULTDATASTORE'))
{
$this->cacheableitems = array('options', 'bitfields');
}
}
/**
* Fetches the contents of the datastore from cache files
*
* @param array Array of items to fetch from the datastore
*
* @return void
*/
function fetch($itemarray)
{
$include_return = @include_once(DATASTORE . '/datastore_cache.php');
if ($include_return === false)
{
if (VB_AREA == 'AdminCP')
{
trigger_error('Datastore cache file does not exist. Please reupload includes/datastore/datastore_cache.php from the original download.', E_USER_ERROR);
}
else
{
parent::fetch($itemarray);
return;
}
}
$itemlist = array();
foreach ($this->cacheableitems AS $item)
{
if ($$item === '' OR !isset($$item))
{
if (VB_AREA == 'AdminCP')
{
$$item = $this->fetch_build($item);
}
else
{
$itemlist[] = "'" . $this->dbobject->escape_string($item) . "'";
continue;
}
}
if ($this->register($item, $$item) === false)
{
trigger_error('Unable to register some datastore items', E_USER_ERROR);
}
unset($$item);
}
foreach ($this->defaultitems AS $item)
{
if (!in_array($item, $this->cacheableitems))
{
$itemlist[] = "'" . $this->dbobject->escape_string($item) . "'";
}
}
if (is_array($itemarray))
{
foreach ($itemarray AS $item)
{
$itemlist[] = "'" . $this->dbobject->escape_string($item) . "'";
}
}
if (!empty($itemlist))
{
$this->do_db_fetch(implode(',', $itemlist));
}
$this->check_options();
// set the version number variable
$this->registry->versionnumber =& $this->registry->options['templateversion'];
}
/**
* Updates the appropriate cache file
*
* @param string title of the datastore item
* @param mixed The data associated with the title
*
* @return void
*/
function build($title, $data)
{
if (!in_array($title, $this->cacheableitems))
{
return;
}
if (!file_exists(DATASTORE . '/datastore_cache.php'))
{
// file doesn't exist so don't try to write to it
return;
}
$data_code = var_export(unserialize(trim($data)), true);
if ($this->lock())
{
$cache = file_get_contents(DATASTORE . '/datastore_cache.php');
// this is equivalent to the old preg_match system, but doesn't have problems with big files (#23186)
$open_match = strpos($cache, "### start $title ###");
if ($open_match) // we don't want to match the first character either!
{
// matched and not at the beginning
$preceding = $cache[$open_match - 1];
if ($preceding != "\n" AND $preceding != "\r")
{
$open_match = false;
}
}
if ($open_match)
{
$close_match = strpos($cache, "### end $title ###", $open_match);
if ($close_match) // we don't want to match the first character either!
{
// matched and not at the beginning
$preceding = $cache[$close_match - 1];
if ($preceding != "\n" AND $preceding != "\r")
{
$close_match = false;
}
}
}
// if we matched the beginning and end, then update the cache
if (!empty($open_match) AND !empty($close_match))
{
$replace_start = $open_match - 1; // include the \n
$replace_end = $close_match + strlen("### end $title ###");
$cache = substr_replace($cache, "\n### start $title ###\n$$title = $data_code;\n### end $title ###", $replace_start, $replace_end - $replace_start);
}
// try an atomic operation first, if that fails go for the old method
$atomic = false;
if (($fp = @fopen(DATASTORE . '/datastore_cache_atomic.php', 'w')))
{
fwrite($fp, $cache);
fclose($fp);
$atomic = $this->atomic_move(DATASTORE . '/datastore_cache_atomic.php', DATASTORE . '/datastore_cache.php');
}
if (!$atomic AND ($fp = @fopen(DATASTORE . '/datastore_cache.php', 'w')))
{
fwrite($fp, $cache);
fclose($fp);
}
$this->unlock();
/*insert query*/
$this->dbobject->query_write("
REPLACE INTO " . TABLE_PREFIX . "adminutil
(title, text)
VALUES
('datastore', '" . $this->dbobject->escape_string($cache) . "')
");
}
else
{
trigger_error('Could not obtain file lock', E_USER_ERROR);
}
}
/**
* Obtains a lock for the datastore. Attempt to get the lock multiple times before failing.
*
* @param string title of the datastore item
*
* @return boolean
*/
function lock($title = '')
{
$lock_attempts = 5;
while ($lock_attempts >= 1)
{
$result = $this->dbobject->query_write("
UPDATE " . TABLE_PREFIX . "adminutil SET
text = UNIX_TIMESTAMP()
WHERE title = 'datastorelock' AND text < UNIX_TIMESTAMP() - 15
");
if ($this->dbobject->affected_rows() > 0)
{
return true;
}
else
{
$lock_attempts--;
sleep(1);
}
}
return false;
}
/**
* Releases the datastore lock
*
* @param string title of the datastore item
*
* @return void
*/
function unlock($title = '')
{
$this->dbobject->query_write("UPDATE " . TABLE_PREFIX . "adminutil SET text = 0 WHERE title = 'datastorelock'");
}
/**
* Fetches the specified datastore item from the database and tries
* to update the file cache with it. Data is automatically unserialized.
*
* @param string Datastore item to fetch
*
* @return mixed Data from datastore (unserialized if fetched)
*/
function fetch_build($title)
{
$data = '';
$dataitem = $this->dbobject->query_first("
SELECT title, data
FROM " . TABLE_PREFIX . "datastore
WHERE title = '" . $this->dbobject->escape_string($title) ."'
");
if (!empty($dataitem['title']))
{
$this->build($dataitem['title'], $dataitem['data']);
$data = unserialize($dataitem['data']);
}
return $data;
}
/**
* Perform an atomic move where a request may occur before a file is written
*
* @param string Source Filename
* @param string Destination Filename
*
* @return boolean
*/
function atomic_move($sourcefile, $destfile)
{
if (!@rename($sourcefile, $destfile))
{
if (copy($sourcefile, $destfile))
{
unlink($sourcefile);
return true;
}
return false;
}
return true;
}
}
Смутило меня следующее:
var $cacheableitems = array(
'options',
'bitfields',
'forumcache',
'usergroupcache',
'stylecache',
'languagecache',
'products',
'pluginlist',
);
//.......................
function build($title, $data)
{
if (!in_array($title, $this->cacheableitems))
{
return;
}
выходит что только определенные в $cacheableitems штуковины будут писаться в файловый кэш, а остальные (bbcodecahe, smiliecache, еще куча стандартых и какиенибудь которые я в будущем захочу) по прежнему в бд. И считываються соответственно из нее, если в фаловом кэше не найдены.
убрал этот дурацкий массив и проверку на наличие в нем кешируемой шняги.
Вопрос 1. В чем тут подвох?
m0rbid добавил 14.07.2010 в 09:04
Потом..
$data_code = var_export(unserialize(trim($data)), true);
if ($this->lock())
{
$cache = file_get_contents(DATASTORE . '/datastore_cache.php');
// this is equivalent to the old preg_match system, but doesn't have problems with big files (#23186)
$open_match = strpos($cache, "### start $title ###");
if ($open_match) // we don't want to match the first character either!
{
// matched and not at the beginning
$preceding = $cache[$open_match - 1];
if ($preceding != "\n" AND $preceding != "\r")
{
$open_match = false;
}
}
if ($open_match)
{
$close_match = strpos($cache, "### end $title ###", $open_match);
if ($close_match) // we don't want to match the first character either!
{
// matched and not at the beginning
$preceding = $cache[$close_match - 1];
if ($preceding != "\n" AND $preceding != "\r")
{
$close_match = false;
}
}
}
// if we matched the beginning and end, then update the cache
if (!empty($open_match) AND !empty($close_match))
{
$replace_start = $open_match - 1; // include the \n
$replace_end = $close_match + strlen("### end $title ###");
$cache = substr_replace($cache, "\n### start $title ###\n$$title = $data_code;\n### end $title ###", $replace_start, $replace_end - $replace_start);
}
// try an atomic operation first, if that fails go for the old method
$atomic = false;
if (($fp = @fopen(DATASTORE . '/datastore_cache_atomic.php', 'w')))
{
fwrite($fp, $cache);
fclose($fp);
$atomic = $this->atomic_move(DATASTORE . '/datastore_cache_atomic.php', DATASTORE . '/datastore_cache.php');
}
if (!$atomic AND ($fp = @fopen(DATASTORE . '/datastore_cache.php', 'w')))
{
fwrite($fp, $cache);
fclose($fp);
}
$this->unlock();
/*insert query*/
$this->dbobject->query_write("
REPLACE INTO " . TABLE_PREFIX . "adminutil
(title, text)
VALUES
('datastore', '" . $this->dbobject->escape_string($cache) . "')
");
}
else
{
trigger_error('Could not obtain file lock', E_USER_ERROR);
}
Зачем такие сложности????
Опять же, все что я захочу кешировать надо будет помимо того что в $this->cacheableitems прописывать, так еще и для каждой кешируемой шняги в файле datastore_cache.php выделять блоки вида:
### start $шняга ###
### end $шняга ###
Снес к чертям этот файл вообще и дурацкую проверку на эти блоки.
Кеширую какждую шнягу в свой файл, типа шняга.dat с сериализованным массивом,
вопервых выигрыш в производительности засчет того что unserialize(trim(file_get_contents())) работает быстрее чем include, тк инклуду нужно проверять на глобальность все что есть в "инклужимом" файле, во вторых памяти сэкономел, так у меня считываться будет не весь этот огроменный кэш каждый раз, а только нужные шняги в нужном месте.
Вопрос 2. Где я тут промахнулся?
m0rbid добавил 14.07.2010 в 09:16
и еще, кто может обьяснить lock, unlock и atomic_move? Я то понимаю что оно хочет файл залочить, но зачем так изощренно, 15 секунд какието.. логики не пойму