263 lines
8.1 KiB
PHP
263 lines
8.1 KiB
PHP
<?php
|
|
/**
|
|
* The Horde_Lock_Sql driver implements a storage backend for the Horde_Lock
|
|
* API.
|
|
*
|
|
* The table structure for the locks is as follows:
|
|
* <pre>
|
|
* CREATE TABLE horde_locks (
|
|
* lock_id VARCHAR(36) NOT NULL,
|
|
* lock_owner VARCHAR(32) NOT NULL,
|
|
* lock_scope VARCHAR(32) NOT NULL,
|
|
* lock_principal VARCHAR(255) NOT NULL,
|
|
* lock_origin_timestamp BIGINT NOT NULL,
|
|
* lock_update_timestamp BIGINT NOT NULL,
|
|
* lock_expiry_timestamp BIGINT NOT NULL,
|
|
* lock_type TINYINT NOT NULL,
|
|
*
|
|
* PRIMARY KEY (lock_id)
|
|
* );
|
|
* </pre>
|
|
*
|
|
* Copyright 2008-2016 Horde LLC (http://www.horde.org/)
|
|
*
|
|
* See the enclosed file COPYING for license information (LGPL). If you did
|
|
* not receive this file, see http://www.horde.org/licenses/lgpl21.
|
|
*
|
|
* @author Ben Klang <bklang@horde.org>
|
|
* @category Horde
|
|
* @package Lock
|
|
*/
|
|
class Horde_Lock_Sql extends Horde_Lock
|
|
{
|
|
/**
|
|
* Handle for the current database connection.
|
|
*
|
|
* @var Horde_Db_Adapter
|
|
*/
|
|
private $_db;
|
|
|
|
/**
|
|
* Constructor.
|
|
*
|
|
* @param array $params Parameters:
|
|
* <pre>
|
|
* 'db' - (Horde_Db_Adapter) [REQUIRED] The DB instance.
|
|
* 'table' - (string) The name of the lock table in 'database'.
|
|
* DEFAULT: 'horde_locks'
|
|
* </pre>
|
|
*
|
|
* @throws Horde_Lock_Exception
|
|
*/
|
|
public function __construct($params = array())
|
|
{
|
|
if (!isset($params['db'])) {
|
|
throw new Horde_Lock_Exception('Missing db parameter.');
|
|
}
|
|
$this->_db = $params['db'];
|
|
unset($params['db']);
|
|
|
|
$params = array_merge(array(
|
|
'table' => 'horde_locks'
|
|
), $params);
|
|
|
|
parent::__construct($params);
|
|
|
|
/* Only do garbage collection 0.1% of the time we create an object. */
|
|
if (substr(time(), -3) === '000') {
|
|
register_shutdown_function(array($this, 'doGC'));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return an array of information about the requested lock.
|
|
*
|
|
* @see Horde_Lock_Base::getLockInfo()
|
|
*/
|
|
public function getLockInfo($lockid)
|
|
{
|
|
$now = time();
|
|
$sql = 'SELECT lock_id, lock_owner, lock_scope, lock_principal, '
|
|
. 'lock_origin_timestamp, lock_update_timestamp, '
|
|
. 'lock_expiry_timestamp, lock_type FROM '
|
|
. $this->_params['table']
|
|
. ' WHERE lock_id = ? AND '
|
|
. '(lock_expiry_timestamp >= ? OR lock_expiry_timestamp = ?)';
|
|
$values = array($lockid, $now, Horde_Lock::PERMANENT);
|
|
|
|
try {
|
|
return $this->_db->selectOne($sql, $values);
|
|
} catch (Horde_Db_Exception $e) {
|
|
throw new Horde_Lock_Exception($e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return a list of valid locks with the option to limit the results
|
|
* by principal, scope and/or type.
|
|
*
|
|
* @see Horde_Lock_Base::getLocks()
|
|
*/
|
|
public function getLocks($scope = null, $principal = null, $type = null)
|
|
{
|
|
$now = time();
|
|
$sql = 'SELECT lock_id, lock_owner, lock_scope, lock_principal, '
|
|
. 'lock_origin_timestamp, lock_update_timestamp, '
|
|
. 'lock_expiry_timestamp, lock_type FROM '
|
|
. $this->_params['table']
|
|
. ' WHERE (lock_expiry_timestamp >= ? OR lock_expiry_timestamp = ?)';
|
|
$values = array($now, Horde_Lock::PERMANENT);
|
|
|
|
// Check to see if we need to filter the results
|
|
if (!empty($principal)) {
|
|
$sql .= ' AND lock_principal = ?';
|
|
$values[] = $principal;
|
|
}
|
|
if (!empty($scope)) {
|
|
$sql .= ' AND lock_scope = ?';
|
|
$values[] = $scope;
|
|
}
|
|
if (!empty($type)) {
|
|
$sql .= ' AND lock_type = ?';
|
|
$values[] = $type;
|
|
}
|
|
|
|
try {
|
|
$result = $this->_db->select($sql, $values);
|
|
} catch (Horde_Db_Exception $e) {
|
|
throw new Horde_Lock_Exception($e);
|
|
}
|
|
|
|
$locks = array();
|
|
foreach ($result as $row) {
|
|
$locks[$row['lock_id']] = $row;
|
|
}
|
|
|
|
return $locks;
|
|
}
|
|
|
|
/**
|
|
* Extend the valid lifetime of a valid lock to now + $lifetime.
|
|
*
|
|
* @see Horde_Lock_Base::resetLock()
|
|
*/
|
|
public function resetLock($lockid, $lifetime)
|
|
{
|
|
$now = time();
|
|
|
|
if (!$this->getLockInfo($lockid)) {
|
|
return false;
|
|
}
|
|
|
|
$expiration = $lifetime == Horde_Lock::PERMANENT ? Horde_Lock::PERMANENT : $now + $lifetime;
|
|
|
|
$sql = 'UPDATE ' . $this->_params['table'] . ' SET ' .
|
|
'lock_update_timestamp = ?, lock_expiry_timestamp = ? ' .
|
|
'WHERE lock_id = ? AND lock_expiry_timestamp <> ?';
|
|
$values = array($now, $expiration, $lockid, Horde_Lock::PERMANENT);
|
|
|
|
try {
|
|
$this->_db->update($sql, $values);
|
|
} catch (Horde_Db_Exception $e) {
|
|
throw new Horde_Lock_Exception($e);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Sets a lock on the requested principal and returns the generated lock
|
|
* ID.
|
|
*
|
|
* @see Horde_Lock_Base::setLock()
|
|
*/
|
|
public function setLock($requestor, $scope, $principal,
|
|
$lifetime = 1, $type = Horde_Lock::TYPE_SHARED)
|
|
{
|
|
$oldlocks = $this->getLocks(
|
|
$scope, $principal,
|
|
$type == Horde_Lock::TYPE_SHARED ? Horde_Lock::TYPE_EXCLUSIVE : null);
|
|
|
|
if (count($oldlocks) != 0) {
|
|
// A lock exists. Deny the new request.
|
|
if ($this->_logger) {
|
|
$this->_logger->log(sprintf('Lock requested for %s denied due to existing lock.', $principal), 'NOTICE');
|
|
}
|
|
return false;
|
|
}
|
|
|
|
$lockid = (string)new Horde_Support_Uuid();
|
|
|
|
$now = time();
|
|
$expiration = $lifetime == Horde_Lock::PERMANENT ? Horde_Lock::PERMANENT : $now + $lifetime;
|
|
$sql = 'INSERT INTO ' . $this->_params['table'] . ' (lock_id, lock_owner, lock_scope, lock_principal, lock_origin_timestamp, lock_update_timestamp, lock_expiry_timestamp, lock_type) VALUES (?, ?, ?, ?, ?, ?, ?, ?)';
|
|
$values = array($lockid, $requestor, $scope, $principal, $now, $now,
|
|
$expiration, $type);
|
|
|
|
try {
|
|
$this->_db->insert($sql, $values);
|
|
} catch (Horde_Db_Exception $e) {
|
|
throw new Horde_Lock_Exception($e);
|
|
}
|
|
|
|
if ($this->_logger) {
|
|
$this->_logger->log(sprintf('Lock %s set successfully by %s in scope %s on "%s"', $lockid, $requestor, $scope, $principal), 'DEBUG');
|
|
}
|
|
|
|
return $lockid;
|
|
}
|
|
|
|
/**
|
|
* Removes a lock given the lock ID.
|
|
*
|
|
* @see Horde_Lock_Base::clearLock()
|
|
*/
|
|
public function clearLock($lockid)
|
|
{
|
|
if (empty($lockid)) {
|
|
throw new Horde_Lock_Exception('Must supply a valid lock ID.');
|
|
}
|
|
|
|
// Since we're trying to clear the lock we don't care
|
|
// whether it is still valid or not. Unconditionally
|
|
// remove it.
|
|
$sql = 'DELETE FROM ' . $this->_params['table'] . ' WHERE lock_id = ?';
|
|
$values = array($lockid);
|
|
|
|
try {
|
|
$this->_db->delete($sql, $values);
|
|
} catch (Horde_Db_Exception $e) {
|
|
throw new Horde_Lock_Exception($e);
|
|
}
|
|
|
|
if ($this->_logger) {
|
|
$this->_logger->log(sprintf('Lock %s cleared successfully.', $lockid), 'DEBUG');
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Do garbage collection needed for the driver.
|
|
*
|
|
* @todo Rename to gc().
|
|
*/
|
|
public function doGC()
|
|
{
|
|
$now = time();
|
|
$query = 'DELETE FROM ' . $this->_params['table'] . ' WHERE ' .
|
|
'lock_expiry_timestamp < ? AND lock_expiry_timestamp != ?';
|
|
$values = array($now, Horde_Lock::PERMANENT);
|
|
|
|
try {
|
|
if ($this->_db) {
|
|
$result = $this->_db->delete($query, $values);
|
|
if ($this->_logger) {
|
|
$this->_logger->log(sprintf('Lock garbage collection cleared %d locks.', $result), 'DEBUG');
|
|
}
|
|
}
|
|
} catch (Horde_Db_Exception $e) {}
|
|
}
|
|
|
|
}
|