<?php
/*
 * $RCSfile: GalleryStorage.class,v $
 *
 * Gallery - a web based photo album viewer and editor
 * Copyright (C) 2000-2005 Bharat Mediratta
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA  02110-1301, USA.
 */
/**
 * @version $Revision: 1.59 $ $Date: 2005/08/23 03:49:03 $
 * @package GalleryCore
 * @author Bharat Mediratta <bharat@menalto.com>
 */

/**
 * Interface to the storage mechanism
 *
 * This object provides the hooks for saving and restoring objects in the persistent store.
 *
 * @package GalleryCore
 * @subpackage Classes
 * @abstract
 */
class GalleryStorage {

    /**
     * Our encapsulated storage implementation
     *
     * @var object GalleryStorage $_impl
     * @access private
     */
    var $_impl;

    /**
     * Have we initialized the connection yet?
     *
     * @var bool $_isInitialized
     * @access private
     */
    var $_isInitialized;

    /**
     * Constructor
     */
    function GalleryStorage() {
	global $gallery;

	/* Initialize our Gallery map and class info caches */
	$this->_entityInfoCache = array();
	$this->_mapInfoCache = array();

	$base = '/GalleryStorage/';
	GalleryCoreApi::relativeRequireOnce(
	    'modules/core/classes' . $base . 'DatabaseStorage.class');
	GalleryCoreApi::relativeRequireOnce(
	    'modules/core/classes' . $base . 'DatabaseSearchResults.class');

	$config = $gallery->getConfig('storage.config');
	switch ($config['type']) {
	case 'postgres':
	case 'postgres7':
	    GalleryCoreApi::relativeRequireOnce(
		'modules/core/classes' . $base . 'DatabaseStorage/PostgreSqlDatabaseStorage.class');
	    $this->_impl = new PostgreSqlDatabaseStorage($config);
	    break;

	default:
	case 'mysql':
	case 'mysqlt':
	    GalleryCoreApi::relativeRequireOnce(
		'modules/core/classes' . $base . 'DatabaseStorage/MySqlDatabaseStorage.class');
	    $this->_impl = new MySqlDatabaseStorage($config);
	    break;

	case 'oci8':
	case 'oci805':
	case 'oci8po':
	case 'oracle':
	    GalleryCoreApi::relativeRequireOnce(
		'modules/core/classes' . $base . 'DatabaseStorage/OracleDatabaseStorage.class');
	    $this->_impl = new OracleDatabaseStorage($config);
	    break;
	}

	$this->_isInitialized = false;
    }

    /**
     * Load the GalleryEntities with the ids specified
     *
     * @param mixed the ids of the GalleryEntities to load
     * @return array object GalleryStatus a status code,
     *               mixed one GalleryEntity or an array of GalleryEntities
     */
    function loadEntities($ids) {
	if (!$this->_isInitialized) {
	    $ret = $this->_impl->init();
	    if ($ret->isError()) {
		return array($ret->wrap(__FILE__, __LINE__), null);
	    } else {
		$this->_isInitialized = true;
	    }
	}

	list ($ret, $entities) = $this->_impl->loadEntities($ids);
	if ($ret->isError()) {
	    return array($ret->wrap(__FILE__, __LINE__), null);
	}
	return array(GalleryStatus::success(), $entities);
    }

    /**
     * Save the changes to the GalleryEntity.
     *
     * @access public
     * @param object GalleryEntity the GalleryEntity to save
     * @return object GalleryStatus a status code
     */
    function saveEntity(&$object) {
	if (!$this->_isInitialized) {
	    $ret = $this->_impl->init();
	    if ($ret->isError()) {
		return $ret->wrap(__FILE__, __LINE__);
	    } else {
		$this->_isInitialized = true;
	    }
	}

	$ret = $this->_impl->saveEntity($object);
	if ($ret->isError()) {
	    return $ret->wrap(__FILE__, __LINE__);
	}
	return GalleryStatus::success();
    }

    /**
     * Delete the GalleryEntity.
     *
     * @access public
     * @param object GalleryEntity the GalleryEntity to delete
     * @return object GalleryStatus a status code
     */
    function deleteEntity($object) {
	if (!$this->_isInitialized) {
	    $ret = $this->_impl->init();
	    if ($ret->isError()) {
		return $ret->wrap(__FILE__, __LINE__);
	    } else {
		$this->_isInitialized = true;
	    }
	}

	$ret = $this->_impl->deleteEntity($object);
	if ($ret->isError()) {
	    return $ret->wrap(__FILE__, __LINE__);
	}
	return GalleryStatus::success();
    }

