<?php

/**
 * Rediska specified connection
 *
 * @author Ivan Shumkov
 * @package Rediska
 * @subpackage Connection
 * @version @package_version@
 * @link http://rediska.geometria-lab.net
 * @license http://www.opensource.org/licenses/bsd-license.php
 */
class Rediska_Connection_Specified
{
    /**
     * Rediska instance
     *
     * @var Rediska
     */
    protected $_rediska;

    /**
     * Specified connection
     *
     * @var Rediska_Connection
     */
    protected $_connection;

    public function __construct(Rediska $rediska)
    {
        $this->_rediska = $rediska;
    }

    /**
     * Magic methdo for execute command
     *
     * @param $name
     * @param $args
     * @return mixed
     */
    public function __call($name, $args)
    {
        if (in_array(strtolower($name), array('pipeline', 'transaction', 'subscribe', 'monitor', 'config'))) {
            return call_user_func_array(array($this->_rediska, $name), $args);
        } else {
            return $this->_executeCommand($name, $args);
        }
    }

    /**
     * Set connection
     *
     * @param $connection
     * @return Rediska_Connection_Specified
     */
    public function setConnection(Rediska_Connection $connection)
    {
        $this->_connection = $connection;

        return $this;
    }

    /**
     * Get connection
     *
     * @return Rediska_Connection
     */
    public function getConnection()
    {
        return $this->_connection;
    }

    /**
     * Reset connection
     *
     * @return Rediska_Connection
     */
    public function resetConnection()
    {
        $this->_connection = null;

        return $this;
    }

    /**
     * Execute command
     *
     * @param string $name
     * @param array $args
     * @return mixed
     */
    protected function _executeCommand($name, $args = array())
    {
        $command = Rediska_Commands::get($this->_rediska, $name, $args);

        $response = $command->execute();

        unset($command);

        return $response;
    }

    /**
     * Generated command methods by 'scripts/add_command_methods.php'
     */

    /**
     * Ask the server to silently close the connection.
     *
     * @return mixed
     */
    public function quit() { $args = func_get_args(); return $this->_executeCommand('quit', $args); }

    /**
     * Test if a key exists
     *
     * @param string $key Key name
     * @return boolean
     */
    public function exists($key) { $args = func_get_args(); return $this->_executeCommand('exists', $args); }

    /**
     * Delete a key or keys
     *
     * @param string|array $keyOrKeys Key name or array of key names
     * @return mixed
     */
    public function delete($keyOrKeys) { $args = func_get_args(); return $this->_executeCommand('delete', $args); }

    /**
     * Get key type
     *
     * @param string $key Key name
     * @return mixed
     */
    public function getType($key) { $args = func_get_args(); return $this->_executeCommand('getType', $args); }

    /**
     * Returns all the keys matching the glob-style pattern
     * Glob style patterns examples:
     *   h?llo will match hello hallo hhllo
     *   h*llo will match hllo heeeello
     *   h[ae]llo will match hello and hallo, but not hillo
     *
     * @param string $pattern Pattern
     * @return mixed
     */
    public function getKeysByPattern($pattern) { $args = func_get_args(); return $this->_executeCommand('getKeysByPattern', $args); }

    /**
     * Return a random key from the key space
     *
     * @return string|null
     */
    public function getRandomKey() { $args = func_get_args(); return $this->_executeCommand('getRandomKey', $args); }

    /**
     * Rename the old key in the new one
     *
     * @param string            $oldKey    Old key name
     * @param string            $newKey    New key name
     * @param boolean[optional] $overwrite Overwrite the new name key if it already exists. For default is false.
     * @return mixed
     */
    public function rename($oldKey, $newKey, $overwrite = true) { $args = func_get_args(); return $this->_executeCommand('rename', $args); }

    /**
     * Get the number of keys
     *
     * @return mixed
     */
    public function getKeysCount() { $args = func_get_args(); return $this->_executeCommand('getKeysCount', $args); }

    /**
     * Set a time to live in seconds or timestamp on a key
     *
     * @param string  $key                   Key name
     * @param integer $secondsOrTimestamp    Time in seconds or timestamp
     * @param boolean $isTimestamp[optional] Time is timestamp. For default is false.
     * @return boolean
     */
    public function expire($key, $secondsOrTimestamp, $isTimestamp = false) { $args = func_get_args(); return $this->_executeCommand('expire', $args); }

    /**
     * Get key lifetime
     *
     * @param string $key Key name
     * @return string|null
     */
    public function getLifetime($key) { $args = func_get_args(); return $this->_executeCommand('getLifetime', $args); }

    /**
     * Select the DB having the specified index
     *
     * @param integer $index Db index
     * @return mixed
     */
    public function selectDb($index) { $args = func_get_args(); return $this->_executeCommand('selectDb', $args); }

    /**
     * Move the key from the currently selected DB to the DB having as index dbindex
     *
     * @param string  $key     Key name
     * @param integer $dbIndex Redis DB index
     * @return boolean
     */
    public function moveToDb($key, $dbIndex) { $args = func_get_args(); return $this->_executeCommand('moveToDb', $args); }

    /**
     * Remove all the keys of the currently selected DB
     *
     * @param boolean[optional] $all Remove from all Db. For default is false.
     * @return boolean
     */
    public function flushDb($all = false) { $args = func_get_args(); return $this->_executeCommand('flushDb', $args); }

    /**
     * Set value to a key or muliple values to multiple keys
     *
     * @param string|array $keyOrData                  Key name or array with key => value.
     * @param mixed        $valueOrOverwrite[optional] Value or overwrite property for array of values. For default true.
     * @param boolean      $overwrite[optional]        Overwrite for single value (if false don't set and return false if key already exist). For default true.
     * @return mixed
     */
    public function set($keyOrData, $valueOrOverwrite = null, $overwrite = true) { $args = func_get_args(); return $this->_executeCommand('set', $args); }

    /**
     * Set + Expire atomic command
     *
     * @param string  $key      Key name
     * @param mixed   $value    Value
     * @param integer $seconds  Expire time in seconds
     * @return mixed
     */
    public function setAndExpire($key, $value, $seconds) { $args = func_get_args(); return $this->_executeCommand('setAndExpire', $args); }

    /**
     * Atomic set value and return old
     *
     * @param string $key   Key name
     * @param mixed  $value Value
     * @return mixed
     */
    public function setAndGet($key, $value) { $args = func_get_args(); return $this->_executeCommand('setAndGet', $args); }

    /**
     * Get value of key or array of values by array of keys
     *
     * @param string|array $keyOrKeys Key name or array of names
     * @return mixed
     */
    public function get($keyOrKeys) { $args = func_get_args(); return $this->_executeCommand('get', $args); }

    /**
     * Append value to a end of string key
     *
     * @param string $key    Key name
     * @param mixed  $value  Value
     * @return mixed
     */
    public function append($key, $value) { $args = func_get_args(); return $this->_executeCommand('append', $args); }

    /**
     * Increment the number value of key by integer
     *
     * @param string            $key    Key name
     * @param integer[optional] $amount Amount to increment. One for default
     * @return mixed
     */
    public function increment($key, $amount = 1) { $args = func_get_args(); return $this->_executeCommand('increment', $args); }

    /**
     * Decrement the number value of key by integer
     *
     * @param string            $key    Key name
     * @param integer[optional] $amount Amount to decrement. One for default
     * @return mixed
     */
    public function decrement($key, $amount = 1) { $args = func_get_args(); return $this->_executeCommand('decrement', $args); }

    /**
     * Overwrite part of a string at key starting at the specified offset
     *
     * @param string  $key    Key name
     * @param integer $offset Offset
     * @param integer $value  Value
     * @return mixed
     */
    public function setRange($key, $offset, $value) { $args = func_get_args(); return $this->_executeCommand('setRange', $args); }

    /**
     * Return a subset of the string from offset start to offset end (both offsets are inclusive)
     *
     * @param string            $key   Key name
     * @param integer           $start Start
     * @param integer[optional] $end   End. If end is omitted, the substring starting from $start until the end of the string will be returned. For default end of string
     * @return mixin
     */
    public function getRange($key, $start, $end = -1) { $args = func_get_args(); return $this->_executeCommand('getRange', $args); }

    /**
     * Returns the bit value at offset in the string value stored at key
     *
     * @param string  $key    Key name
     * @param integer $offset Offset
     * @param integer $bit    Bit (0 or 1)
     * @return mixed
     */
    public function setBit($key, $offset, $bit) { $args = func_get_args(); return $this->_executeCommand('setBit', $args); }

    /**
     * Returns the bit value at offset in the string value stored at key
     *
     * @param string  $key    Key name
     * @param integer $offset Offset
     * @return mixed
     */
    public function getBit($key, $offset) { $args = func_get_args(); return $this->_executeCommand('getBit', $args); }

    /**
     * Returns the length of the string value stored at key
     *
     * @param string  $key Key name
     * @return mixed
     */
    public function getLength($key) { $args = func_get_args(); return $this->_executeCommand('getLength', $args); }

    /**
     * Append value to the end of List
     *
     * @param string            $key                Key name
     * @param mixed             $value              Element value
     * @param boolean[optional] $createIfNotExists  Create list if not exists
     * @return mixed
     */
    public function appendToList($key, $value, $createIfNotExists = true) { $args = func_get_args(); return $this->_executeCommand('appendToList', $args); }

    /**
     * Append value to the head of List
     *
     * @param string            $key                Key name
     * @param mixed             $value              Element value
     * @param boolean[optional] $createIfNotExists  Create list if not exists
     * @return mixed
     */
    public function prependToList($key, $value, $createIfNotExists = true) { $args = func_get_args(); return $this->_executeCommand('prependToList', $args); }

    /**
     * Return the length of the List value at key
     *
     * @param string $key Key name
     * @return mixed
     */
    public function getListLength($key) { $args = func_get_args(); return $this->_executeCommand('getListLength', $args); }

