<?php
/*
 * $RCSfile: GalleryFileSystemEntity.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.48 $ $Date: 2005/08/23 03:49:03 $
 * @package GalleryCore
 * @author Bharat Mediratta <bharat@menalto.com>
 */

/**
 * Load the parent class
 */
GalleryCoreApi::relativeRequireOnce('modules/core/classes/GalleryChildEntity.class');

/**
 * A GalleryChildEntity that also has data stored in the filesystem
 *
 * This class understands how to manage data on the filesystem in parallel to
 * the data in the persistent store.
 *
 * @g2 <class-name>GalleryFileSystemEntity</class-name>
 * @g2 <parent-class-name>GalleryChildEntity</parent-class-name>
 * @g2 <schema>
 * @g2   <schema-major>1</schema-major>
 * @g2   <schema-minor>0</schema-minor>
 * @g2 </schema>
 * @g2 <requires-id/>
 *
 * @package GalleryCore
 * @subpackage Classes
 */
class GalleryFileSystemEntity_core extends GalleryChildEntity {

    /**
     * The path component of this item (eg. "image1").  This
     * value, when combined with the paths of all the parent
     * objects (say, "rootAlbum", "album01") will form the
     * complete path to the item ("rootAlbum/album01/image1").
     *
     * @g2 <member>
     * @g2   <member-name>pathComponent</member-name>
     * @g2   <member-type>STRING</member-type>
     * @g2   <member-size>MEDIUM</member-size>
     * @g2   <indexed/>
     * @g2 </member>
     *
     * @var string $_pathComponent
     * @access private
     */
    var $_pathComponent;

    /*
     * ****************************************
     *                 Methods
     * ****************************************
     */


    /**
     * Create a new instance of this FileSystemEntity in the persistent store
     *
     * Let the parent do its work, then add any initialization specific to this
     * class.
     *
     * @param int the id of the parent GalleryChildEntity
     * @param string the path component of this entity
     * @return object GalleryStatus a status code
     */
    function create($parentId, $pathComponent) {
	global $gallery;

	if (empty($pathComponent)) {
	    return GalleryStatus::error(ERROR_BAD_PARAMETER, __FILE__, __LINE__);
	}

	/*
	 * The parent must be read locked at this point to make sure that it's
	 * not going to be moved around while we're adding stuff to it.
	 * Realistically, the entire parent tree must be locked but it's not
	 * really practical to check the entire tree so just check the parent.
	 */
	if (!GalleryCoreApi::isReadLocked($parentId)) {
	    return GalleryStatus::error(ERROR_LOCK_REQUIRED, __FILE__, __LINE__);
	}

	list ($ret, $pathComponent) =
	    GalleryCoreApi::getLegalPathComponent($pathComponent, $parentId);
	if ($ret->isError()) {
	    return $ret->wrap(__FILE__, __LINE__);
	}

	$ret = parent::create($parentId);
	if ($ret->isError()) {
	    return $ret->wrap(__FILE__, __LINE__);
	}

	/* Set our path component */
	$this->setPathComponent($pathComponent);

	return GalleryStatus::success();
    }

    /**
     * Create a new root level instance of this FileSystemEntity in the persistent store
     *
     * Let the parent do its work, then add any initialization specific to this
     * class.
     *
     * @return object GalleryStatus a status code
     */
    function createRoot() {
	global $gallery;

	/* No collision -- proceed! */
	$ret = parent::createRoot();
	if ($ret->isError()) {
	    return $ret->wrap(__FILE__, __LINE__);
	}

	/* The root has no path component */
	$this->setPathComponent(NULL);

	return GalleryStatus::success();
    }

    /**
     * @see GalleryEntity::createLink
     *
     * @param int the id of the parent GalleryChildEntity
     * @param string the path component of this entity
     * @return object GalleryStatus a status code
     */
    function createLink($entity, $parentId) {
	global $gallery;

	$ret = parent::createLink($entity, $parentId);
	if ($ret->isError()) {
	    return $ret->wrap(__FILE__, __LINE__);
	}

	list ($ret, $pathComponent) =
	    GalleryCoreApi::getLegalPathComponent($entity->getPathComponent(), $parentId);
	if ($ret->isError()) {
	    return $ret->wrap(__FILE__, __LINE__);
	}

	/* Set our path component */
	$this->setPathComponent($pathComponent);

	return GalleryStatus::success();
    }