    /**
     * Create a new GalleryEntity
     *
     * @access public
     * @param object GalleryEntity the GalleryEntity to put the data in
     * @return object GalleryStatus a status code
     */
    function newEntity(&$object) {
	if (!$this->_isInitialized) {
	    $ret = $this->_impl->init();
	    if ($ret->isError()) {
		return $ret->wrap(__FILE__, __LINE__);
	    } else {
		$this->_isInitialized = true;
	    }
	}

	$ret = $this->_impl->newEntity($object);
	if ($ret->isError()) {
	    return $ret->wrap(__FILE__, __LINE__);
	}
	return GalleryStatus::success();
    }

    /**
     * Refresh a GalleryEntity
     *
     * Refresh a GalleryEntity from the database, if it has changed.
     *
     * @access public
     * @param object GalleryEntity the object to refresh
     * @return array object GalleryStatus a status code,
     *               object GalleryEntity the fresh entity
     */
    function refreshEntity($object) {
	if (!$this->_isInitialized) {
	    $ret = $this->_impl->init();
	    if ($ret->isError()) {
		return array($ret->wrap(__FILE__, __LINE__), null);
	    } else {
		$this->_isInitialized = true;
	    }
	}

	list ($ret, $entity) = $this->_impl->refreshEntity($object);
	if ($ret->isError()) {
	    return array($ret->wrap(__FILE__, __LINE__), null);
	}
	return array(GalleryStatus::success(), $entity);
    }

    /**
     * Search the persistent store for the target values matching the given
     * criteria
     *
     * @access public
     * @param array the search query
     * @param array any explicit data values required by the query
     * @param array optional arguments (eg, limits)
     * @return array object GalleryStatus a status code,
     *               array the result values
     */
    function search($query, $data=array(), $optional=array()) {
	if (!$this->_isInitialized) {
	    $ret = $this->_impl->init();
	    if ($ret->isError()) {
		return array($ret->wrap(__FILE__, __LINE__), null);
	    } else {
		$this->_isInitialized = true;
	    }
	}

	list ($ret, $results) = $this->_impl->search($query, $data, $optional);
	if ($ret->isError()) {
	    return array($ret->wrap(__FILE__, __LINE__), null);
	}
	return array(GalleryStatus::success(), $results);
    }

    /**
     * Execute a database statement
     *
     * @access public
     * @param array the SQL statement
     * @param array any explicit data values required by the query
     * @return object GalleryStatus a status code,
     */
    function execute($statement, $data=array()) {
	if (!$this->_isInitialized) {
	    $ret = $this->_impl->init();
	    if ($ret->isError()) {
		return $ret->wrap(__FILE__, __LINE__);
	    } else {
		$this->_isInitialized = true;
	    }
	}

	$ret = $this->_impl->execute($statement, $data);
	if ($ret->isError()) {
	    return $ret->wrap(__FILE__, __LINE__);
	}
	return GalleryStatus::success();
    }

    /**
     * Add a new entry to a map
     *
     * @param object the map we're working on
     * @param array an associative array of data about the entry
     * @return object GalleryStatus a status code
     */
    function addMapEntry($map, $entry) {
	if (!$this->_isInitialized) {
	    $ret = $this->_impl->init();
	    if ($ret->isError()) {
		return $ret->wrap(__FILE__, __LINE__);
	    } else {
		$this->_isInitialized = true;
	    }
	}

	$ret = $this->_impl->addMapEntry($map, $entry);
	if ($ret->isError()) {
	    return $ret->wrap(__FILE__, __LINE__);
	}
	return GalleryStatus::success();
    }

    /**
     * Remove an entry from a map
     *
     * @param object the map we're working on
     * @param array an associative array of data about the entry
     * @return object GalleryStatus a status code
     */
    function removeMapEntry($map, $entry) {
	if (!$this->_isInitialized) {
	    $ret = $this->_impl->init();
	    if ($ret->isError()) {
		return $ret->wrap(__FILE__, __LINE__);
	    } else {
		$this->_isInitialized = true;
	    }
	}

	$ret = $this->_impl->removeMapEntry($map, $entry);
	if ($ret->isError()) {
	    return $ret->wrap(__FILE__, __LINE__);
	}
	return GalleryStatus::success();
    }