    /**
     * Get List by key
     *
     * @param string  $key                         Key name
     * @param integer $start[optional]             Start index. For default is begin of list
     * @param integer $end[optional]               End index. For default is end of list
     * @param boolean $responseIterator[optional]  If true - command return iterator which read from socket buffer.
     *                                             Important: new connection will be created
     * @return array
     */
    public function getList($key, $start = 0, $end = -1, $responseIterator = false) { $args = func_get_args(); return $this->_executeCommand('getList', $args); }

    /**
     * Trim the list at key to the specified range of elements
     *
     * @param string  $key   Key name
     * @param integer $start Start index
     * @param integer $end   End index
     * @return boolean
     */
    public function truncateList($key, $start, $end) { $args = func_get_args(); return $this->_executeCommand('truncateList', $args); }

    /**
     * Return element of List by index at key
     *
     * @param string  $key   Key name
     * @param integer $index Index
     * @return mixed
     */
    public function getFromList($key, $index) { $args = func_get_args(); return $this->_executeCommand('getFromList', $args); }

    /**
     * Set a new value as the element at index position of the List at key
     *
     * @param string  $key   Key name
     * @param mixed   $value Value
     * @param integer $index Index
     * @return boolean
     */
    public function setToList($key, $index, $member) { $args = func_get_args(); return $this->_executeCommand('setToList', $args); }

    /**
     * Delete element from list by member at key
     *
     * @param $key             Key name
     * @param $value           Element value
     * @param $count[optional] Limit of deleted items. For default no limit.
     * @return mixed
     */
    public function deleteFromList($key, $value, $count = 0) { $args = func_get_args(); return $this->_executeCommand('deleteFromList', $args); }

    /**
     * Return and remove the first element of the List at key
     *
     * @param string $key Key name
     * @return mixed
     */
    public function shiftFromList($key) { $args = func_get_args(); return $this->_executeCommand('shiftFromList', $args); }

    /**
     * Return and remove the first element of the List at key and block if list is empty or not exists
     *
     * @param string $keyOrKeys   Key name or array of names
     * @param string $timeout     Blocking timeout in seconds. Timeout disabled for default.
     * @return mixed
     */
    public function shiftFromListBlocking($keyOrKeys, $timeout = 0) { $args = func_get_args(); return $this->_executeCommand('shiftFromListBlocking', $args); }

    /**
     * Return and remove the last element of the List at key
     *
     * @param string           $name       Key name
     * @param string[optional] $pushToName If not null - push value to another key.
     * @return mixed
     */
    public function popFromList($key, $pushToKey = null) { $args = func_get_args(); return $this->_executeCommand('popFromList', $args); }

    /**
     * Return and remove the last element of the List at key and block if list is empty or not exists
     *
     * @param string|array $keyOrKeys           Key name or array of names
     * @param integer      $timeout[optional]   Timeout. 0 for default - timeout is disabled.
     * @param string       $pushToKey[optional] If not null - push value to another list.
     * @return mixed
     */
    public function popFromListBlocking($keyOrKeys, $timeout = 0, $pushToKey = null) { $args = func_get_args(); return $this->_executeCommand('popFromListBlocking', $args); }

    /**
     * Insert a new value as the element before or after the reference value
     *
     * @param string  $key            Key name
     * @param string  $position       BEFORE or AFTER
     * @param mixed   $referenceValue Reference value
     * @param mixed   $value          Value
     * @return integer|boolean
     */
    public function insertToList($key, $position, $referenceValue, $value) { $args = func_get_args(); return $this->_executeCommand('insertToList', $args); }

    /**
     * Insert a new value as the element after the reference value
     *
     * @param string  $key            Key name
     * @param mixed   $referenceValue Reference value
     * @param mixed   $value          Value
     * @return integer|boolean
     */
    public function insertToListAfter($key, $referenceValue, $value) { $args = func_get_args(); return $this->_executeCommand('insertToListAfter', $args); }

    /**
     * Insert a new value as the element before the reference value
     *
     * @param string  $key            Key name
     * @param mixed   $referenceValue Reference value
     * @param mixed   $value          Value
     * @return integer|boolean
     */
    public function insertToListBefore($key, $referenceValue, $value) { $args = func_get_args(); return $this->_executeCommand('insertToListBefore', $args); }

    /**
     * Add the specified member to the Set value at key
     *
     * @param string $key    Key name
     * @param mixed  $member Member
     * @return boolean
     */
    public function addToSet($key, $member) { $args = func_get_args(); return $this->_executeCommand('addToSet', $args); }

    /**
     * Remove the specified member from the Set value at key
     *
     * @param string $key    Key name
     * @param mixed  $member Member
     * @return boolean
     */
    public function deleteFromSet($key, $member) { $args = func_get_args(); return $this->_executeCommand('deleteFromSet', $args); }

    /**
     * Get random element from the Set value at key
     *
     * @param string            $key  Key name
     * @param boolean[optional] $pop  If true - pop value from the set. For default is false
     * @return mixed
     */
    public function getRandomFromSet($key, $pop = false) { $args = func_get_args(); return $this->_executeCommand('getRandomFromSet', $args); }

    /**
     * Return the number of elements (the cardinality) of the Set at key
     *
     * @param string $key Key name
     * @return mixed
     */
    public function getSetLength($key) { $args = func_get_args(); return $this->_executeCommand('getSetLength', $args); }

    /**
     * Test if the specified value is a member of the Set at key
     *
     * @param string $key    Key value
     * @param mixed  $member Member
     * @return boolean
     */
    public function existsInSet($key, $member) { $args = func_get_args(); return $this->_executeCommand('existsInSet', $args); }

    /**
     * Return the intersection between the Sets stored at key1, key2, ..., keyN
     *
     * @param array            $keys     Array of key names
     * @param string[optional] $storeKey Store to set with key name
     * @return mixed
     */
    public function intersectSets(array $keys, $storeKey = null) { $args = func_get_args(); return $this->_executeCommand('intersectSets', $args); }

    /**
     * Return the union between the Sets stored at key1, key2, ..., keyN
     *
     * @param array            $keys     Array of key names
     * @param string[optional] $storeKey Store to set with key name
     * @return mixed
     */
    public function unionSets(array $keys, $storeKey = null) { $args = func_get_args(); return $this->_executeCommand('unionSets', $args); }

    /**
     * Return the difference between the Set stored at key1 and all the Sets key2, ..., keyN
     *
     * @param array            $keys     Array of key names
     * @param string[optional] $storeKey Store to set with key name
     * @return mixed
     */
    public function diffSets(array $keys, $storeKey = null) { $args = func_get_args(); return $this->_executeCommand('diffSets', $args); }

    /**
     * Return all the members of the Set value at key
     *
     * @param string  $key Key name
     * @param boolean $responseIterator[optional]  If true - command return iterator which read from socket buffer.
     *                                             Important: new connection will be created
     * @return array
     */
    public function getSet($key, $responseIterator = false) { $args = func_get_args(); return $this->_executeCommand('getSet', $args); }

    /**
     * Move the specified member from one Set to another atomically
     *
     * @param string $fromKey From key name
     * @param string $toKey   To key name
     * @param mixed  $member  Member
     * @return mixed
     */
    public function moveToSet($fromKey, $toKey, $member) { $args = func_get_args(); return $this->_executeCommand('moveToSet', $args); }

    /**
     * Add member to sorted set
     *
     * @param string $key    Key name
     * @param mixed  $member Member
     * @param number $score  Score of member
     * @return boolean
     */
    public function addToSortedSet($key, $member, $score) { $args = func_get_args(); return $this->_executeCommand('addToSortedSet', $args); }

    /**
     * Delete the specified member from the sorted set by value
     *
     * @param string $key    Key name
     * @param mixed  $member Member
     * @return boolean
     */
    public function deleteFromSortedSet($key, $member) { $args = func_get_args(); return $this->_executeCommand('deleteFromSortedSet', $args); }

    /**
     * Get all the members of the Sorted Set value at key
     *
     * @param string  $key                         Key name
     * @param integer $withScores[optional]        Return values with scores. For default is false.
     * @param integer $start[optional]             Start index. For default is begin of set.
     * @param integer $end[optional]               End index. For default is end of set.
     * @param boolean $revert[optional]            Revert elements (not used in sorting). For default is false
     * @param boolean $responseIterator[optional]  If true - command return iterator which read from socket buffer.
     *                                             Important: new connection will be created
     * @return array
     */
    public function getSortedSet($key, $withScores = false, $start = 0, $end = -1, $revert = false, $responseIterator = false) { $args = func_get_args(); return $this->_executeCommand('getSortedSet', $args); }

    /**
     * Get members from sorted set by min and max score
     *
     * @param string            $key        Key name
     * @param number            $min        Min score
     * @param number            $max        Max score
     * @param boolean[optional] $withScores Get with scores. For default is false
     * @param integer[optional] $limit      Limit. For default is no limit
     * @param integer[optional] $offset     Offset. For default is no offset
     * @param boolean[optional] $revert     Revert. For default is false
     * @return array
     */
    public function getFromSortedSetByScore($key, $min, $max, $withScores = false, $limit = null, $offset = null, $revert = false) { $args = func_get_args(); return $this->_executeCommand('getFromSortedSetByScore', $args); }

    /**
     * Get length of Sorted Set
     *
     * @param string $key Key name
     * @return mixed
     */
    public function getSortedSetLength($key) { $args = func_get_args(); return $this->_executeCommand('getSortedSetLength', $args); }

    /**
     * Get count of members from sorted set by min and max score
     *
     * @param string $key Key name
     * @param number $min Min score
     * @param number $max Max score
     * @return mixed
     */
    public function getSortedSetLengthByScore($key, $min, $max) { $args = func_get_args(); return $this->_executeCommand('getSortedSetLengthByScore', $args); }

    /**
     * Increment score of sorted set element
     *
     * @param string        $key   Key name
     * @param mixed         $value Member
     * @param integer|float $score Score to increment
     * @return mixed
     */
    public function incrementScoreInSortedSet($key, $value, $score) { $args = func_get_args(); return $this->_executeCommand('incrementScoreInSortedSet', $args); }

