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

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

/**
 * A subclass of GalleryItem for items containing actual data
 *
 * DataItems are ones that have an actual data source, such as
 * PhotoItem or MovieItem.
 *
 * @g2 <class-name>GalleryDataItem</class-name>
 * @g2 <parent-class-name>GalleryItem</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 GalleryDataItem_core extends GalleryItem {

    /*
     * ****************************************
     *                 Members
     * ****************************************
     */

    /**
     * The mime type of the data file
     *
     * @g2 <member>
     * @g2   <member-name>mimeType</member-name>
     * @g2   <member-type>STRING</member-type>
     * @g2   <member-size>MEDIUM</member-size>
     * @g2   <linked/>
     * @g2 </member>
     *
     * @var string $_mimeType
     * @access private
     */
    var $_mimeType;

    /**
     * The byte size of the data file
     *
     * @g2 <member>
     * @g2   <member-name>size</member-name>
     * @g2   <member-type>INTEGER</member-type>
     * @g2   <linked/>
     * @g2 </member>
     *
     * @var string $_size
     * @access private
     */
    var $_size;

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

    /**
     * Data items that can be viewed inline (photos, movies, etc) should return
     * true.  Items that can't be viewed inline (word documents, text, etc)
     * should return false.
     *
     * Classes that return true for this query must implement getWidth() and getHeight()
     *
     * @return boolean true if this data item can be viewed inline
     */
    function canBeViewedInline() {
	return false;
    }

    /**
     * Create a new instance of this type in the persistent store.
     *
     * @access public
     * @param int the id of the parent GalleryItem
     * @param string the path to a data file to be contained
     * @param string the mime type of the new item
     * @param string (optional) the desired name of the new item
     * @param boolean (optional) a boolean true if we should symlink instead
     *        of copy (default is false).
     * @return object GalleryStatus a status code
     */
    function create($parentId, $inputFileName, $mimeType, $targetName=null, $symlink=false) {
	global $gallery;
	$platform = $gallery->getPlatform();

	/* Validate the input file */
	if (empty($inputFileName)) {
	    return GalleryStatus::error(ERROR_BAD_PARAMETER, __FILE__, __LINE__);
	}

	if (!$platform->is_uploaded_file($inputFileName)
		&& !$platform->file_exists($inputFileName)) {
	    return GalleryStatus::error(ERROR_BAD_PATH, __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.
	 */
	if (!GalleryCoreApi::isReadLocked($parentId)) {
	    return GalleryStatus::error(ERROR_LOCK_REQUIRED, __FILE__, __LINE__);
	}

	/* Set the mime type before we call the parent so that it can use this info if necessary */
	$this->setMimeType($mimeType);

	$baseName = !empty($targetName) ? $targetName : basename($inputFileName);
	$ret = parent::create($parentId, $baseName);
	if ($ret->isError()) {
	    return $ret->wrap(__FILE__, __LINE__);
	}

	/* Figure out the new location for this file */
	list($ret, $parent) = GalleryCoreApi::loadEntitiesById($parentId);
	if ($ret->isError()) {
	    return $ret->wrap(__FILE__, __LINE__);
	}

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

	/*
	 * Grab the new name from the path component, since GalleryFileSystemEntity
	 * may adapt the newName to avoid conflicts
	 */
	$newFileName = $parentPath . $this->getPathComponent();

	/*
	 * XXX: Race condition here!  It's possible for two threads to be
	 * adding a file with the same name to the container at the same time.
	 * We have to write lock the container to be sure that this doesn't
	 * happen.
	 */
	if ($symlink) {
	    if (!$platform->isSymlinkSupported()) {
		return GalleryStatus::error(ERROR_UNSUPPORTED_OPERATION, __FILE__, __LINE__);
	    }
	    if (!$platform->symlink($inputFileName, $newFileName)) {
		return GalleryStatus::error(ERROR_PLATFORM_FAILURE, __FILE__, __LINE__);
	    }
	} else {
	    /* Copy the file to its new location */
	    if (!$platform->copy($inputFileName, $newFileName)) {
		return GalleryStatus::error(ERROR_PLATFORM_FAILURE, __FILE__, __LINE__);
	    }
	}

	/* Set my file size */
	$this->setSize($platform->filesize($newFileName));

	return GalleryStatus::success();
    }

    /**
     * @see GalleryEntity::detachLink
     */
    function detachLink() {
	global $gallery;

	/* Stash the linked entity because the super class is going to get rid of it */
	$linkedEntity = $this->getLinkedEntity();

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

	$platform = $gallery->getPlatform();
	list ($ret, $sourcePath) = $linkedEntity->fetchPath();
	if ($ret->isError()) {
	    return $ret->wrap(__FILE__, __LINE__);
	}

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

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

	/* This path should already be legal, unless we screwed up elsewhere */
	$newPath = $newPathBase . $this->getPathComponent();

	if (!$platform->copy($sourcePath, $newPath)) {
	    return GalleryStatus::error(ERROR_PLATFORM_FAILURE, __FILE__, __LINE__);
	}

	return GalleryStatus::success();
    }

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

	/* TODO: Check for derivative relationships and expire as necessary. */

	/* Delete myself */
	$ret = parent::delete();
	if ($ret->isError()) {
	    return $ret->wrap(__FILE__, __LINE__);
	}

	/* Delete our source file, if we're not a link */
	if (!$this->isLinked()) {
	    list($ret, $path) = $this->fetchPath();
	    if ($ret->isError()) {
		return $ret->wrap(__FILE__, __LINE__);
	    }

	    $platform = $gallery->getPlatform();
	    if ($platform->file_exists($path) && !$platform->unlink($path)) {
		return GalleryStatus::error(ERROR_BAD_PATH, __FILE__, __LINE__,
					    "Could not delete $path");
	    }
	}

	return GalleryStatus::success();
    }

    /**
     * Scan this item's data for changes.
     *
     * @return object GalleryStatus a status code
     */
    function rescan() {
	global $gallery;

	$platform = $gallery->getPlatform();

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

	$this->setSize($platform->filesize($path));

	return GalleryStatus::success();
    }

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

	if ($newName == $this->getPathComponent()) {
	    return GalleryStatus::success();
	}

	/*
	 * Read lock the parent so that we don't have a race condition below.
	 */
	list ($ret, $lockId) = GalleryCoreApi::acquireReadLock($this->getParentId());
	if ($ret->isError()) {
	    return $ret->wrap(__FILE__, __LINE__);
	}

	$platform = $gallery->getPlatform();


	/*
	 * Get the current path before we let the superclass do the rename
	 */
	list($ret, $currentPath) = $this->fetchPath();
	if ($ret->isError()) {
	    return $ret->wrap(__FILE__, __LINE__);
	}

	/*
	 * Set the new path in the database.  If that succeeds then we should
	 * be ok in general.  Jump through hoops to make sure that we release
	 * our locks at the end.
	 */
	$error = null;
	$ret = parent::rename($newName);

	if ($ret->isError()) {
	    $error = $ret->wrap(__FILE__, __LINE__);
	}

	/* Only rename the filesystem entity if we're not a link */
	if (!$this->isLinked()) {

	    /*
	     * Grab the new name from the path component, since GalleryFileSystemEntity
	     * may adapt the newName to avoid conflicts
	     */
	    $newName = $this->getPathComponent();

	    if (!isset($error)) {
		$newPath = dirname($currentPath) . '/' . $newName;

		/* Check to see if the desired path exists. */
		if ($platform->file_exists($newPath)) {
		    $error = GalleryStatus::error(ERROR_COLLISION, __FILE__, __LINE__);
		}
	    }

	    if (!isset($error)) {
		if (!$platform->rename($currentPath, $newPath)) {
		    $error = GalleryStatus::error(ERROR_BAD_PATH, __FILE__, __LINE__);
		}
	    }
	}

	/* Release locks */
	$ret = GalleryCoreApi::releaseLocks($lockId);
	if ($ret->isError()) {
	    return $ret->wrap(__FILE__, __LINE__);
	}

	if (isset($error)) {
	    return $error;
	}

	return GalleryStatus::success();
    }

    /**
     * Move this item to a new parent
     *
     * @param int the id of the GalleryItem parent
     * @return object GalleryStatus a status code
     */
    function move($newParentId) {
	/* Get the current parent sequence */
	list ($ret, $parentSequence) = GalleryCoreApi::fetchParentSequence($this->getId());
	if ($ret->isError()) {
	    return $ret->wrap(__FILE__, __LINE__);
	}

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

	/* Set the new parent sequence */
	list ($ret, $newParentSequence) = GalleryCoreApi::fetchParentSequence($this->getParentId());
	if ($ret->isError()) {
	    return $ret->wrap(__FILE__, __LINE__);
	}
	$newParentSequence[] = $this->getParentId();

	/* Set my parent sequence accordingly */
	$ret = GalleryCoreApi::setParentSequence($this->getId(), $newParentSequence);
	if ($ret->isError()) {
	    return $ret->wrap(__FILE__, __LINE__);
	}

	return GalleryStatus::success();
    }

    /**
     * Get the full path to the data file.
     *
     * @access public
     * @return array object GalleryStatus a status code,
     *               string a path where children can store their data files
     */
    function fetchPath() {
	global $gallery;

	/*
	 * Source files in the item's directory.  But the parent may be an item
	 * that doesn't have its own directory, so we need to get the path of
	 * the nearest parent who is a container (eg an AlbumItem), because a
	 * container has a directory for the items it contains.
	 */
	if ($this->isLinked()) {
	    $target = $this->getLinkedEntity();
	} else {
	    $target = $this;
	}

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

	return array(GalleryStatus::success(), $containerPath . $target->getPathComponent());
    }

    /**
     * Render this item in the given format.  For example, GalleryPhotoItem may want to
     * render as an <img> tag in the HTML format, whereas GalleryMovieItem would use
     * <object> or <embed> in HTML.
     *
     * @param string the format (eg, "HTML")
     * @param array format specific key value pairs
     */
    function render($format, $params) {
	return null;
    }
}

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