    /**
     * Remove ALL entries from a map.. use with caution!
     *
     * @param object the map we're working on
     * @return object GalleryStatus a status code
     */
    function removeAllMapEntries($map) {
	if (!$this->_isInitialized) {
	    $ret = $this->_impl->init();
	    if ($ret->isError()) {
		return $ret->wrap(__FILE__, __LINE__);
	    } else {
		$this->_isInitialized = true;
	    }
	}

	$ret = $this->_impl->removeAllMapEntries($map);
	if ($ret->isError()) {
	    return $ret->wrap(__FILE__, __LINE__);
	}
	return GalleryStatus::success();
    }

    /**
     * Update an entry in this map
     *
     * @param array the entry to match
     * @param array the values to change
     * @return object GalleryStatus a status code
     */
    function updateMapEntry($map, $match, $change) {
	if (!$this->_isInitialized) {
	    $ret = $this->_impl->init();
	    if ($ret->isError()) {
		return $ret->wrap(__FILE__, __LINE__);
	    } else {
		$this->_isInitialized = true;
	    }
	}

	$ret = $this->_impl->updateMapEntry($map, $match, $change);
	if ($ret->isError()) {
	    return $ret->wrap(__FILE__, __LINE__);
	}
	return GalleryStatus::success();
    }

    /**
     * Configure the persistent store for this strategy, for the given module.
     *
     * @return object GalleryStatus a status code
     */
    function configureStore($moduleId) {
	if (!$this->_isInitialized) {
	    $ret = $this->_impl->init();
	    if ($ret->isError()) {
		return $ret->wrap(__FILE__, __LINE__);
	    } else {
		$this->_isInitialized = true;
	    }
	}

	$ret = $this->_impl->configureStore($moduleId);
	if ($ret->isError()) {
	    return $ret->wrap(__FILE__, __LINE__);
	}
	return GalleryStatus::success();
    }

    /**
     * Perform any cleanup necessary after installing or upgrading the given module.
     *
     * @return object GalleryStatus a status code
     */
    function configureStoreCleanup($moduleId) {
	if (!$this->_isInitialized) {
	    $ret = $this->_impl->init();
	    if ($ret->isError()) {
		return $ret->wrap(__FILE__, __LINE__);
	    } else {
		$this->_isInitialized = true;
	    }
	}

	$ret = $this->_impl->configureStoreCleanup($moduleId);
	if ($ret->isError()) {
	    return $ret->wrap(__FILE__, __LINE__);
	}
	return GalleryStatus::success();
    }

    /**
     * Uninstall the database schema for the given module
     *
     * @return object GalleryStatus a status code
     */
    function unconfigureStore($moduleId) {
	if (!$this->_isInitialized) {
	    $ret = $this->_impl->init();
	    if ($ret->isError()) {
		return $ret->wrap(__FILE__, __LINE__);
	    } else {
		$this->_isInitialized = true;
	    }
	}

	$ret = $this->_impl->unconfigureStore($moduleId);
	if ($ret->isError()) {
	    return $ret->wrap(__FILE__, __LINE__);
	}
	return GalleryStatus::success();
    }

    /**
     * Clean out and reset the persistent store for this strategy.
     *
     * @return object GalleryStatus a status code
     */
    function cleanStore() {
	if (!$this->_isInitialized) {
	    $ret = $this->_impl->init();
	    if ($ret->isError()) {
		return $ret->wrap(__FILE__, __LINE__);
	    } else {
		$this->_isInitialized = true;
	    }
	}

	$ret = $this->_impl->cleanStore();
	if ($ret->isError()) {
	    return $ret->wrap(__FILE__, __LINE__);
	}
	return GalleryStatus::success();
    }

    /**
     * Begin a new transaction, if the storage layer supports them.
     *
     * @return object GalleryStatus a status code
     */
    function beginTransaction() {
	if (!$this->_isInitialized) {
	    $ret = $this->_impl->init();
	    if ($ret->isError()) {
		return $ret->wrap(__FILE__, __LINE__);
	    } else {
		$this->_isInitialized = true;
	    }
	}

	$ret = $this->_impl->beginTransaction();
	if ($ret->isError()) {
	    return $ret->wrap(__FILE__, __LINE__);
	}
	return GalleryStatus::success();
    }