    /**
     * Remove all the elements in the sorted set at key with a score between min and max (including elements with score equal to min or max).
     *
     * @param string  $key   Key name
     * @param numeric $min   Min value
     * @param numeric $max   Max value
     * @return mixed
     */
    public function deleteFromSortedSetByScore($key, $min, $max) { $args = func_get_args(); return $this->_executeCommand('deleteFromSortedSetByScore', $args); }

    /**
     * Remove all elements in the sorted set at key with rank between start  and end
     *
     * @param string  $key   Key name
     * @param numeric $start Start position
     * @param numeric $end   End position
     * @return mixed
     */
    public function deleteFromSortedSetByRank($key, $start, $end) { $args = func_get_args(); return $this->_executeCommand('deleteFromSortedSetByRank', $args); }

    /**
     * Get member score from Sorted Set
     *
     * @param string $key    Key name
     * @param mixed  $member Member value
     * @return mixed
     */
    public function getScoreFromSortedSet($key, $member) { $args = func_get_args(); return $this->_executeCommand('getScoreFromSortedSet', $args); }

    /**
     * Get rank of member from sorted set
     *
     * @param string  $key              Key name
     * @param integer $member           Member value
     * @param boolean $revert[optional] Revert elements (not used in sorting). For default is false
     * @return mixed
     */
    public function getRankFromSortedSet($key, $member, $revert = false) { $args = func_get_args(); return $this->_executeCommand('getRankFromSortedSet', $args); }

    /**
     * Store to key union between the sorted sets
     *
     * @param array  $keys       Array of key names or associative array with weights
     * @param string $storeKey   Result sorted set key name
     * @param string $aggregation Aggregation method: SUM (for default), MIN, MAX.
     * @return mixed
     */
    public function unionSortedSets(array $keys, $storeKey, $aggregation = Rediska_Command_UnionSortedSets::SUM) { $args = func_get_args(); return $this->_executeCommand('unionSortedSets', $args); }

    /**
     * Store to key intersection between sorted sets
     *
     * @param array  $keys       Array of key names or associative array with weights
     * @param string $storeKey   Result sorted set key name
     * @param string $aggregation Aggregation method: SUM (for default), MIN, MAX.
     * @return mixed
     */
    public function intersectSortedSets(array $keys, $storeKey, $aggregation = Rediska_Command_IntersectSortedSets::SUM) { $args = func_get_args(); return $this->_executeCommand('intersectSortedSets', $args); }

    /**
     * Set value to a hash field or fields
     *
     * @param string        $key          Key name
     * @param array|string  $fieldOrData  Field or array of many fields and values: field => value
     * @param mixed         $value        Value for single field
     * @param boolean       $overwrite    Overwrite for single field (if false don't set and return false if key already exist). For default true.
     * @return boolean
     */
    public function setToHash($key, $fieldOrData, $value = null, $overwrite = true) { $args = func_get_args(); return $this->_executeCommand('setToHash', $args); }

    /**
     * Get value from hash field or fields
     *
     * @param string       $key           Key name
     * @param string|array $fieldOrFields Field or fields
     * @return mixed
     */
    public function getFromHash($key, $fieldOrFields) { $args = func_get_args(); return $this->_executeCommand('getFromHash', $args); }

    /**
     * Increment field value in hash
     *
     * @param string $key              Key name
     * @param mixed  $field            Field
     * @param number $amount[optional] Increment amount. One for default
     * @return mixed
     */
    public function incrementInHash($key, $field, $amount = 1) { $args = func_get_args(); return $this->_executeCommand('incrementInHash', $args); }

    /**
     * Test if field is present in hash
     *
     * @param string $key   Key name
     * @param mixed  $field Field
     * @return boolean
     */
    public function existsInHash($key, $field) { $args = func_get_args(); return $this->_executeCommand('existsInHash', $args); }

    /**
     * Delete field from hash
     *
     * @param string $key   Key name
     * @param mixed  $field Field
     * @return boolean
     */
    public function deleteFromHash($key, $field) { $args = func_get_args(); return $this->_executeCommand('deleteFromHash', $args); }

    /**
     * Return the number of fields in hash
     *
     * @param string $key Key name
     * @return mixed
     */
    public function getHashLength($key) { $args = func_get_args(); return $this->_executeCommand('getHashLength', $args); }

    /**
     * Get hash fields and values
     *
     * @param string $key Key name
     * @return array
     */
    public function getHash($key) { $args = func_get_args(); return $this->_executeCommand('getHash', $args); }

    /**
     * Get hash fields
     *
     * @param string $key Key name
     * @return mixed
     */
    public function getHashFields($key) { $args = func_get_args(); return $this->_executeCommand('getHashFields', $args); }

    /**
     * Get hash values
     *
     * @param string $key Key name
     * @return array
     */
    public function getHashValues($key) { $args = func_get_args(); return $this->_executeCommand('getHashValues', $args); }

    /**
     * Get sorted elements contained in the List, Set, or Sorted Set value at key.
     *
     * @param string        $key   Key name
     * @param array         $value Options:
     *                               * order
     *                               * limit
     *                               * offset
     *                               * alpha
     *                               * get
     *                               * by
     *                               * store
     *
     *                              See more: http://code.google.com/p/redis/wiki/SortCommand

     *                              If you use more then one connection to Redis servers,
     *                              it will choose by key name, and key by you pattern's may not present on it.
     *
     * @return array
     */
    public function sort($key, array $options = array()) { $args = func_get_args(); return $this->_executeCommand('sort', $args); }

    /**
     * Publish message to pubsub channel
     *
     * @param array|string $channelOrChannels Channel or array of channels
     * @param mixed        $message           Message
     * @return mixed
     */
    public function publish($channelOrChannels, $message) { $args = func_get_args(); return $this->_executeCommand('publish', $args); }

    /**
     * Save the DB on disk
     *
     * @param boolean[optional] $background Save asynchronously. For default is false
     * @return mixed
     */
    public function save($background = false) { $args = func_get_args(); return $this->_executeCommand('save', $args); }

    /**
     * Return the UNIX time stamp of the last successfully saving of the dataset on disk
     *
     * @return mixed
     */
    public function getLastSaveTime() { $args = func_get_args(); return $this->_executeCommand('getLastSaveTime', $args); }

    /**
     * Stop all the clients, save the DB, then quit the server
     *
     * @return mixed
     */
    public function shutdown() { $args = func_get_args(); return $this->_executeCommand('shutdown', $args); }

    /**
     * Rewrite the Append Only File in background when it gets too big
     *
     * @return mixed
     */
    public function rewriteAppendOnlyFile() { $args = func_get_args(); return $this->_executeCommand('rewriteAppendOnlyFile', $args); }

    /**
     * Provide information and statistics about the server
     *
     * @return mixed
     */
    public function info() { $args = func_get_args(); return $this->_executeCommand('info', $args); }

    /**
     * This command is often used to test if a connection is still alive, or to
     * measure latency.
     *
     * @return mixed
     */
    public function ping() { $args = func_get_args(); return $this->_executeCommand('ping', $args); }

    /**
     * Change the replication settings of a slave on the fly
     *
     * @param string|Rediska_Connection|false $aliasOrConnection Server alias, Rediska_Connection object or false if not slave
     * @return mixed
     */
    public function slaveOf($aliasOrConnection) { $args = func_get_args(); return $this->_executeCommand('slaveOf', $args); }

}


<?php

/**
 * Rediska autoloader
 *
 * @author Ivan Shumkov
 * @package Rediska
 * @version @package_version@
 * @link http://rediska.geometria-lab.net
 * @license http://www.opensource.org/licenses/bsd-license.php
 */
class Rediska_Autoloader
{
    /**
     * Is registered Rediska autoload
     *
     * @var boolean
     */
    protected static $_isRegistered;

    /**
     * Rediska path
     *
     * @var string
     */
    protected static $_rediskaPath;

    /**
     * Autoload callback
     *
     * @var array
     */
    protected static $_callback = array('Rediska_Autoloader', 'load');

    /**
     * Register Rediska autoload
     *
     * @param boolean $prepend Prepend the autoloader in the chain, the default is
     *                         'false'. This parameter is available since PHP 5.3.0
     *                         and will be silently disregarded otherwise.
     * @return boolean
     */
    public static function register($prepend = false)
    {
        if (self::isRegistered()) {
            return false;
        }
        if (!is_bool($prepend)) {
            $prepend = (bool) $prepend;
        }

        if (version_compare(PHP_VERSION, '5.3.0', '<')) {
            self::$_isRegistered = spl_autoload_register(self::$_callback);
        } else {
            self::$_isRegistered = spl_autoload_register(
                self::$_callback,
                true,
                $prepend
            );
        }
        return self::$_isRegistered;
    }

    /**
     * Unregister Rediska autoload
     *
     * @return boolean
     */
    public static function unregister()
    {
        if (!self::isRegistered()) {
            return false;
        }

        self::$_isRegistered = !spl_autoload_unregister(self::$_callback);

        return self::$_isRegistered;
    }

    /**
     * Is Rediska autoload registered
     *
     * @return boolean
     */
    public static function isRegistered()
    {
        return self::$_isRegistered;
    }

    /**
     * Load class
     *
     * @param string $className
     *
     * @return boolean
     */
    public static function load($className)
    {
        if (0 !== strpos($className, 'Rediska')) {
            return false;
        }

        $path = self::getRediskaPath() . '/' . str_replace('_', '/', $className) . '.php';

        return include $path;
    }

    /**
     * Get Rediska path
     *
     * @return string
     */
    public static function getRediskaPath()
    {
        if (!self::$_rediskaPath) {
            self::$_rediskaPath = realpath(dirname(__FILE__) . '/..');
        }

        return self::$_rediskaPath;
    }
}


<?php

/**
 * @author Kijin Sung <kijinbear@gmail.com>
 * @package Rediska
 * @subpackage Key distributor
 * @link http://github.com/kijin/distrib
 * @version 0.1.1
 * @link http://rediska.geometria-lab.net
 * @license http://www.opensource.org/licenses/bsd-license.php
 */