    /**
     * Rename this item
     *
     * @access public
     * @param string the path component
     * @return object GalleryStatus a status code
     */
    function rename($newPathComponent) {
	global $gallery;

	list ($ret, $rootAlbumId) =
	    GalleryCoreApi::getPluginParameter('module', 'core', 'id.rootAlbum');
	if ($ret->isError()) {
	    return $ret->wrap(__FILE__, __LINE__);
	}
	if ($this->getId() == $rootAlbumId) {
	    return GalleryStatus::error(ERROR_BAD_PARAMETER, __FILE__, __LINE__);
	}

	if (empty($newPathComponent)) {
	    return GalleryStatus::error(ERROR_BAD_PARAMETER, __FILE__, __LINE__);
	}

	/* No need to do anything if renaming to same name */
	if ($newPathComponent == $this->getPathComponent()) {
	    return GalleryStatus::success();
	}

	$parentId = $this->getParentId();
	list ($ret, $newPathComponent) =
	    GalleryCoreApi::getLegalPathComponent($newPathComponent, $parentId, $this->getId());
	if ($ret->isError()) {
	    return $ret->wrap(__FILE__, __LINE__);
	}

	$this->setPathComponent($newPathComponent);

	return GalleryStatus::success();
    }

    /**
     * Move item to a new parent
     *
     * @access public
     * @param int the id of the new parent GalleryItem
     * @return object GalleryStatus a status code
     * @todo Make sure that 'order' stuff is maintained, so that the moved
     *       items become the last in order in the new album if that album
     *       is marked as unordered
     */
    function move($newParentId) {
	global $gallery;

	/* No need to do anything if moving to same parent */
	if ($newParentId == $this->getParentId()) {
	    return GalleryStatus::success();
	}

	/* Make sure the old parent and the new parent are read locked */
	if (!(GalleryCoreApi::isReadLocked($newParentId)
		&& GalleryCoreApi::isReadLocked($this->getParentId()))) {
	    return GalleryStatus::error(ERROR_LOCK_REQUIRED, __FILE__, __LINE__,
					sprintf("One parent id (%d or %d) is not read locked",
						$newParentId,
						$this->getParentId()));
	}

	/* Get the new parent */
	list ($ret, $newParent) = GalleryCoreApi::loadEntitiesById($newParentId);
	if ($ret->isError()) {
	    return $ret->wrap(__FILE__, __LINE__);
	}

	if (!$newParent->getCanContainChildren()) {
	    return GalleryStatus::error(ERROR_BAD_PARAMETER, __FILE__, __LINE__);
	}

	list ($ret, $newPathComponent) = GalleryCoreApi::getLegalPathComponent(
					 $this->getPathComponent(), $newParentId, $this->getId());
	if ($ret->isError()) {
	    return $ret->wrap(__FILE__, __LINE__);
	}

	list ($ret, $currentPath) = $this->fetchPath();
	if ($ret->isError()) {
	    return $ret->wrap(__FILE__, __LINE__);
	}

	list ($ret, $newParentPath) = $newParent->fetchPath();
	if ($ret->isError()) {
	    return $ret->wrap(__FILE__, __LINE__);
	}

	/* No collision -- proceed! */
	$ret = parent::move($newParentId);
	if ($ret->isError()) {
	    return $ret->wrap(__FILE__, __LINE__);
	}

	if (!$this->isLinked()) {
	    $newPath = $newParentPath . $newPathComponent;
	    $platform = $gallery->getPlatform();
	    if (!$platform->rename($currentPath, $newPath)) {
		return GalleryStatus::error(ERROR_BAD_PATH, __FILE__, __LINE__,
					    "rename $currentPath to $newPath");
	    }
	}

	/* Our path component may have changed when it got "legalized" */
	$this->setPathComponent($newPathComponent);

	return GalleryStatus::success();
    }

