Files
oc-server3/htdocs/okapi/services/caches/search/save.php
2013-04-09 16:54:36 +02:00

182 lines
5.1 KiB
PHP

<?php
namespace okapi\services\caches\search\save;
require_once('searching.inc.php');
use okapi\Okapi;
use okapi\OkapiRequest;
use okapi\ParamMissing;
use okapi\InvalidParam;
use okapi\Db;
use okapi\OkapiLock;
use okapi\services\caches\search\SearchAssistant;
class WebService
{
public static function options()
{
return array(
'min_auth_level' => 1
);
}
/**
* Get [set_id, date_created, expires] for the given params_hash
* (or [null, null, null] if not found).
*/
private static function find_param_set($params_hash, $ref_max_age)
{
$tmp = Db::select_row("
select
id as id,
unix_timestamp(date_created) as date_created,
unix_timestamp(expires) as expires
from okapi_search_sets
where
params_hash = '".mysql_real_escape_string($params_hash)."'
and date_add(date_created, interval '".mysql_real_escape_string($ref_max_age)."' second) > now()
order by id desc
limit 1
");
if ($tmp === null)
return array(null, null, null);
return array($tmp['id'], $tmp['date_created'], $tmp['expires']);
}
public static function call(OkapiRequest $request)
{
# "Cache control" parameters.
$tmp = $request->get_parameter('min_store');
if ($tmp === null) $tmp = "300";
$min_store = intval($tmp);
if (("$min_store" !== $tmp) ||($min_store < 0) || ($min_store > 64800))
throw new InvalidParam('min_store', "Has to be in the 0..64800 range.");
$tmp = $request->get_parameter('ref_max_age');
if ($tmp === null) $tmp = "300";
if ($tmp == "nolimit") $tmp = "9999999";
$ref_max_age = intval($tmp);
if (("$ref_max_age" !== $tmp) || ($ref_max_age < 300))
throw new InvalidParam('ref_max_age', "Has to be >=300.");
# Search params.
$search_params = SearchAssistant::get_common_search_params($request);
$tables = array_merge(
array('caches'),
$search_params['extra_tables']
);
$where_conds = array_merge(
array('caches.wp_oc is not null'),
$search_params['where_conds']
);
unset($search_params);
# Generate, or retrieve an existing set, and return the result.
$result = self::get_set($tables, $where_conds, $min_store, $ref_max_age);
return Okapi::formatted_response($request, $result);
}
/**
* Important: YOU HAVE TO make sure $tables and $where_conds don't contain
* unescaped user-supplied data!
*/
public static function get_set($tables, $where_conds, $min_store, $ref_max_age)
{
# Compute the "params hash".
$params_hash = md5(serialize(array($tables, $where_conds)));
# Check if there exists an entry for this hash, which also meets the
# given freshness criteria.
list($set_id, $date_created, $expires) = self::find_param_set($params_hash, $ref_max_age);
if ($set_id === null)
{
# To avoid generating the same results by multiple threads at once
# (the "tile" method uses the "save" method, so the problem is
# quite real!), we will acquire a write-lock here.
$lock = OkapiLock::get("search-results-writer");
$lock->acquire();
try
{
# Make sure we were the first to acquire the lock.
list($set_id, $date_created, $expires) = self::find_param_set($params_hash, $ref_max_age);
if ($set_id === null)
{
# We are in the first thread which have acquired the lock.
# We will proceed with result-set creation. Other threads
# will be waiting until we finish.
Db::execute("
insert into okapi_search_sets (params_hash, date_created, expires)
values (
'processing in progress',
now(),
date_add(now(), interval '".mysql_real_escape_string($min_store + 60)."' second)
)
");
$set_id = Db::last_insert_id();
$date_created = time();
$expires = $date_created + $min_store + 60;
Db::execute("
insert into okapi_search_results (set_id, cache_id)
select distinct
'".mysql_real_escape_string($set_id)."',
caches.cache_id
from ".implode(", ", $tables)."
where (".implode(") and (", $where_conds).")
");
# Lock barrier, to make sure the data is visible by other
# sessions. See http://bugs.mysql.com/bug.php?id=36618
Db::execute("lock table okapi_search_results write");
Db::execute("unlock tables");
Db::execute("
update okapi_search_sets
set params_hash = '".mysql_real_escape_string($params_hash)."'
where id = '".mysql_real_escape_string($set_id)."'
");
} else {
# Some other thread acquired the lock before us and it has
# generated the result set. We don't need to do anything.
}
$lock->release();
}
catch (Exception $e)
{
# SQL error? Make sure the lock is released and rethrow.
$lock->release();
throw $e;
}
}
# If we got an old set, we may need to expand its lifetime in order to
# meet user's "min_store" criterium.
if (time() + $min_store > $expires)
{
Db::execute("
update okapi_search_sets
set expires = date_add(now(), interval '".mysql_real_escape_string($min_store + 60)."' second)
where id = '".mysql_real_escape_string($set_id)."'
");
}
return array(
'set_id' => "$set_id",
'generated_at' => date('c', $date_created),
'expires' => date('c', $expires),
);
}
}