class Rediska_KeyDistributor_ConsistentHashing implements Rediska_KeyDistributor_Interface
{
    protected $_backends = array();
    protected $_backendsCount = 0;

    protected $_hashring = array();
    protected $_hashringCount = 0;

    protected $_replicas = 256;
    protected $_slicesCount = 0;
    protected $_slicesHalf = 0;
    protected $_slicesDiv = 0;

    protected $_cache = array();
    protected $_cacheCount = 0;
    protected $_cacheMax = 256;

    protected $_hashringIsInitialized = false;

    /**
     * (non-PHPdoc)
     * @see Rediska_KeyDistributor_Interface#addConnection
     */
    public function addConnection($connectionString, $weight = Rediska_Connection::DEFAULT_WEIGHT)
    {
        if (isset($this->_backends[$connectionString])) {
            throw new Rediska_KeyDistributor_Exception("Connection '$connectionString' already exists.");
        }

        $this->_backends[$connectionString] = $weight;

        $this->_backendsCount++;

        $this->_hashringIsInitialized = false;

        return $this;
    }

    /**
     * (non-PHPdoc)
     * @see Rediska_KeyDistributor_Interface#removeConnection
     */
    public function removeConnection($connectionString)
    {
        if (!isset($this->_backends[$connectionString])) {
            throw new Rediska_KeyDistributor_Exception("Connection '$connectionString' not exist.");
        }

        unset($this->_backends[$connectionString]);

        $this->_backendsCount--;

        $this->_hashringIsInitialized = false;

        return $this;
    }

    /**
     * (non-PHPdoc)
     * @see Rediska_KeyDistributor_Interface#getConnectionByKeyName
     */
    public function getConnectionByKeyName($name)
    {
        $connections = $this->getConnectionsByKeyName($name, 1);
        if (empty($connections)) {
            throw new Rediska_KeyDistributor_Exception('No connections exist');
        }
        return $connections[0];
    }

    /**
     * Get a list of connections by key name
     *
     * @param string $name Key name
     * @param int $requestedCount The length of the list to return
     * @return array List of connections
     */
    public function getConnectionsByKeyName($name, $count)
    {
        // If we have only one backend, return it.
        if ($this->_backendsCount == 1) {
            return array_keys($this->_backends);
        }

        if (!$this->_hashringIsInitialized) {
            $this->_initializeHashring();
            $this->_hashringIsInitialized = true;
        }

        // If the key has already been mapped, return the cached entry.
        if ($this->_cacheMax > 0 && isset($this->_cache[$name])) {
            return $this->_cache[$name];
        }

        // If $count is greater than or equal to the number of available backends, return all.
        if ($count >= $this->_backendsCount) return array_keys($this->_backends);

        // Initialize the return array.
        $return = array();

        $crc32 = crc32($name);

        // Select the slice to begin with.
        $slice = floor($crc32 / $this->_slicesDiv) + $this->_slicesHalf;

        // This counter prevents going through more than 1 loop.
        $looped = false;

        while (true) {
            // Go through the hashring, one slice at a time.
            foreach ($this->_hashring[$slice] as $position => $backend) {
                // If we have a usable backend, add to the return array.
                if ($position >= $crc32) {
                    // If $count = 1, no more checks are necessary.
                    if ($count === 1) {
                        $return = array($backend);
                        break 2;
                    } elseif (!in_array($backend, $return)) {
                        $return[] = $backend;
                        if (count($return) >= $count) break 2;
                    }

                    $return = array($backend);
                    break 1;
                }
            }

            // Continue to the next slice.
            $slice++;

            // If at the end of the hashring.
            if ($slice >= $this->_slicesCount) {
                // If already looped once, something is wrong.
                if ($looped) {
                    break;
                }

                // Otherwise, loop back to the beginning.
                $crc32 = -2147483648;
                $slice = 0;
                $looped = true;
            }
        }

        // Cache the result for quick retrieval in the future.
        if ($this->_cacheMax > 0) {
            // Add to internal cache.
            $this->_cache[$name] = $return;
            $this->_cacheCount++;

            // If the cache is getting too big, clear it.
            if ($this->_cacheCount > $this->_cacheMax) {
                $this->_cleanCache();
            }
        }

        // Return the result.

        return $return;
    }

    protected function _initializeHashring()
    {
        if ($this->_backendsCount < 2) {
            $this->_hashring = array();
            $this->_hashringCount = 0;

            $this->_slicesCount = 0;
            $this->_slicesHalf = 0;
            $this->_slicesDiv = 0;
        } else {
            $this->_slicesCount = ($this->_replicas * $this->_backendsCount) / 8;
            $this->_slicesHalf = $this->_slicesCount / 2;
            $this->_slicesDiv = (2147483648 / $this->_slicesHalf);

            // Initialize the hashring.
            $this->_hashring = array_fill(0, $this->_slicesCount, array());

            // Calculate the average weight.
            $avg = round(array_sum($this->_backends) / $this->_backendsCount, 2);

            // Interate over the backends.
            foreach ($this->_backends as $backend => $weight) {
                // Adjust the weight.
                $weight = round(($weight / $avg) * $this->_replicas);

                // Create as many replicas as $weight.
                for ($i = 0; $i < $weight; $i++) {
                    $position = crc32($backend . ':' . $i);
                    $slice = floor($position / $this->_slicesDiv) + $this->_slicesHalf;
                    $this->_hashring[$slice][$position] = $backend;
                }
            }

            // Sort each slice of the hashring.
            for ($i = 0; $i < $this->_slicesCount; $i++) {
                ksort($this->_hashring[$i], SORT_NUMERIC);
            }
        }

        $this->_cleanCache();
    }

    protected function _cleanCache()
    {
        $this->_cache = array();
        $this->_cacheCount = 0;
    }
}


<?php

/**
 * This iterator is used by Rediska_PubSub_Channel
 * to repeatedly iterate through available connections
 *
 * @author Ivan Shumkov
 * @package Rediska
 * @subpackage PublishSubscribe
 * @version @package_version@
 * @link http://rediska.geometria-lab.net
 * @license http://www.opensource.org/licenses/bsd-license.php
 */
class Rediska_PubSub_Connections implements IteratorAggregate, Countable
{
    /**
     * PubSub channel
     *
     * @var Rediska_PubSub_Channel
     */
    protected $_channel;

    /**
     * Channels by connections
     *
     * @var array
     */
    protected $_channelsByConnections = array();

    /**
     * Specified by server alias connection
     *
     * @var Rediska_Connection
     */
    protected $_specifiedConnection;

    /**
     * Connections
     *
     * @var array
     */
    protected $_connections = array();

    /**
     * Constructor
     *
     * @param Rediska_PubSub_Channel $channel
     */
    public function __construct(Rediska_PubSub_Channel $channel)
    {
        $this->_channel = $channel;

        // Set specified connection
        if ($channel->getServerAlias() instanceof Rediska_Connection) {
            $this->_specifiedConnection = $channel->getServerAlias();
        } elseif ($channel->getServerAlias() !== null) {
            $this->_specifiedConnection = $channel->getRediska()->getConnectionByAlias($channel->getServerAlias());
        }
    }

    /**
     * Add channel
     *
     * @param string $channel
     * @return Rediska_Connection
     */
    public function addChannel($channel)
    {
        $connection = $this->getConnectionByChannelName($channel);

        if (!array_key_exists($connection->getAlias(), $this->_channelsByConnections)) {
            $this->_channelsByConnections[$connection->getAlias()] = array();
            $this->_connections[$connection->getAlias()] = $connection;
        }
        if (!in_array($channel, $this->_channelsByConnections[$connection->getAlias()])) {
            $this->_channelsByConnections[$connection->getAlias()][] = $channel;
        }

        return $connection;
    }

    /**
     * Has channel?
     *
     * @param string $channel
     * @return boolean
     */
    public function hasChannel($channel)
    {
        $connection = $this->getConnectionByChannelName($channel);

        return isset($this->_channelsByConnections[$connection->getAlias()])
            && in_array($channel, $this->_channelsByConnections[$connection->getAlias()]);
    }

    /**
     * Remove channel
     *
     * @param string $channel
     * @return Rediska_Connection
     */
    public function removeChannel($channel)
    {
        $connection = $this->getConnectionByChannelName($channel);

        $key = array_search($channel, $this->_channelsByConnections[$connection->getAlias()]);
        unset($this->_channelsByConnections[$connection->getAlias()][$key]);

        if (empty($this->_channelsByConnections[$connection->getAlias()])) {
            unset($this->_channelsByConnections[$connection->getAlias()]);
            unset($this->_connections[$connection->getAlias()]);

            // If only one - move to blocking mode
            if (count($this->_connections) == 1) {
                foreach($this->_connections as $connection) {
                    if (!$connection->getOption('blockingMode')) {
                        $connection->setOption('blockingMode', true);
                    }
                }
            }
        }

        return $connection;
    }

    /**
     * Get connection by channel name
     *
     * @param string $name Channel name
     * @return Rediska_Connection
     */
    public function getConnectionByChannelName($name)
    {
        // Get connection
        if ($this->_specifiedConnection) {
            $connection = $this->_specifiedConnection;
        } else {
            $connection = $this->_channel->getRediska()->getConnectionByKeyName($name);
        }

        if (!array_key_exists($connection->getAlias(), $this->_connections)) {
            $this->_connections[$connection->getAlias()] = clone $connection;

            // If more than one - move to non blocking mode
            if (count($this->_connections) > 1) {
                foreach($this->_connections as $connection) {
                    if ($connection->getOption('blockingMode')) {
                        $connection->setOption('blockingMode', false);
                    }
                }
            }
        }
        $connection = $this->_connections[$connection->getAlias()];

        return $connection;
    }

    /**
     * Get connection by alias
     *
     * @param string $alias
     * @return Rediska_Connection
     */
    public function getConnectionByAlias($alias)
    {
        if (!isset($this->_connections[$alias])) {
            throw new Rediska_PubSub_Exception("Can't find connection '$alias'");
        }

        return $this->_connections[$alias];
    }