    /**
     * Delete this GalleryFileSystemEntity
     *
     * @access public
     * @return object GalleryStatus a status code
     */
    function delete() {
	global $gallery;

	/*
	 * The parent must be read or write locked at this point to make sure
	 * that it's not going to be moved around while we're deleting stuff
	 * from its children.  Realistically, the entire parent tree must be at
	 * least read locked but it's not really practical to check the entire
	 * tree so just check the parent.
	 */
	$parentId = $this->getParentId();
	if (!empty($parentId)) {
	    if (!GalleryCoreApi::isReadLocked($parentId) &&
		    !GalleryCoreApi::isWriteLocked($parentId)) {
		return GalleryStatus::error(ERROR_LOCK_REQUIRED, __FILE__, __LINE__);
	    }
	}

	return parent::delete();
    }

    /**
     * Return a path for any objects contained within this one (ie, children)
     *
     * Subclasses should specify their container-ness by overloading
     * isContainer()
     *
     * @return array object GalleryStatus a status code,
     *               string a path where children can store their data files
     */
    function fetchContainerPath() {
	global $gallery;

	if ($this->isContainer()) {
	    list ($ret, $path) = $this->fetchPath();
	    if ($ret->isError()) {
		return array($ret->wrap(__FILE__, __LINE__), null);
	    }
	    return array(GalleryStatus::success(), $path);
	} else {
	    list ($ret, $parent) = $this->fetchParent();
	    if ($ret->isError()) {
		return array($ret->wrap(__FILE__, __LINE__), null);
	    }

	    if (isset($parent)) {
		list ($ret, $path) = $parent->fetchContainerPath();
		if ($ret->isError()) {
		    return array($ret->wrap(__FILE__, __LINE__), null);
		}

		return array(GalleryStatus::success(), $path);
	    } else {
		return array(GalleryStatus::error(ERROR_BAD_PATH, __FILE__, __LINE__), null);
	    }
	}
    }

    /**
     * Can this item contain other file system items?
     *
     * @return true if this item can contain other file system items
     */
    function isContainer() {
	return false;
    }

    /**
     * Return the logical path to this item.  Note that this path is only valid as
     * long as the entire tree is at least read locked.
     *
     * @access public
     * @return array object GalleryStatus a status code,
     *         array path component names
     */
    function fetchLogicalPath() {
	global $gallery;

	$parentId = $this->getParentId();
	if (!empty($parentId)) {
	    list($ret, $parent) = $this->fetchParent();
	    if ($ret->isError()) {
		return array($ret->wrap(__FILE__, __LINE__), null);
	    }

	    list($ret, $parentPath) = $parent->fetchLogicalPath();
	    if ($ret->isError()) {
		return array($ret->wrap(__FILE__, __LINE__), null);
	    }
	} else {
	    $parentPath = '';
	}

	$path = $parentPath . $this->getPathComponent();
	if ($this->isContainer()) {
	    $path .= '/';
	}

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

    /**
     * Return the full path of this item.  Note that this path is only valid as
     * long as the entire tree is at least read locked.
     *
     * @access public
     * @return array a GalleryStatus status, string a path
     */
    function fetchPath() {
	global $gallery;
	$absolutePath = $gallery->getConfig('data.gallery.albums');

	if ($this->isLinked()) {
	    $linkedEntity = $this->getLinkedEntity();
	    list ($ret, $logicalPath) = $linkedEntity->fetchLogicalPath();
	    if ($ret->isError()) {
		return array($ret->wrap(__FILE__, __LINE__), null);
	    }
	} else {
	    list ($ret, $logicalPath) = $this->fetchLogicalPath();
	    if ($ret->isError()) {
		return array($ret->wrap(__FILE__, __LINE__), null);
	    }
	}

	/*
	 * The album path ends with a slash, and the logical path starts with one (because the
	 * root path component is empty) so we'll have two slashes, unless we remove one of them.
	 */
	$logicalPath = substr($logicalPath, 1);

	/*
	 * Logical path is slash (/) delimited.  Convert that to the platform's actual
	 * directory separator.
	 */
	$platform = $gallery->getPlatform();
	if ($platform->getDirectorySeparator() != '/') {
	    $logicalPath = str_replace('/', $platform->getDirectorySeparator(), $logicalPath);
	}

	return array(GalleryStatus::success(), $absolutePath . $logicalPath);
    }
}

include(dirname(__FILE__) . '/interfaces/GalleryFileSystemEntity.inc');
?>