    /**
     * Commit our transaction, if the storage layer supports them.
     *
     * @return object GalleryStatus a status code
     */
    function commitTransaction() {
	if (!$this->_isInitialized) {
	    $ret = $this->_impl->init();
	    if ($ret->isError()) {
		return $ret->wrap(__FILE__, __LINE__);
	    } else {
		$this->_isInitialized = true;
	    }
	}

	$ret = $this->_impl->commitTransaction();
	if ($ret->isError()) {
	    return $ret->wrap(__FILE__, __LINE__);
	}
	return GalleryStatus::success();
    }

    /**
     * Roll back our transaction, if the storage layer supports them.
     *
     * @return object GalleryStatus a status code
     */
    function rollbackTransaction() {
	if (!$this->_isInitialized) {
	    $ret = $this->_impl->init();
	    if ($ret->isError()) {
		return $ret->wrap(__FILE__, __LINE__);
	    } else {
		$this->_isInitialized = true;
	    }
	}

	$ret = $this->_impl->rollbackTransaction();
	if ($ret->isError()) {
	    return $ret->wrap(__FILE__, __LINE__);
	}
	return GalleryStatus::success();
    }

    /**
     * Mark a storage checkpoint, which will commit pending transactions
     * and perform any future tied-tasks
     *
     * @return object GalleryStatus a status code
     */
    function checkPoint() {
	if (!$this->_isInitialized) {
	    return GalleryStatus::success();
	}

	$ret = $this->_impl->commitTransaction();
	if ($ret->isError()) {
	    return $ret;
	}

	$ret = GalleryStatus::success();
	if ($ret->isError()) {
	    return $ret->wrap(__FILE__, __LINE__);
	}
	return GalleryStatus::success();
    }

    /**
     * Acquire read locks on the given items
     *
     * @access public
     * @param array entityIds list of entityIds
     * @param int timeout before giving up on the lock
     * @return array object GalleryStatus a status code
     *               object a GalleryLock instance
     */
    function acquireReadLock($entityIds, $timeout) {
	if (!$this->_isInitialized) {
	    $ret = $this->_impl->init();
	    if ($ret->isError()) {
		return array($ret->wrap(__FILE__, __LINE__), null);
	    } else {
		$this->_isInitialized = true;
	    }
	}

	list ($ret, $lock) = $this->_impl->acquireReadLock($entityIds, $timeout);
	if ($ret->isError()) {
	    return array($ret->wrap(__FILE__, __LINE__), null);
	}

	return array(GalleryStatus::success(), $lock);
    }

    /**
     * Acquire write locks on the given items
     *
     * @access public
     * @param array or integer a set of ids
     * @param int timeout before giving up on the lock
     * @return array object GalleryStatus a status code
     *               object a GalleryLock instance
     */
    function acquireWriteLock($entityIds, $timeout) {
	if (!$this->_isInitialized) {
	    $ret = $this->_impl->init();
	    if ($ret->isError()) {
		return array($ret->wrap(__FILE__, __LINE__), null);
	    } else {
		$this->_isInitialized = true;
	    }
	}

	list ($ret, $lock) = $this->_impl->acquireWriteLock($entityIds, $timeout);
	if ($ret->isError()) {
	    return array($ret->wrap(__FILE__, __LINE__), null);
	}

	return array(GalleryStatus::success(), $lock);
    }

    /**
     * Refresh all the locks that we hold so that they aren't accidentally considered expired
     *
     * @param array the lock ids
     * @param int the new "fresh until" timestamp
     * @return object GalleryStatus a status code
     * @static
     */
    function refreshLocks($lockIds, $freshUntil) {
	if (!$this->_isInitialized) {
	    $ret = $this->_impl->init();
	    if ($ret->isError()) {
		return $ret->wrap(__FILE__, __LINE__);
	    } else {
		$this->_isInitialized = true;
	    }
	}

	$ret = $this->_impl->refreshLocks($lockIds, $freshUntil);
	if ($ret->isError()) {
	    return $ret->wrap(__FILE__, __LINE__);
	}
	return GalleryStatus::success();
    }