    /**
     * Get channels by connection
     *
     * @param $connection
     * @return array
     */
    public function getChannelsByConnection(Rediska_Connection $connection)
    {
        if (!isset($this->_channelsByConnections[$connection->getAlias()])) {
            throw new Rediska_PubSub_Exception("Channels by this connection not present");
        }

        return $this->_channelsByConnections[$connection->getAlias()];
    }

    /* IteratorAggregate implementation */

    public function getIterator()
    {
        return new ArrayObject($this->_connections);
    }

    /* Countable implementation */

    public function count()
    {
        return count($this->_connections);
    }
}


<?php

/**
 * Redis server config
 *
 * @author Ivan Shumkov
 * @package Rediska
 * @version @package_version@
 * @link http://rediska.geometria-lab.net
 * @license http://www.opensource.org/licenses/bsd-license.php
 */
class Rediska_Config implements IteratorAggregate, ArrayAccess, Countable
{
    /**
     * Rediska instance
     *
     * @var Rediska
     */
    protected $_rediska;

    /**
     * Rediska specified connection instance
     *
     * @var Rediska_Connection
     */
    protected $_connection;

    /**
     * Constructor
     *
     * @param Rediska            $rediska    Rediska instance
     * @param Rediska_Connection $connection Config connection
     */
    public function __construct(Rediska $rediska, Rediska_Connection $connection)
    {
        $this->_rediska    = $rediska;
        $this->_connection = $connection;

        $this->_throwIfNotSupported();
    }

    /**
     * Get parameter or parameters by pattern
     *
     * @param string $nameOrPattern Name or pattern
     * @return string|array
     */
    public function get($nameOrPattern)
    {
        $configGet = new Rediska_Connection_Exec($this->_connection, array('CONFIG', 'GET', $nameOrPattern));
        $valuesAfterNames = $configGet->execute();

        if (preg_match('/\W/i', $nameOrPattern)) { // Pattern
            $isParameter = true;
            $namesAndValues = array();
            foreach($valuesAfterNames as $nameOrValue) {
                if ($isParameter) {
                    $name = $nameOrValue;
                } else {
                    $namesAndValues[$name] = $this->_sanitizeValue($nameOrValue);
                }

                $isParameter = !$isParameter;
            }

            return $namesAndValues;
        } else { // Parameter name
            if (empty($valuesAfterNames)) {
                return null;
            } else {
                return $this->_sanitizeValue($valuesAfterNames[1]);
            }
        }
    }

    /**
     * Magic for get a parameter
     * @param string $name Name
     * @return string
     */
    public function __get($name)
    {
        return $this->get($name);
    }

    /**
     * Array magic for get a parameter
     * @param string $nameOrPattern Name or pattern
     * @return string
     */
    public function offsetGet($nameOrPattern)
    {
        return $this->get($nameOrPattern);
    }

    /**
     * Set value to parameter
     *
     * @param string $name  Name
     * @param string $value Value
     * @return Rediska_Config
     */
    public function set($name, $value)
    {
        $exec = new Rediska_Connection_Exec($this->_connection, array('CONFIG', 'SET', $name, $value));
        $exec->execute();

        return $this;
    }

    /**
     * Magic for set a value to parameter
     *
     * @param string $name  Name
     * @param mixed  $value Value
     * @return $value
     */
    public function __set($name, $value)
    {
        $this->set($name, $value);

        return $value;
    }

    /**
     * Array magic for set a value to parameter
     *
     * @param string $field
     * @param mixed $value
     * @return boolean
     */
    public function offsetSet($name, $value)
    {
        return $this->set($name, $value);
    }

    /**
     * Get all parameters as array
     *
     * @return array
     */
    public function toArray()
    {
        return $this->get('*');
    }

    /* Countable implementation */

    public function count()
    {
        $exec = new Rediska_Connection_Exec($this->_connection, array('CONFIG', 'GET', '*'));
        $namesAndValues = $exec->execute();

        return count($namesAndValues) / 2;
    }

    /* IteratorAggregate implementation */

    public function getIterator()
    {
        return new ArrayObject($this->toArray());
    }

    /* ArrayAccess implementation */

    public function offsetExists($offset)
    {
        throw new Rediska_Exception('Not implemented!');
    }

    public function offsetUnset($offset)
    {
        throw new Rediska_Exception('Not implemented!');
    }

    /**
     * Sanitize value
     *
     * @param string $value
     * @return string|integer|float
     */
    protected function _sanitizeValue($value)
    {
        $value = trim($value);

        // Try to check number
        if (strpos($value, '.') !== false) {
            $number = (integer)$value;
        } else {
            $number = (float)$value;
        }

        if ((string)$number == $value) {
            $value = $number;
        }

        return $value;
    }

    /**
     * Throw if config not supported by Redis
     */
    protected function _throwIfNotSupported()
    {
        $version = '2.0';
        $redisVersion = $this->_rediska->getOption('redisVersion');
        if (version_compare($version, $this->_rediska->getOption('redisVersion')) == 1) {
            throw new Rediska_Transaction_Exception("Transaction requires {$version}+ version of Redis server. Current version is {$redisVersion}. To change it specify 'redisVersion' option.");
        }
    }
}


<?php

// Require Rediska
require_once dirname(__FILE__) . '/../../../../Rediska.php';

/**
 * @see Zend_Queue_Adapter_AdapterAbstract
 */
require_once 'Zend/Queue/Adapter/AdapterAbstract.php';

/**
 * Redis adapter for Zend_Queue
 *
 * @author Ivan Shumkov
 * @package Rediska
 * @subpackage ZendFrameworkIntegration
 * @version @package_version@
 * @link http://rediska.geometria-lab.net
 * @license http://www.opensource.org/licenses/bsd-license.php
 */
class Rediska_Zend_Queue_Adapter_Redis extends Zend_Queue_Adapter_AdapterAbstract
{
    const KEY_PREFIX = 'Zend_Queue_';

    /**
     * Rediska instance
     *
     * @var Rediska
     */
    protected $_rediska = Rediska::DEFAULT_NAME;

    /**
     * Queues set
     *
     * @var Rediska_Zend_Queue_Adapter_Redis_Queues
     */
    protected $_queues;

    /**
     * Queue Lists array
     *
     * @var array
     */
    protected $_queueObjects;

    /**
     * Constructor
     *
     * @param  array|Zend_Config $options
     * @param  null|Zend_Queue $queue
     * @return void
     */
    public function __construct($options, Zend_Queue $queue = null)
    {
        parent::__construct($options, $queue);

        if (!empty($this->_options['driverOptions'])) {
            $this->_rediska = $options['driverOptions'];
        }

        $this->_rediska = Rediska_Options_RediskaInstance::getRediskaInstance($this->_rediska, 'Zend_Queue_Exception', 'driverOptions');

        $queuesSet = new Rediska_Key_Set($this->_getKeyName('queues'), array('rediska' => $this->_rediska));
        $this->_queues = new Rediska_Zend_Queue_Adapter_Redis_Queues($queuesSet);
    }

    /**
     * Does a queue already exist?
     *
     * Throws an exception if the adapter cannot determine if a queue exists.
     * use isSupported('isExists') to determine if an adapter can test for
     * queue existance.
     *
     * @param  string $name
     * @return boolean
     */
    public function isExists($name)
    {
        return $this->_queues->exists($name);
    }

    /**
     * Create a new queue
     *
     * Visibility timeout is how long a message is left in the queue "invisible"
     * to other readers.  If the message is acknowleged (deleted) before the
     * timeout, then the message is deleted.  However, if the timeout expires
     * then the message will be made available to other queue readers.
     *
     * @param  string  $name    queue name
     * @param  integer $timeout default visibility timeout
     * @return boolean
     */
    public function create($name, $timeout = null)
    {
        $this->_queues->add($name);
        $this->_queueObjects[$name] = new Rediska_Key_List($this->_getKeyName("queue_$name"), array('rediska' => $this->_rediska));

        return true;
    }

    /**
     * Delete a queue and all of it's messages
     *
     * Returns false if the queue is not found, true if the queue exists
     *
     * @param  string  $name queue name
     * @return boolean
     */
    public function delete($name)
    {
        if ($this->_queues->remove($name)) {
            if (isset($this->_queueObjects[$name])) {
                unset($this->_queueObjects[$name]);
            }

            return $this->_rediska->delete($this->_getKeyName("queue_$name"));
        }
    }

    /**
     * Get an array of all available queues
     *
     * Not all adapters support getQueues(), use isSupported('getQueues')
     * to determine if the adapter supports this feature.
     *
     * @return array
     */
    public function getQueues()
    {
        return $this->_queues->toArray();
    }

    protected function _getKeyName($name)
    {
        return self::KEY_PREFIX . $name;
    }

    /**
     * Return the approximate number of messages in the queue
     *
     * @param  Zend_Queue $queue
     * @return integer
     */
    public function count(Zend_Queue $queue=null)
    {
        if ($queue === null) {
            $queue = $this->_queue;
        }

        $queueName = $queue->getName();

        if (!$this->isExists($queueName)) {
            require_once 'Zend/Queue/Exception.php';
            throw new Zend_Queue_Exception('Queue does not exist:' . $queueName);
        }

        if (!isset($this->_queueObjects[$queueName])) {
            $this->_queueObjects[$queueName] = new Rediska_Key_List($this->_getKeyName("queue_$queueName"), array('rediska' => $this->_rediska));
        }

        return count($this->_queueObjects[$queueName]);
    }

    /********************************************************************
     * Messsage management functions
     *********************************************************************/

    /**
     * Send a message to the queue
     *
     * @param  string     $message Message to send to the active queue
     * @param  Zend_Queue $queue
     * @return Zend_Queue_Message
     * @throws Zend_Queue_Exception
     */
    public function send($message, Zend_Queue $queue=null)
    {
        if ($queue === null) {
            $queue = $this->_queue;
        }

        $queueName = $queue->getName();

        if (!$this->isExists($queueName)) {
            require_once 'Zend/Queue/Exception.php';
            throw new Zend_Queue_Exception('Queue does not exist:' . $queueName);
        }

        if (!isset($this->_queueObjects[$queueName])) {
            $this->_queueObjects[$queueName] = new Rediska_Key_List($this->_getKeyName("queue_$queueName"), array('rediska' => $this->_rediska));
        }

        $result = $this->_queueObjects[$queueName]->prepend($message);

        if ($result === false) {
            require_once 'Zend/Queue/Exception.php';
            throw new Zend_Queue_Exception('Failed to insert message into queue:' . $queueName);
        }

        $options = array(
            'queue' => $queue,
            'data'  => array('body' => $message),
        );

        $classname = $queue->getMessageClass();
        if (!class_exists($classname)) {
            require_once 'Zend/Loader.php';
            Zend_Loader::loadClass($classname);
        }
        return new $classname($options);
    }

    /**
     * Get messages in the queue
     *
     * @param  integer    $maxMessages  Maximum number of messages to return
     * @param  integer    $timeout      Visibility timeout for these messages
     * @param  Zend_Queue $queue
     * @return Zend_Queue_Message_Iterator
     */
    public function receive($maxMessages=null, $timeout=null, Zend_Queue $queue=null)
    {
        if ($maxMessages === null) {
            $maxMessages = 1;
        }
        if ($queue === null) {
            $queue = $this->_queue;
        }

        $queueName = $queue->getName();

        if (!$this->isExists($queueName)) {
            require_once 'Zend/Queue/Exception.php';
            throw new Zend_Queue_Exception('Queue does not exist:' . $queueName);
        }

        if (!isset($this->_queueObjects[$queueName])) {
            $this->_queueObjects[$queueName] = new Rediska_Key_List($this->_getKeyName("queue_$queueName"), array('rediska' => $this->_rediska));
        }

        $messages = array();
        for ($i = 0; $i < $maxMessages; $i++) {
            $message = $this->_queueObjects[$queueName]->pop();
            if (!is_null($message)) {
                $messages[] = array('body' => $message);
            }
        }

        $options = array(
            'queue'        => $queue,
            'data'         => $messages,
            'messageClass' => $queue->getMessageClass(),
        );

        $classname = $queue->getMessageSetClass();
        if (!class_exists($classname)) {
            require_once 'Zend/Loader.php';
            Zend_Loader::loadClass($classname);
        }
        return new $classname($options);
    }

    /**
     * Delete a message from the queue
     *
     * Returns true if the message is deleted, false if the deletion is
     * unsuccessful.
     *
     * @param  Zend_Queue_Message $message
     * @return boolean
     */
    public function deleteMessage(Zend_Queue_Message $message)
    {
        $queueName = $this->_queue->getName();

        if (!isset($this->_queueObjects[$queueName])) {
            $this->_queueObjects[$queueName] = new Rediska_Key_List($this->_getKeyName("queue_$queueName"), array('rediska' => $this->_rediska));
        }

        return (boolean)$this->_queueObjects[$queueName]->remove($message->body);
    }

    /********************************************************************
     * Supporting functions
     *********************************************************************/

    /**
     * Return a list of queue capabilities functions
     *
     * $array['function name'] = true or false
     * true is supported, false is not supported.
     *
     * @param  string $name
     * @return array
     */
    public function getCapabilities()
    {
        return array(
            'create'        => true,
            'delete'        => true,
            'send'          => true,
            'receive'       => true,
            'deleteMessage' => true,
            'getQueues'     => true,
            'count'         => true,
            'isExists'      => true,
        );
    }
}


<?php

/**
 * Rename the old key in the new one
 *
 * @author Ivan Shumkov
 * @package Rediska
 * @subpackage Commands
 * @version @package_version@
 * @link http://rediska.geometria-lab.net
 * @license http://www.opensource.org/licenses/bsd-license.php
 */
class Rediska_Command_Rename extends Rediska_Command_Abstract
{
    /**
     * Create command
     *
     * @param string            $oldKey    Old key name
     * @param string            $newKey    New key name
     * @param boolean[optional] $overwrite Overwrite the new name key if it already exists. For default is false.
     * @return Rediska_Connection_Exec
     */
    public function create($oldKey, $newKey, $overwrite = true)
    {
        $oldKeyConnection = $this->_rediska->getConnectionByKeyName($oldKey);
        $newKeyConnection = $this->_rediska->getConnectionByKeyName($newKey);

        $command = '';
        if ($oldKeyConnection === $newKeyConnection) {
            if ($overwrite) {
                $command = array();
            } else {
                $command = "";
            }
            $command = array($overwrite ? 'RENAME' : 'RENAMENX',
                             $this->_rediska->getOption('namespace') . $oldKey,
                             $this->_rediska->getOption('namespace') . $newKey);
        } else {
            $this->setAtomic(false);

            $command = array('GET',
                             $this->_rediska->getOption('namespace') . $oldKey);
        }

        return new Rediska_Connection_Exec($oldKeyConnection, $command);
    }

    /**
     * Parse responses
     *
     * @param array $responses
     * @return boolean
     */
    public function parseResponses($responses)
    {
        if (!$this->isAtomic()) {
            $oldValue = $this->_rediska->getSerializer()->unserialize($responses[0]);
            if (!is_null($oldValue)) {
                $reply = $this->_rediska->set($this->newKey, $oldValue, $this->overwrite);

                if ($reply) {
                    $this->_rediska->delete($this->oldKey);
                }

                return $reply;
            } else {
                throw new Rediska_Command_Exception('No such key');
            }
        } else {
            return (boolean)$responses[0];
        }
    }
}


<?php

/**
 * Rediska profiler
 *
 * @author Ivan Shumkov
 * @package Rediska
 * @subpackage Profiler
 * @version @package_version@
 * @link http://rediska.geometria-lab.net
 * @license http://www.opensource.org/licenses/bsd-license.php
 */
class Rediska_Profiler implements Rediska_Profiler_Interface,
                                  IteratorAggregate,
                                  Countable
{
    /**
     * Profiles
     *
     * @var array
     */
    protected $_profiles = array();

    /**
     * Start profile
     *
     * @param mixed $context
     * @return Rediska_Profiler_Profile
     */
    public function start($context)
    {
        $profile = new Rediska_Profiler_Profile($this, $context);
        $profile->start();

        $this->_profiles[] = $profile;

        return $profile;
    }

    /**
     * Stop profile
     *
     * @return Rediska_Profiler_Profile
     */
    public function stop()
    {
        $hasUnstopped = false;
        foreach(array_reverse($this->_profiles) as $profile) {
            if (!$profile->hasStopped()) {
                $hasUnstopped = true;
                break;
            }
        }
        if ($hasUnstopped) {
            $profile->stop();
        } else {
            throw new Rediska_Profiler_Exception('You need start profiler before stop it');
        }

        return $profile;
    }

    /**
     * Reset profiler
     *
     * @return Rediska_Profiler
     */
    public function reset()
    {
        $this->_profiles = array();

        return $this;
    }

    /**
     * Get profiles
     *
     * @return array
     */
    public function getProfiles()
    {
        return $this->_profiles;
    }

    /**
     * Get total elapsed time
     *
     * @param integer[optional] $decimals
     * @return integer
     */
    public function getTotalElapsedTime($decimals = null)
    {
        $totalElapsedTime = 0;
        foreach ($this->getProfiles() as $profile) {
            if ($profile->hasStopped()) {
                $totalElapsedTime += $profile->getElapsedTime();
            }
        }

        if ($decimals) {
            return number_format($totalElapsedTime, $decimals);
        } else {
            return $totalElapsedTime;
        }
    }

    /**
     * Start callback. Called from profile
     *
     * @param Rediska_Profiler_Profile $profile
     */
    public function startCallBack(Rediska_Profiler_Profile $profile)
    {

    }

    /**
     * Stop callback. Called from profile
     *
     * @param Rediska_Profiler_Profile $profile
     */
    public function stopCallback(Rediska_Profiler_Profile $profile)
    {

    }

    /**
     * Get iterator. Implements IteratorAggregate interface
     *
     * @return ArrayObject
     */
    public function getIterator()
    {
        return new ArrayObject($this->_profiles);
    }

    /**
     * Get profiles count. Implements Countable interface
     *
     * @return integer
     */
    public function count()
    {
        return count($this->_profiles);
    }

    /**
     * Magic to string
     *
     * @return string
     */
    public function  __toString()
    {
        return count($this) . ' profiles => ' . $this->getTotalElapsedTime(4);
    }
}


<?php

/**
 * @author Ivan Shumkov
 * @package Rediska
 * @subpackage Key distributor
 * @version @package_version@
 * @link http://rediska.geometria-lab.net
 * @license http://www.opensource.org/licenses/bsd-license.php
 */
class Rediska_KeyDistributor_Crc32 implements Rediska_KeyDistributor_Interface
{
    protected $_connections = array();

    protected $_connectionCount = 0;

    protected $_connectionPositions = array();

    protected $_connectionPositionCount = 0;

    /**
     * (non-PHPdoc)
     * @see Rediska_KeyDistributor_Interface#addConnection
     */
    public function addConnection($connectionString, $weight = Rediska_Connection::DEFAULT_WEIGHT)
    {
        if (in_array($connectionString, $this->_connections)) {
            throw new Rediska_KeyDistributor_Exception("Connection '$connectionString' already exists.");
        }

        $this->_connections[] = $connectionString;
        $this->_connectionCount++;

        // add connection positions
        for ($index = 0; $index < $weight; $index++) {
            $this->_connectionPositions[] = $connectionString;
            $this->_connectionPositionCount++;
        }

        return $this;
    }

    /**
     * (non-PHPdoc)
     * @see Rediska_KeyDistributor_Interface#removeConnection
     */
    public function removeConnection($connectionString)
    {
        if (!in_array($connectionString, $this->_connections)) {
            throw new Rediska_KeyDistributor_Exception("Connection '$connectionString' does not exist.");
        }

        $index = array_search($connectionString, $this->_connections);
        unset($this->_connections[$index]);
        $this->_connectionCount--;

        // remove connection positions
        $connectionPositions = $this->_connectionPositions;
        $this->_connectionPositions = array();
        $this->_connectionPositionCount = 0;
        foreach($connectionPositions as $connection) {
            if ($connection != $connectionString) {
                $this->_connectionPositions[] = $connection;
                $this->_connectionPositionCount++;
            }
        }

        return $this;
    }