    /**
     * Internal function to release a lock by id.  Outsiders should use
     * releaseLock()
     *
     * @access private
     * @param int the lock id
     * @return object GalleryStatus a status code
     */
    function releaseLocks($lockIds) {
 	if (!$this->_isInitialized) {
	    $ret = $this->_impl->init();
	    if ($ret->isError()) {
		return $ret->wrap(__FILE__, __LINE__);
	    } else {
		$this->_isInitialized = true;
	    }
	}

	$ret = $this->_impl->releaseLocks($lockIds);
	if ($ret->isError()) {
	    return $ret->wrap(__FILE__, __LINE__);
	}
	return GalleryStatus::success();
    }

    /**
     * Get a new, unique id.
     *
     * @return array object GalleryStatus a status code
     *               int an id
     */
    function getUniqueId() {
 	if (!$this->_isInitialized) {
	    $ret = $this->_impl->init();
	    if ($ret->isError()) {
		return array($ret->wrap(__FILE__, __LINE__), null);
	    } else {
		$this->_isInitialized = true;
	    }
	}

	list ($ret, $id) = $this->_impl->getUniqueId();
	if ($ret->isError()) {
	    return array($ret->wrap(__FILE__, __LINE__), null);
	}
	return array(GalleryStatus::success(), $id);
    }

    /**
     * Convert an integer into something that the database will accept into a
     * bit column
     *
     * @param integer integer value
     * @param mixed bit value
     */
    function convertIntToBits($intVal) {
	return $this->_impl->convertIntToBits($intVal);
    }

    /**
     * Convert an integer into something that the database will accept into a
     * bit column
     *
     * @param integer integer value
     * @param mixed bit value
     */
    function convertBitsToInt($bitsVal) {
	return $this->_impl->convertBitsToInt($bitsVal);
    }

    /**
     * Return a customized function for this database platform
     *
     * @param string the function name
     * @param array mixed the function arguments
     * @return array GalleryStatus a status code
     *               string the function SQL
     */
    function getFunctionSql($functionName, $args) {
	return $this->_impl->getFunctionSql($functionName, $args);
    }

    /**
     * Return storage profiling information in HTML format
     *
     * @return string HTML
     */
    function getProfilingHtml() {
	return $this->_impl->getProfilingHtml();
    }

    /**
     * Return true if enough of this storage system is installed that
     * there'll be a conflict if you try to do another install.
     *
     * @return array object GalleryStatus a status code
     *               boolean true if the tables are installed
     */
    function isInstalled() {
	if (!$this->_isInitialized) {
	    $ret = $this->_impl->init();
	    if ($ret->isError()) {
		return array($ret->wrap(__FILE__, __LINE__), null);
	    } else {
		$this->_isInitialized = true;
	    }
	}

	list ($ret, $isInstalled) = $this->_impl->isInstalled();
	if ($ret->isError()) {
	    return array($ret->wrap(__FILE__, __LINE__), null);
	}
	return array(GalleryStatus::success(), $isInstalled);
    }

    /**
     * Extracts the class names from a given query
     *
     * Query should be something like
     * '[GalleryItem::id] = ? AND [GalleryPhotoItem::id] = ?'
     *
     * Results would be:
     * array('[GalleryItem]', 'GalleryPhotoItem')
     *
     * @param string query
     * @return array GalleryStatus a return status,
     *               array strings table names
     */
    function extractClasses($query) {
    	$regexp = '/\[([^:]*)::[^\]]*\]/';
	$classes = array();

	preg_match_all($regexp, $query, $matches, PREG_PATTERN_ORDER);
	foreach ($matches[1] as $match) {
	    $classes['[' . $match . ']'] = 1;
	}
	return array(GalleryStatus::success(),
		     array_keys($classes));
    }

    /**
     * Optimize the back end.
     *
     * @return object GalleryStatus a status code
     */
    function optimize() {
	if (!$this->_isInitialized) {
	    $ret = $this->_impl->init();
	    if ($ret->isError()) {
		return $ret->wrap(__FILE__, __LINE__);
	    } else {
		$this->_isInitialized = true;
	    }
	}

	$ret = $this->_impl->optimize();
	if ($ret->isError()) {
	    return $ret->wrap(__FILE__, __LINE__);
	}
	return GalleryStatus::success();
    }
}
?>