    /**
     * (non-PHPdoc)
     * @see Rediska_KeyDistributor_Interface#getConnectionByKeyName
     */
    public function getConnectionByKeyName($name)
    {
        if (empty($this->_connections)) {
            throw new Rediska_KeyDistributor_Exception("No connection exists.");
        }

        if ($this->_connectionCount == 1) {
            return $this->_connections[0];
        }

        $index = abs(crc32($name) % $this->_connectionPositionCount);

        return $this->_connectionPositions[$index];
    }
}


<?php

// Require Rediska
require_once dirname(__FILE__) . '/../../Rediska.php';

/**
 * Rediska hash key
 *
 * @author Ivan Shumkov
 * @package Rediska
 * @subpackage Key objects
 * @version @package_version@
 * @link http://rediska.geometria-lab.net
 * @license http://www.opensource.org/licenses/bsd-license.php
 */
class Rediska_Key_Hash extends Rediska_Key_Abstract implements IteratorAggregate, ArrayAccess, Countable
{
    /**
     * Construct key
     *
     * @param string                    $name        Key name
     * @param integer                   $options     Options:
     *                                                  expire            - Expire time
     *                                                  expireIsTimestamp - Expire time is timestamp. For default false (in seconds)
     *                                                  serverAlias       - Server alias or connection object
     *                                                  rediska           - Rediska instance name, Rediska object or Rediska options for new instance
     * @param string|Rediska_Connection $serverAlias Server alias or Rediska_Connection object where key is placed. Deprecated!
     */
    public function  __construct($name, $options = array(), $serverAlias = null)
    {
        parent::__construct($name, $options, $serverAlias);

        $this->_throwIfNotSupported();
    }

    /**
     * Set value to a hash field or fields
     *
     * @param array|string  $fieldOrData  Field or array of many fields and values: field => value
     * @param mixed         $value        Value for single field
     * @param boolean       $overwrite    Overwrite for single field (if false don't set and return false if key already exist). For default true.
     * @return boolean
     */
    public function set($fieldOrData, $value = null, $overwrite = true)
    {
        $result = $this->_getRediskaOn()->setToHash($this->getName(), $fieldOrData, $value, $overwrite);

        if (!is_null($this->getExpire()) && ((!$overwrite && $result) || ($overwrite))) {
            $this->expire($this->getExpire(), $this->isExpireTimestamp());
        }

        return $result;
    }

    /**
     * Magic for set a field
     *
     * @param string $field
     * @param mixed  $value
     * @return boolean
     */
    public function  __set($field, $value)
    {
        $this->set($field, $value);

        return $value;
    }

    /**
     * Array magic for set a field
     *
     * @param string $field
     * @param mixed $value
     * @return boolean
     */
    public function offsetSet($field, $value)
    {
        if (is_null($field)) {
            throw new Rediska_Key_Exception('Field must be present');
        }

        $this->set($field, $value);

        return $value;
    }

    /**
     * Get value from hash field or fields
     *
     * @param string       $name          Key name
     * @param string|array $fieldOrFields Field or fields
     * @return mixed
     */
    public function get($fieldOrFields)
    {
        return $this->_getRediskaOn()->getFromHash($this->getName(), $fieldOrFields);
    }

    /**
     * Magic for get a field
     *
     * @param string $field
     * @return mixed
     */
    public function  __get($field)
    {
        return $this->get($field);
    }

    /**
     * Array magic for get a field
     *
     * @param string $name
     * @return mixed
     */
    public function offsetGet($field)
    {
        return $this->get($field);
    }

    /**
     * Increment field value in hash
     *
     * @param mixed  $field            Field
     * @param number $amount[optional] Increment amount. Default: 1
     * @return integer
     */
    public function increment($field, $amount = 1)
    {
        $result = $this->_getRediskaOn()->incrementInHash($this->getName(), $field, $amount);

        if (!is_null($this->getExpire()) && $result) {
            $this->expire($this->getExpire(), $this->isExpireTimestamp());
        }

        return $result;
    }

    /**
     * Test if field is present in hash
     *
     * @param mixed  $field Field
     * @return boolean
     */
    public function exists($field)
    {
        return $this->_getRediskaOn()->existsInHash($this->getName(), $field);
    }

    /**
     * Magic for test if field is present in hash
     *
     * @param string $field
     * @return boolean
     */
    public function  __isset($field)
    {
        return $this->exists($field);
    }

    /**
     * Array magic for test if field is present in hash
     *
     * @param string $field
     * @return boolean
     */
    public function offsetExists($field)
    {
        return $this->exists($field);
    }

    /**
     * Remove field from hash
     *
     * @param mixed  $field Field
     * @return boolean
     */
    public function remove($field)
    {
        $result = $this->_getRediskaOn()->deleteFromHash($this->getName(), $field);

        if (!is_null($this->getExpire()) && $result) {
            $this->expire($this->getExpire(), $this->isExpireTimestamp());
        }

        return $result;
    }

    /**
     * Magic for remove field from hash
     *
     * @param string $field
     * @return boolean
     */
    public function  __unset($field)
    {
        return $this->remove($field);
    }

    /**
     * Array magic for remove field from hash
     *
     * @param string $field
     * @return boolean
     */
    public function offsetUnset($field)
    {
        return $this->remove($field);
    }

    /**
     * Get hash fields
     *
     * @return array
     */
    public function getFields()
    {
        return $this->_getRediskaOn()->getHashFields($this->getName());
    }

    /**
     * Get hash values
     *
     * @return array
     */
    public function getValues()
    {
        return $this->_getRediskaOn()->getHashValues($this->getName());
    }

    /**
     * Get hash fields and values
     *
     * @return array
     */
    public function getFieldsAndValues()
    {
        return $this->_getRediskaOn()->getHash($this->getName());
    }

    /**
     * Get hash length
     *
     * @return intger
     */
    public function getLength()
    {
        return $this->_getRediskaOn()->getHashLength($this->getName());
    }

    /**
     * Get hash as array
     *
     * @return array
     */
    public function toArray()
    {
        return $this->getFieldsAndValues();
    }

    /* Countable implementation */

    public function count()
    {
        return $this->getLength();
    }

    /* IteratorAggregate implementation */

    public function getIterator()
    {
        return new ArrayObject($this->toArray());
    }

    /**
     * Throw if PubSub not supported by Redis
     */
    protected function _throwIfNotSupported()
    {
        $version = '1.3.10';
        $redisVersion = $this->getRediska()->getOption('redisVersion');
        if (version_compare($version, $this->getRediska()->getOption('redisVersion')) == 1) {
            throw new Rediska_PubSub_Exception("Publish/Subscribe requires {$version}+ version of Redis server. Current version is {$redisVersion}. To change it specify 'redisVersion' option.");
        }
    }
}


<?php

// Require Rediska
require_once dirname(__FILE__) . '/../../../../Rediska.php';

/**
 * @see Zend_Auth_Adapter_Interface
 */
require_once 'Zend/Auth/Adapter/Interface.php';

/**
 * @see Zend_Auth_Result
 */
require_once 'Zend/Auth/Result.php';

/**
 * @see Zend_Auth_Adapter_Exception
 */
require_once 'Zend/Auth/Adapter/Exception.php';

/**
 * @see Zend_Config
 */
require_once 'Zend/Config.php';

/**
 * Redis adapter for Zend_Auth
 *
 * @author Ivan Shumkov
 * @package Rediska
 * @subpackage ZendFrameworkIntegration
 * @version @package_version@
 * @link http://rediska.geometria-lab.net
 * @license http://www.opensource.org/licenses/bsd-license.php
 */
class Rediska_Zend_Auth_Adapter_Redis extends Rediska_Options_RediskaInstance implements Zend_Auth_Adapter_Interface
{
    /**
     * User identity
     *
     * @var string
     */
    protected $_identity;

    /**
     * User credential
     *
     * @var string
     */
    protected $_credential;

    /**
     * User data
     *
     * @var array|object
     */
    protected $_userData;

    /**
     * Exception class name for options
     *
     * @var string
     */
    protected $_optionsException = 'Zend_Auth_Adapter_Exception';

    /**
     * Configuration
     *
     * userIdKey               - Redis key where you store relation between login and id. '*' replaced to identity (login)
     * userDataKey             - Redis key where you store user data
     * credentialAttributeName - Name of credential (password) attribute in user data
     * userDataIsArray         - Set true if you store user data in associative array
     * identity                - User identity (login) for authorization
     * credential              - User credintial (password) for authorization
     * rediska                 - Rediska instance name, Rediska object or array of options
     *
     * @var array
     */
    protected $_options = array(
        'userIdKey'               => 'user_ids:*',
        'userDataKey'             => 'users:*',
        'credentialAttributeName' => 'password',
        'userDataIsArray'         => false,
    );

    /**
     * Set identity (login)
     *
     * @param string $credential Identity (login)
     * @return Rediska_Zend_Auth_Adapter_Redis
     */
    public function setIdentity($identity)
    {
        $this->_identity = $identity;

        return $this;
    }

    /**
     * Get identity (login)
     *
     * @return string
     */
    public function getIdentity()
    {
        return $this->_identity;
    }

    /**
     * Set credential (password)
     *
     * @param string $credential Credential (password)
     * @return Rediska_Zend_Auth_Adapter_Redis
     */
    public function setCredential($credential)
    {
        $this->_credential = $credential;

        return $this;
    }

    /**
     * Get credential (password)
     *
     * @return string
     */
    public function getCredential()
    {
        return $this->_credential;
    }

    /**
     * Get result user data
     *
     * @return object|array
     */
    public function getResultUserData()
    {
        return $this->_userData;
    }

    /**
     * (non-PHPdoc)
     * @see Zend_Auth_Adapter_Interface#authenticate()
     */
    public function authenticate()
    {
        $identity = $this->getIdentity();

        $userIdKey = str_replace('*', $identity, $this->getOption('userIdKey'));

        $userId = $this->getRediska()->get($userIdKey);

        if (is_null($userId)) {
            $code    = Zend_Auth_Result::FAILURE_IDENTITY_NOT_FOUND;
            $message = 'User with the supplied identity could not be found.';
        } else {
            $userDataKey = str_replace('*', $userId, $this->getOption('userDataKey'));

            $userData = $this->getRediska()->get($userDataKey);

            if (is_null($userData)) {
                throw new Zend_Auth_Adapter_Exception("User data key '$userDataKey' not found");
            }

            $credentialAttributeName = $this->getOption('credentialAttributeName');
            if ($this->getOption('userDataIsArray')) {
                if (!array_key_exists($credentialAttributeName, $userData)) {
                    throw new Zend_Auth_Adapter_Exception("Credential key with name '$credentialAttributeName' not found in user data");
                }
                $credential = $userData[$credentialAttributeName];
            } else {
                if (!isset($userData->$credentialAttributeName)) {
                    throw new Zend_Auth_Adapter_Exception("Credential attribute with name '$credentialAttributeName' not found in user data");
                }
                $credential = $userData->$credentialAttributeName;
            }

            if ($this->getCredential() == $credential) {
                $code     = Zend_Auth_Result::SUCCESS;
                $message  = 'Authentication successful.';
                $this->_userData = $userData;
            } else {
                $code     = Zend_Auth_Result::FAILURE_CREDENTIAL_INVALID;
                $message  = 'Supplied credential is invalid.';
            }
        }

        return new Zend_Auth_Result($code, $identity, array($message));
    }
}


<?php

/**
 * Get sorted elements contained in the List, Set, or Sorted Set value at key.
 *
 * @author Ivan Shumkov
 * @package Rediska
 * @subpackage Commands
 * @version @package_version@
 * @link http://rediska.geometria-lab.net
 * @license http://www.opensource.org/licenses/bsd-license.php
 */
class Rediska_Command_Sort extends Rediska_Command_Abstract
{
    const ASC  = 'asc';
    const DESC = 'desc';

    protected $_options = array(
        'order'  => self::ASC,
        'limit'  => null,
        'offset' => null,
        'alpha'  => false,
        'by'     => null,
        'get'    => null,
        'store'  => null,
    );

    /**
     * Create command
     *
     * @param string        $key   Key name
     * @param array         $value Options:
     *                               * order
     *                               * limit
     *                               * offset
     *                               * alpha
     *                               * get
     *                               * by
     *                               * store
     *
     *                              See more: http://code.google.com/p/redis/wiki/SortCommand

     *                              If you use more then one connection to Redis servers,
     *                              it will choose by key name, and key by you pattern's may not present on it.
     *
     * @return Rediska_Connection_Exec
     */
    public function create($key, array $options = array())
    {
        $connection = $this->_rediska->getConnectionByKeyName($key);

        $command = array('SORT',
                         $this->_rediska->getOption('namespace') . $key);

        foreach($options as $name => $value) {
            if (!array_key_exists($name, $this->_options)) {
                throw new Rediska_Command_Exception("Unknown option '$name'");
            }
            $this->_options[$name] = $value;
        }

        // Limit
        if (isset($this->_options['limit'])) {
            $offset = isset($this->_options['offset']) ? $this->_options['offset'] : 0;
            $command[] = 'LIMIT';
            $command[] = $offset;
            $command[] = $this->_options['limit'];
        }

        // Alpha
        if ($this->_options['alpha']) {
            $command[] = 'ALPHA';
        }

        // Order
        if (isset($this->_options['order'])) {
            $command[] = strtoupper($this->_options['order']);
        }

        // By
        if (isset($this->_options['by'])) {
            $this->_throwExceptionIfManyConnections('by');
            $command[] = 'BY';
            $command[] = $this->_rediska->getOption('namespace') . $this->_options['by'];
        }

        // Get
        if (isset($this->_options['get'])) {
            $this->_throwExceptionIfManyConnections('get');
            if (!is_string($this->_options['get'])) {
                foreach($this->_options['get'] as $pattern) {
                    $command[] = 'GET';
                    $command[] = $this->_addNamespaceToGetIfNeeded($pattern);
                }
            } else {
                $command[] = 'GET';
                $command[] = $this->_addNamespaceToGetIfNeeded($this->_options['get']);
            }
        }

        // Store
        if (isset($this->_options['store'])) {
            $this->_throwExceptionIfManyConnections('store');
            $command[] = 'STORE';
            $command[] = $this->_rediska->getOption('namespace') . $this->_options['store'];
        }

        return new Rediska_Connection_Exec($connection, $command);
    }

    /**
     * Parse response
     *
     * @param array $response
     * @return array
     */
    public function parseResponse($response)
    {
        return array_map(array($this->_rediska->getSerializer(), 'unserialize'), $response);
    }

    protected function _addNamespaceToGetIfNeeded($pattern)
    {
        if ($pattern != '#') {
            $pattern = $this->_rediska->getOption('namespace') . $pattern;
        } else {
            $this->_throwExceptionIfNotSupported('1.1');
        }

        return $pattern;
    }

    protected function _throwExceptionIfManyConnections($argument)
    {
        $connections = $this->_rediska->getConnections();
        if (count($connections) > 1) {
            throw new Rediska_Command_Exception("You can use '$argument' with only one connection. Use 'on' method to specify it.");
        }
    }
}


<?php

// Require Rediska
require_once dirname(__FILE__) . '/../../../../Rediska.php';

/**
 * @see Zend_Session
 */
require_once 'Zend/Session.php';

/**
 * @see Zend_Config
 */
require_once 'Zend/Config.php';

/**
 * @see Zend_Session_SaveHandler_Interface
 */
require_once 'Zend/Session/SaveHandler/Interface.php';

/**
 * @see Zend_Session_SaveHandler_Exception
 */
require_once 'Zend/Session/SaveHandler/Exception.php';

/**
 * Redis save handler for Zend_Session
 *
 * @author Ivan Shumkov
 * @package Rediska
 * @subpackage ZendFrameworkIntegration
 * @version @package_version@
 * @link http://rediska.geometria-lab.net
 * @license http://www.opensource.org/licenses/bsd-license.php
 */
class Rediska_Zend_Session_SaveHandler_Redis extends Rediska_Options_RediskaInstance implements Zend_Session_SaveHandler_Interface
{
    /**
     * Sessions set
     *
     * @var Rediska_Zend_Session_Set
     */
    protected $_set;

    /**
     * Configuration
     *
     * @var array
     */
    protected $_options = array(
        'keyPrefix' => 'PHPSESSIONS_',
        'lifetime'  => null,
    );

    /**
     * Exception class name for options
     *
     * @var string
     */
    protected $_optionsException = 'Zend_Session_SaveHandler_Exception';

    /**
     * Construct save handler
     *
     * @param Zend_Config|array $options
     */
    public function __construct($options = array())
    {
        if ($options instanceof Zend_Config) {
            $options = $options->toArray();
        }

        // Set default lifetime
        if (!isset($options['lifetime'])) {
            $lifetime = (int)ini_get('session.gc_maxlifetime');

            if ($lifetime != 0) {
                $options['lifetime'] = $lifetime;
            } else {
                trigger_error(
                    "Please set session.gc_maxlifetime to enable garbage collection.",
                    E_USER_WARNING
                );
            }
        }

        parent::__construct($options);

        Rediska_Zend_Session_Set::setSaveHandler($this);
        $this->_set = new Rediska_Zend_Session_Set();
    }

    /**
     * Open Session
     *
     * @param string $save_path
     * @param string $name
     * @return boolean
     */
    public function open($save_path, $name)
    {
        return true;
    }

    /**
     * Close session
     *
     * @return boolean
     */
    public function close()
    {
        return true;
    }

    /**
     * Read session data
     *
     * @param string $id
     * @return string
     */
    public function read($id)
    {
        return $this->getRediska()->get($this->_getKeyName($id));
    }

    /**
     * Write session data
     *
     * @param string $id
     * @param string $data
     * @return boolean
     */
    public function write($id, $data)
    {
        try {
            $timestamp = time();
            $this->_set[$timestamp] = $id;
        } catch (Rediska_Connection_Exec_Exception $e) {
            $this->_deleteSetOrThrowException($e);
        }

        return $this->getRediska()->setAndExpire(
            $this->_getKeyName($id),
            $data,
            $this->getOption('lifetime')
        );
    }

    /**
     * Destroy session
     *
     * @param string $id
     * @return boolean
     */
    public function destroy($id)
    {
        try {
            $this->_set->remove($id);
        } catch(Rediska_Connection_Exec_Exception $e) {
            $this->_deleteSetOrThrowException($e);
        }

        return $this->getRediska()->delete($this->_getKeyName($id));
    }

    /**
     * Garbage Collection
     *
     * @param int $maxlifetime
     * @return true
     */
    public function gc($maxlifetime)
    {
        try {
            return $this->_set->removeByScore(0, time() - $this->getOption('lifetime'));
        } catch(Rediska_Connection_Exec_Exception $e) {
            $this->_deleteSetOrThrowException($e);
        }
    }

    /**
     * Add prefix to session name
     * @param string $id
     * @return string
     */
    protected function _getKeyName($id)
    {
        return $this->getOption('keyPrefix') . $id;
    }

    /**
     * Delete old set or throw exception
     *
     * @throws Rediska_Connection_Exec_Exception
     * @param Rediska_Connection_Exec_Exception $e
     * @return void
     */
    protected function _deleteSetOrThrowException(Rediska_Connection_Exec_Exception $e)
    {
        if ($e->getMessage() == 'Operation against a key holding the wrong kind of value') {
            $this->_set->delete();
        } else {
            throw $e;
        }
    }

    /**
     * Destructor
     *
     * @return void
     */
    public function __destruct()
    {
        Zend_Session::writeClose();
    }
}