<?php
require_once("BasicStor.php");
if (isset($WHITE_SCREEN_OF_DEATH) && $WHITE_SCREEN_OF_DEATH) {
    echo __FILE__.':line '.__LINE__.": Loaded BasicStor<br>";
}
require_once("Transport.php");
if (isset($WHITE_SCREEN_OF_DEATH) && $WHITE_SCREEN_OF_DEATH) {
    echo __FILE__.':line '.__LINE__.": Loaded Transport<br>";
}

/**
 * LocStor class
 *
 * Local storage interface
 *
 * @package Campcaster
 * @subpackage StorageServer
 * @copyright 2010 Sourcefabric O.P.S.
 * @license http://www.gnu.org/licenses/gpl.txt
 */
class LocStor extends BasicStor {

    /* ---------------------------------------------------------------- store */

    /**
     * Store or replace existing audio clip.
     *
     * Sending a file to the storage server is a 3 step process:
     * 1) Call storeAudioClipOpen
     * 2) Upload the file to the URL specified
     * 3) Call storeAudioClipClose
     *
     * @param string $sessid
     * 		session id
     * @param string $gunid
     * 		global unique id
     * @param string $metadata
     * 		metadata XML string
     * @param string $fname
     * 		human readable menmonic file name
     *      with extension corresponding to filetype
     * @param string $chsum
     * 		md5 checksum of media file
     * @param string $ftype
     * 		audioclip | playlist | webstream
     * @return array
     * 		{url:writable URL for HTTP PUT, token:access token}
     */
    protected function storeAudioClipOpen($sessid, $gunid, $metadata,
        $fname, $chsum, $ftype='audioclip')
    {
        // Check the gunid format
        if (!BasicStor::CheckGunid($gunid)) {
            return PEAR::raiseError(
                "LocStor::storeAudioClipOpen: Wrong gunid ($gunid)"
            );
        }

        // Check if we already have this file.
        $duplicate = StoredFile::RecallByMd5($chsum);
        if (!empty($chsum) && $duplicate) {
            return PEAR::raiseError(
                "LocStor::storeAudioClipOpen: Duplicate file"
                ." - Matched MD5 ($chsum) against '".$duplicate->getName()."'",
                888);
        }

        // Check if specified gunid exists.
        $storedFile =& StoredFile::RecallByGunid($gunid);
        if (!is_null($storedFile) && !PEAR::isError($storedFile)) {
            // gunid exists - do replace
            $oid = $storedFile->getId();
            if (($res = BasicStor::Authorize('write', $oid, $sessid)) !== TRUE) {
                return $res;
            }
            if ($storedFile->isAccessed()) {
                return PEAR::raiseError(
                    'LocStor::storeAudioClipOpen: is accessed'
                );
            }
            $res = $storedFile->replace($oid, $storedFile->getName(), '', $metadata, 'string');
            if (PEAR::isError($res)) {
                return $res;
            }
        } else {
            // gunid doesn't exist - do insert:
            $tmpFname = uniqid();
            if (($res = BasicStor::Authorize('write', null, $sessid)) !== TRUE) {
                return $res;
            }
            $values = array(
                "metadata" => $metadata,
                "gunid" => $gunid,
                "filetype" => $ftype);
            $storedFile =& StoredFile::Insert($values);
            if (PEAR::isError($storedFile)) {
                return $storedFile;
            }
            if (PEAR::isError($res)) {
                return $res;
            }
        }
        $res = $storedFile->setState('incomplete');
        if (PEAR::isError($res)) {
            return $res;
        }
        if ($fname == '') {
            $fname = "newFile";
        }
        $res = $this->bsRenameFile($storedFile->id, $fname);
        if (PEAR::isError($res)) {
            return $res;
        }
        return $this->bsOpenPut($chsum, $storedFile->gunid);
    }


    /**
     * Store or replace existing audio clip
     *
     * @param string $sessid
     * @param string $token
     * @return string gunid|PEAR_Error
     */
    protected function storeAudioClipClose($sessid, $token)
    {
        $storedFile =& StoredFile::RecallByToken($token);
        if (is_null($storedFile) || PEAR::isError($storedFile)) {
            return $storedFile;
        }
        $arr = $this->bsClosePut($token);
        if (PEAR::isError($arr)) {
            $storedFile->delete();
            return $arr;
        }
        $fname = $arr['fname'];
        $res = $storedFile->setRawMediaData($fname);
        if (PEAR::isError($res)) {
            return $res;
        }
        if (file_exists($fname)) {
            @unlink($fname);
        }
        $res = $storedFile->setState('ready');
        if (PEAR::isError($res)) {
            return $res;
        }
        return $storedFile->gunid;
    }


    /**
     * Check uploaded file
     *
     * @param string $token
     * 		"put" token
     * @return array
     * 		hash, (status: boolean, size: int - filesize)
     */
    protected function uploadCheck($token)
    {
        return $this->bsCheckPut($token);
    }


    /**
     * Store webstream
     *
     * @param string $sessid
     * 		session id
     * @param string $gunid
     * 		global unique id
     * @param string $metadata
     * 		metadata XML string
     * @param string $fname
     * 		human readable menmonic file name with extension corresponding to filetype
     * @param string $url
     * 		webstream url
     * @return string
     * 		gunid
     */
    protected function storeWebstream($sessid, $gunid, $metadata, $fname, $url)
    {
        $a = $this->storeAudioClipOpen(
            $sessid, $gunid, $metadata, $fname, md5(''), 'webstream');
        if (PEAR::isError($a)) {
            return $a;
        }
        $gunid = $this->storeAudioClipClose($sessid, $a['token']);
        if (PEAR::isError($gunid)) {
            return $gunid;
        }
        $storedFile =& StoredFile::RecallByGunid($gunid);
        if (is_null($storedFile) || PEAR::isError($storedFile)) {
            return $storedFile;
        }
        $oid = $storedFile->getId();
        $r = $this-> bsSetMetadataValue($oid, 'ls:url', $url);
        if (PEAR::isError($r)) {
            return $r;
        }
        return $gunid;
    }


    /* --------------------------------------------------------------- access */
    /**
     * Make access to audio clip
     *
     * @param string $sessid
     * @param string $gunid
     * @param int $parent
     * 		parent token
     * @return array
     * 		with: seekable filehandle, access token
     */
    public function accessRawAudioData($sessid, $gunid, $parent='0')
    {
        $storedFile =& StoredFile::RecallByGunid($gunid);
        if (is_null($storedFile) || PEAR::isError($storedFile)) {
            return $storedFile;
        }
        if (($res = BasicStor::Authorize('read', $storedFile->getId(), $sessid)) !== TRUE) {
            return $res;
        }
        return $storedFile->accessRawMediaData($parent);
    }


    /**
     * Release access to audio clip
     *
     * @param string $sessid
     * @param string $token
     * 		access token
     * @return boolean|PEAR_Error
     */
    public function releaseRawAudioData($sessid, $token)
    {
        $storedFile =& StoredFile::RecallByToken($token);
        if (is_null($storedFile) || PEAR::isError($storedFile)) {
            return $storedFile;
        }
        return $storedFile->releaseRawMediaData($token);
    }


    /* ------------------------------------------------------------- download */
    /**
     * Create and return downloadable URL for audio file
     *
     * @param string $sessid
     * 		session id
     * @param string $gunid
     * 		global unique id
     * @return array
     * 		array with strings:
     *      downloadable URL, download token, chsum, size, filename
     */
    protected function downloadRawAudioDataOpen($sessid, $gunid)
    {
        $ex = $this->existsAudioClip($sessid, $gunid);
        if (PEAR::isError($ex)) {
            return $ex;
        }
        $id = BasicStor::IdFromGunid($gunid);
        if (is_null($id) || !$ex) {
            return PEAR::raiseError(
                "LocStor::downloadRawAudioDataOpen: gunid not found ($gunid)",
                GBERR_NOTF
            );
        }
        if (($res = BasicStor::Authorize('read', $id, $sessid)) !== TRUE) {
            return $res;
        }
        return $this->bsOpenDownload($id);
    }


    /**
     * Discard downloadable URL for audio file
     *
     * @param string $token
     * 		download token
     * @return string
     * 		gunid
     */
    protected function downloadRawAudioDataClose($token)
    {
        return $this->bsCloseDownload($token);
    }


    /**
     * Create and return downloadable URL for metadata
     *
     * @param string $sessid
     * 		session id
     * @param string $gunid
     * 		global unique id
     * @return array
     * 		array with strings:
     *      downloadable URL, download token, chsum, filename
     */
    protected function downloadMetadataOpen($sessid, $gunid)
    {
//        $res = $this->existsAudioClip($sessid, $gunid);
//        if(PEAR::isError($res)) return $res;
        $id = BasicStor::IdFromGunid($gunid);
        if (is_null($id)) {
            return PEAR::raiseError(
             "LocStor::downloadMetadataOpen: gunid not found ($gunid)"
            );
        }
        if (($res = BasicStor::Authorize('read', $id, $sessid)) !== TRUE) {
            return $res;
        }
        $res = $this->bsOpenDownload($id, 'metadata');
        #unset($res['filename']);
        return $res;
    }


    /**
     * Discard downloadable URL for metadata
     *
     * @param string $token
     * 		download token
     * @return string
     * 		gunid
     */
    protected function downloadMetadataClose($token)
    {
        return $this->bsCloseDownload($token, 'metadata');
    }


    /**
     * Return metadata as XML
     *
     * @param string $sessid
     * @param string $gunid
     * @return string|PEAR_Error
     */
    protected function getAudioClip($sessid, $gunid)
    {
        $storedFile =& StoredFile::RecallByGunid($gunid);
        if (is_null($storedFile) || PEAR::isError($storedFile)) {
            return $storedFile;
        }
        if (($res = BasicStor::Authorize('read', $storedFile->getId(), $sessid)) !== TRUE) {
            return $res;
        }
        $md = $this->bsGetMetadata($storedFile->getId());
        if (PEAR::isError($md)) {
            return $md;
        }
        return $md;
    }


    /* ------------------------------------------------------- search, browse */

    /**
     * Search in metadata database
     *
     * @param string $sessid
     * @param array $criteria
     * 	with following structure:<br>
     *   <ul>
     *     <li>filetype - string, type of searched files,
     *       meaningful values: 'audioclip', 'webstream', 'playlist', 'all'</li>
     *     <li>operator - string, type of conditions join
     *       (any condition matches / all conditions match),
     *       meaningful values: 'and', 'or', ''
     *       (may be empty or ommited only with less then 2 items in
     *       &quot;conditions&quot; field)
     *     </li>
     *     <li>limit : int - limit for result arrays (0 means unlimited)</li>
     *     <li>offset : int - starting point (0 means without offset)</li>
     *     <li>orderby : string - metadata category for sorting (optional)
     *          or array of strings for multicolumn orderby
     *          [default: dc:creator, dc:source, dc:title]
     *     </li>
     *     <li>desc : boolean - flag for descending order (optional)
     *          or array of boolean for multicolumn orderby
     *          (it corresponds to elements of orderby field)
     *          [default: all ascending]
     *     </li>
     *     <li>conditions - array of hashes with structure:
     *       <ul>
     *           <li>cat - string, metadata category name</li>
     *           <li>op - string, operator - meaningful values:
     *               'full', 'partial', 'prefix', '=', '&lt;',
     *               '&lt;=', '&gt;', '&gt;='</li>
     *           <li>val - string, search value</li>
     *       </ul>
     *     </li>
     *   </ul>
     *  @return array of hashes, fields:
     *   <ul>
     *       <li>cnt : integer - number of matching gunids
     *              of files have been found</li>
     *       <li>results : array of hashes:
     *          <ul>
     *           <li>gunid: string</li>
     *           <li>type: string - audioclip | playlist | webstream</li>
     *           <li>title: string - dc:title from metadata</li>
     *           <li>creator: string - dc:creator from metadata</li>
     *           <li>source: string - dc:source from metadata</li>
     *           <li>length: string - dcterms:extent in extent format</li>
     *          </ul>
     *      </li>
     *   </ul>
     *  @see BasicStor::localSearch
      */
    public function searchMetadata($sessid, $criteria)
    {
        if (($res = BasicStor::Authorize('read', $this->storId, $sessid)) !== TRUE) {
            return $res;
        }
        $criteria['resultMode'] = 'xmlrpc';
        $res = $this->localSearch($criteria, $sessid);
        return $res;
    }


    /**
     * @param array $criteria
     * @param mixed $sessid
     *      This variable isnt used.
     * @return unknown
     */
    public function localSearch($criteria, $sessid='')
    {
        $limit = intval(isset($criteria['limit']) ? $criteria['limit'] : 0);
        $offset = intval(isset($criteria['offset']) ? $criteria['offset'] : 0);
        $res = $this->bsLocalSearch($criteria, $limit, $offset);
        return $res;
    }


    /**
     * Return values of specified metadata category
     *
     * @param string $category
     * 		metadata category name
     *      with or without namespace prefix (dc:title, author)
     * @param hash $criteria
     * 		see searchMetadata method
     * @param string $sessid
     * @return array
     * 		hash, fields:
     *       results : array with found values
     *       cnt : integer - number of matching values
     *  @see BasicStor::bsBrowseCategory
     */
    protected function browseCategory($category, $criteria=NULL, $sessid='')
    {
        $limit = intval(isset($criteria['limit']) ? $criteria['limit'] : 0);
        $offset = intval(isset($criteria['offset']) ? $criteria['offset'] : 0);
        $res = $this->bsBrowseCategory($category, $limit, $offset, $criteria);
        return $res;
    }


    /* ----------------------------------------------------------------- etc. */
    /**
     * Check if audio clip exists
     *
     * @param string $sessid
     * @param string $gunid
     * @return boolean
     */
    protected function existsAudioClip($sessid, $gunid)
    {
        $ex = $this->existsFile($sessid, $gunid, 'audioclip');
        // webstreams are subset of audioclips - moved to BasicStor
        // if($ex === FALSE ){
        //    $ex = $this->existsFile($sessid, $gunid, 'webstream');
        // }
        if ($ex === FALSE ) {
            return FALSE;
        }
        if (PEAR::isError($ex)) {
            return $ex;
        }
        $storedFile =& StoredFile::RecallByGunid($gunid);
        if (is_null($storedFile) || PEAR::isError($storedFile)) {
            return $storedFile;
        }
        return $storedFile->exists();
    }


    /**
     * Check if file exists in the storage
     *
     * @param string $sessid
     * @param string $gunid
     * @param string $ftype
     * 		internal file type
     * @return boolean
     */
    protected function existsFile($sessid, $gunid, $ftype=NULL)
    {
        $id = BasicStor::IdFromGunid($gunid);
        if (is_null($id)) {
            return FALSE;
        }
        if (($res = BasicStor::Authorize('read', $id, $sessid)) !== TRUE) {
            return $res;
        }
        $ex = $this->bsExistsFile($id, $ftype);
        return $ex;
    }


    /**
     * Delete existing audio clip
     *
     * @param string $sessid
     * @param string $gunid
     * @param boolean $forced
     * 		if true, don't use trash
     * @return boolean|PEAR_Error
     */
    protected function deleteAudioClip($sessid, $gunid, $forced=FALSE)
    {
        $storedFile =& StoredFile::RecallByGunid($gunid);
        if (is_null($storedFile)) {
            return TRUE;
        }
        if (PEAR::isError($storedFile)) {
            if ($storedFile->getCode()==GBERR_FOBJNEX && $forced) {
                return TRUE;
            }
            return $storedFile;
        }
        if (($res = BasicStor::Authorize('write', $storedFile->getId(), $sessid)) !== TRUE) {
            return $res;
        }
        $res = $this->bsDeleteFile($storedFile->getId(), $forced);
        if (PEAR::isError($res)) {
            return $res;
        }
        return TRUE;
    }


    /**
     * Update existing audio clip metadata
     *
     * @param string $sessid
     * @param string $gunid
     * @param string $metadata
     * 		metadata XML string
     * @return boolean|PEAR_Error
     */
    protected function updateAudioClipMetadata($sessid, $gunid, $metadata)
    {
        $storedFile =& StoredFile::RecallByGunid($gunid);
        if (is_null($storedFile) || PEAR::isError($storedFile)) {
            return $storedFile;
        }
        if (($res = BasicStor::Authorize('write', $storedFile->getId(), $sessid)) !== TRUE) {
            return $res;
        }
        return $storedFile->setMetadata($metadata, 'string');
    }


    /*====================================================== playlist methods */
    /**
     * Create a new empty playlist.
     *
     * @param string $sessid
     * 		session ID
     * @param string $playlistId
     * 		playlist global unique ID
     * @param string $fname
     * 		human readable mnemonic file name
     * @return string
     * 		playlist global unique ID
     */
    public function createPlaylist($sessid, $playlistId, $fname)
    {
        $ex = $this->existsPlaylist($sessid, $playlistId);
        if (PEAR::isError($ex)) {
            return $ex;
        }
        if ($ex) {
            return PEAR::raiseError(
                'LocStor::createPlaylist: already exists'
            );
        }
        $tmpFname = uniqid('');
        if (($res = BasicStor::Authorize('write', null, $sessid)) !== TRUE) {
            return $res;
        }
        $values = array(
            "metadata" => dirname(__FILE__).'/emptyPlaylist.xml',
            "gunid" => $playlistId,
            "filetype" => "playlist");
        $storedFile = StoredFile::Insert($values);
        if (PEAR::isError($storedFile)) {
            $res = BasicStor::RemoveObj($oid);
            return $storedFile;
        }
        if ($fname == '') {
            $fname = "newFile.xml";
        }
        $res = $this->bsRenameFile($oid, $fname);
        if (PEAR::isError($res)) {
            return $res;
        }
        $res = $storedFile->setState('ready');
        if (PEAR::isError($res)) {
            return $res;
        }
        $res = $storedFile->setMime('application/smil');
        if (PEAR::isError($res)) {
            return $res;
        }
        return $storedFile->gunid;
    }


    /**
     * Open a Playlist metafile for editing.
     * Open readable URL and mark file as beeing edited.
     *
     * @param string $sessid
     * 		session ID
     * @param string $playlistId
     * 		playlist global unique ID
     * @return struct
     *      {url:readable URL for HTTP GET, token:access token, chsum:checksum}
     */
    public function editPlaylist($sessid, $playlistId)
    {
        $ex = $this->existsPlaylist($sessid, $playlistId);
        if (PEAR::isError($ex)) {
            return $ex;
        }
        if (!$ex) {
            return PEAR::raiseError(
                'LocStor::editPlaylist: playlist not exists'
            );
        }
        if ($this->isEdited($playlistId) !== FALSE) {
            return PEAR::raiseError(
                'LocStor::editPlaylist: playlist already edited'
            );
        }
        $storedFile =& StoredFile::RecallByGunid($playlistId);
        if (is_null($storedFile) || PEAR::isError($storedFile)) {
            return $storedFile;
        }
        $id = $storedFile->getId();
        if (($res = BasicStor::Authorize('write', $id, $sessid)) !== TRUE) {
            return $res;
        }
        $res = $this->bsOpenDownload($id, 'metadata');
        if (PEAR::isError($res)) {
            return $res;
        }
        $r = $this->setEditFlag($playlistId, TRUE, $sessid);
        if (PEAR::isError($r)) {
            return $r;
        }
        unset($res['filename']);
        return $res;
    }


    /**
     * Store a new Playlist metafile in place of the old one.
     *
     * @param string $sessid
     * 		session ID
     * @param string $playlistToken
     * 		playlist access token
     * @param string $newPlaylist
     * 		new playlist as XML string
     * @return string
     * 		playlistId
     */
    protected function savePlaylist($sessid, $playlistToken, $newPlaylist)
    {
        $playlistId = $this->bsCloseDownload($playlistToken, 'metadata');
        if (PEAR::isError($playlistId)) {
            return $playlistId;
        }
        $storedFile =& StoredFile::RecallByGunid($playlistId);
        if (is_null($storedFile) || PEAR::isError($storedFile)) {
            return $storedFile;
        }
        $res = $storedFile->setMetadata($newPlaylist, 'string', 'playlist');
        if (PEAR::isError($res)) {
            return $res;
        }
        $r = $this->setEditFlag($playlistId, FALSE, $sessid);
        if (PEAR::isError($r)) {
            return $r;
        }
        return $playlistId;
    }


    /**
     * RollBack playlist changes to the locked state
     *
     * @param string $playlistToken
     * 		playlist access token
     * @param string $sessid
     * 		session ID
     * @return string
     * 		gunid of playlist
     */
    public function revertEditedPlaylist($playlistToken, $sessid='')
    {
        $gunid = $this->bsCloseDownload($playlistToken, 'metadata');
        if (PEAR::isError($gunid)) {
            return $gunid;
        }
        $storedFile =& StoredFile::RecallByGunid($gunid);
        if (is_null($storedFile) || PEAR::isError($storedFile)) {
            return $storedFile;
        }
        $id = $storedFile->getId();
        $mdata = $storedFile->getMetadata();
        if (PEAR::isError($mdata)) {
            return $mdata;
        }
        $res = $storedFile->setMetadata($mdata, 'string');
        if (PEAR::isError($res)) {
            return $res;
        }
        $this->setEditFlag($gunid, FALSE, $sessid);
        return $gunid;
    }


    /**
     * Delete a Playlist metafile.
     *
     * @param string $sessid
     * 		session ID
     * @param string $playlistId
     * 		playlist global unique ID
     * @param boolean $forced
     * 		if true don't use trash
     * @return boolean
     */
    public function deletePlaylist($sessid, $playlistId, $forced=FALSE)
    {
        $ex = $this->existsPlaylist($sessid, $playlistId);
        if (PEAR::isError($ex)) {
            return $ex;
        }
        if (!$ex) {
            if ($forced) {
                return TRUE;
            }
            return PEAR::raiseError(
                'LocStor::deletePlaylist: playlist not exists',
                GBERR_FILENEX
            );
        }
        $storedFile =& StoredFile::RecallByGunid($playlistId);
        if (is_null($storedFile) || PEAR::isError($storedFile)) {
            return $storedFile;
        }
        if (($res = BasicStor::Authorize('write', $storedFile->getId(), $sessid)) !== TRUE) {
            return $res;
        }
        $res = $this->bsDeleteFile($storedFile->getId(), $forced);
        if (PEAR::isError($res)) {
            return $res;
        }
        return TRUE;
    }


    /**
     * Access (read) a Playlist metafile.
     *
     * @param string $sessid
     * 		session ID
     * @param string $playlistId
     * 		playlist global unique ID
     * @param boolean $recursive
     * 		flag for recursive access content inside playlist
     * @param int $parent
     * 		parent token
     * @return struct {
     *      url: readable URL for HTTP GET,
     *      token: access token,
     *      chsum: checksum,
     *      content: array of structs - recursive access (optional)
     *      filename: string mnemonic filename
     *  }
     */
    public function accessPlaylist($sessid, $playlistId, $recursive=FALSE, $parent='0')
    {
        if ($recursive) {
            require_once("AccessRecur.php");
            $r = AccessRecur::accessPlaylist($this, $sessid, $playlistId);
            if (PEAR::isError($r)) {
                return $r;
            }
            return $r;
        }
        $ex = $this->existsPlaylist($sessid, $playlistId);
        if (PEAR::isError($ex)) {
            return $ex;
        }
        if (!$ex) {
            return PEAR::raiseError(
                "LocStor::accessPlaylist: playlist not found ($playlistId)",
                GBERR_NOTF
            );
        }
        $id = BasicStor::IdFromGunid($playlistId);
        if (($res = BasicStor::Authorize('read', $id, $sessid)) !== TRUE) {
            return $res;
        }
        $res = $this->bsOpenDownload($id, 'metadata', $parent);
        #unset($res['filename']);
        return $res;
    }


    /**
     * Release the resources obtained earlier by accessPlaylist().
     *
     * @param string $sessid
     * 		session ID
     * @param string $playlistToken
     * 		playlist access token
     * @param boolean $recursive
     * 		flag for recursive access content inside playlist
     * @return string
     * 		playlist ID
     */
    public function releasePlaylist($sessid, $playlistToken, $recursive=FALSE)
    {
        if ($recursive) {
            require_once"AccessRecur.php";
            $r = AccessRecur::releasePlaylist($this, $sessid, $playlistToken);
            if (PEAR::isError($r)) {
                return $r;
            }
            return $r;
        }
        return $this->bsCloseDownload($playlistToken, 'metadata');
    }


    /**
     * Create a tarfile with playlist export - playlist and all matching
     * sub-playlists and media files (if desired)
     *
     * @param string $sessid
     * 		session ID
     * @param array $plids
     * 		array of strings, playlist global unique IDs (one gunid is accepted too)
     * @param string $type
     * 		playlist format, values: lspl | smil | m3u
     * @param boolean $standalone
     * 		if only playlist should be exported or with all related files
     * @return hasharray with  fields:
     *      url string: readable url,
     *      token string: access token
     *      chsum string: md5 checksum,
     */
    protected function exportPlaylistOpen($sessid, $plids, $type='lspl', $standalone=FALSE)
    {
        $res = $this->bsExportPlaylistOpen($plids, $type, !$standalone);
        if (PEAR::isError($res)) {
            return $res;
        }
        $url = BasicStor::GetUrlPart()."access/".basename($res['fname']);
        $chsum = md5_file($res['fname']);
        $size = filesize($res['fname']);
        return array(
            'url' => $url,
            'token' => $res['token'],
            'chsum' => $chsum,
        );
    }


    /**
     * Close playlist export previously opened by the exportPlaylistOpen method
     *
     * @param string $token
     * 		access token obtained from exportPlaylistOpen method call
     * @return boolean|PEAR_Error
     */
    protected function exportPlaylistClose($token)
    {
        return $this->bsExportPlaylistClose($token);
    }


    /**
     * Open writable handle for import playlist in LS Archive format
     *
     * @param string $sessid
     * 		session id
     * @param string $chsum
     * 		md5 checksum of imported file
     * @return hasharray with:
     *      url string: writable URL
     *      token string: PUT token
     */
    protected function importPlaylistOpen($sessid, $chsum)
    {
        $userid = Alib::GetSessUserId($sessid);
        if (PEAR::isError($userid)) {
            return $userid;
        }
        $r = $this->bsOpenPut($chsum, NULL, $userid);
        if (PEAR::isError($r)) {
            return $r;
        }
        return $r;
    }


    /**
     * Close import-handle and import playlist
     *
     * @param string $token
     * 		import token obtained by importPlaylistOpen method
     * @return string
     * 		result file global id (or error object)
     */
    protected function importPlaylistClose($token)
    {
        $arr = $this->bsClosePut($token);
        if (PEAR::isError($arr)) {
            return $arr;
        }
        $fname = $arr['fname'];
        $owner = $arr['owner'];
        $res = $this->bsImportPlaylist($fname);
        if (file_exists($fname)) {
            @unlink($fname);
        }
        if (PEAR::isError($res)) {
            return $res;
        }
        return BasicStor::GunidFromId($res);
    }


    /**
     * Check whether a Playlist metafile with the given playlist ID exists.
     *
     * @param string $sessid
     * 		session ID
     * @param string $playlistId
     * 		playlist global unique ID
     * @return boolean
     */
    public function existsPlaylist($sessid, $playlistId)
    {
        return $this->existsFile($sessid, $playlistId, 'playlist');
    }


    /**
     * Check whether a Playlist metafile with the given playlist ID
     * is available for editing, i.e., exists and is not marked as
     * being edited.
     *
     * @param string $sessid
     * 		session ID
     * @param string $playlistId
     * 		playlist global unique ID
     * @param boolean $getUid
     * 		flag for returning editedby uid
     * @return boolean
     */
    public function playlistIsAvailable($sessid, $playlistId, $getUid=FALSE)
    {
        $ex = $this->existsPlaylist($sessid, $playlistId);
        if (PEAR::isError($ex)) {
            return $ex;
        }
        if (!$ex) {
            return PEAR::raiseError(
                'LocStor::playlistIsAvailable: playlist not exists'
            );
        }
        $ie = $this->isEdited($playlistId);
        if ($ie === FALSE) {
            return TRUE;
        }
        if ($getUid) {
            return $ie;
        }
        return FALSE;
    }


    /* ------------------------------------------------------- render methods */
    /**
     * Render playlist to ogg file (open handle)
     *
     * @param string $sessid
     * 		session id
     * @param string $plid
     * 		 playlist gunid
     * @return hasharray
     *      token: string - render token
     */
    protected function renderPlaylistToFileOpen($sessid, $plid)
    {
        require_once("Renderer.php");
        $r = Renderer::rnRender2FileOpen($this, $plid);
        if (PEAR::isError($r)) {
            return $r;
        }
        return $r;
    }


    /**
     * Render playlist to ogg file (check results)
     *
     * @param string $token
     * 		render token
     * @return hasharray:
     *      status : string - success | working | fault
     *      url : string - readable url
     */
    protected function renderPlaylistToFileCheck($token)
    {
        require_once("Renderer.php");
        $r = Renderer::rnRender2FileCheck($this, $token);
        if (PEAR::isError($r)) {
            return $r;
        }
        return array('status'=>$r['status'], 'url'=>$r['url']);
    }


    /**
     * Render playlist to ogg file (close handle)
     *
     * @param string $token
     * 		render token
     * @return boolean status
     */
    protected function renderPlaylistToFileClose($token)
    {
        require_once("Renderer.php");
        $r = Renderer::rnRender2FileClose($this, $token);
        if (PEAR::isError($r)) {
            return $r;
        }
        return array(TRUE);
    }


    /**
     * Render playlist to storage media clip (open handle)
     *
     * @param string $sessid
     * 		session id
     * @param string $plid
     * 		playlist gunid
     * @return string
     * 		render token
     */
    protected function renderPlaylistToStorageOpen($sessid, $plid)
    {
        require_once("Renderer.php");
        $owner = Alib::GetSessUserId($sessid);
        if (PEAR::isError($owner)) {
            return $owner;
        }
        $r = Renderer::rnRender2FileOpen($this, $plid, $owner);
        if (PEAR::isError($r)) {
            return $r;
        }
        return $r;
    }


    /**
     * Render playlist to storage media clip (check results)
     *
     * @param string $token
     * 		render token
     * @return hasharray:
     *      status : string - success | working | fault
     *      gunid  : string - gunid of result file
     */
    protected function renderPlaylistToStorageCheck($token)
    {
        require_once("Renderer.php");
        $r = Renderer::rnRender2StorageCheck($this, $token);
        if (PEAR::isError($r)) {
            return $r;
        }
        return $r;
    }


    /**
     * Render playlist to RSS file (open handle)
     *
     * @param string $sessid
     * 		session id
     * @param string $plid
     * 		 playlist gunid
     * @return string
     * 		render token
     */
    protected function renderPlaylistToRSSOpen($sessid, $plid)
    {
        global $CC_CONFIG;
        $token = '123456789abcdeff';
        $fakeFile = $CC_CONFIG['accessDir']."/$token.rss";
        file_put_contents($fakeFile, "fake rendered file");
        return array('token'=>$token);
    }


    /**
     * Render playlist to RSS file (check results)
     *
     * @param string $token
     * 		render token
     * @return hasharray :
     *      status : string - success | working | fault
     *      url    : string - readable url
     */
    protected function renderPlaylistToRSSCheck($token)
    {
        $fakeFile = $CC_CONFIG['accessDir']."/$token.rss";
        if ($token != '123456789abcdeff' || !file_exists($fakeFile)) {
            return PEAR::raiseError(
                "LocStor::renderPlaylistToRSSCheck: invalid token ($token)"
            );
        }
        $fakeFUrl = BasicStor::GetUrlPart()."access/$token.rss";
        return array(
            'status'=> 'success',
            'url'   => $fakeFUrl,
        );
    }


    /**
     * Render playlist to RSS file (close handle)
     *
     * @param string $token
     * 		render token
     * @return boolean
     * 		status
     */
    protected function renderPlaylistToRSSClose($token)
    {
        if ($token != '123456789abcdeff') {
            return PEAR::raiseError(
                "LocStor::renderPlaylistToRSSClose: invalid token"
            );
        }
        $fakeFile = $CC_CONFIG['accessDir']."/$token.rss";
        unlink($fakeFile);
        return TRUE;
    }


    /*================================================= storage admin methods */

    /* ------------------------------------------------------- backup methods */

    /**
     * Create backup of storage (open handle)
     *
     * @param string $sessid
     * 		session id
     * @param array $criteria
     * 		see search criteria
     * @return array
     *           token  : string - backup token
     */
    protected function createBackupOpen($sessid, $criteria='')
    {
        require_once("Backup.php");
        $bu = new Backup($this);
        if (PEAR::isError($bu)) {
            return $bu;
        }
        $r = $bu->openBackup($sessid,$criteria);
        if ($r === FALSE) {
            return PEAR::raiseError(
                "LocStor::createBackupOpen: false returned from Backup"
            );
        }
        return $r;
    }


    /**
     * Create backup of storage (check results)
     *
     * @param string $token
     * 		backup token
     * @return hasharray
     * 		with field:
     *      status : string - susccess | working | fault
     *      faultString: string - description of fault
     *      token  : stirng - backup token
     *      url    : string - access url
     */
    protected function createBackupCheck($token)
    {
        require_once("Backup.php");
        $bu = new Backup($this);
        if (PEAR::isError($bu)) {
            return $bu;
        }
        return $bu->checkBackup($token);
    }


    /**
     * Create backup of storage (list results)
     *
     * @param string $sessid
     * 		session id
     * @param status $stat
     *      if this parameter is not set, then return with all unclosed backups
     * @return array
     * 		array of hasharray with field:
     *      status : string - susccess | working | fault
     *      token  : stirng - backup token
     *      url    : string - access url
     */
    protected function createBackupList($sessid, $stat='')
    {
        require_once("Backup.php");
        $bu = new Backup($this);
        if (PEAR::isError($bu)) {
            return $bu;
        }
        return $bu->listBackups($stat);
    }


    /**
     * Create backup of storage (close handle)
     *
     * @param string $token
     * 		backup token
     * @return boolean
     * 		status
     */
    protected function createBackupClose($token)
    {
        require_once("Backup.php");
        $bu = new Backup($this);
        if (PEAR::isError($bu)) {
            return $bu;
        }
        return $bu->closeBackup($token);
    }


    /* ------------------------------------------------------ restore methods */

    /**
     * Restore a backup file (open handle)
     *
     * @param string $sessid
     * 		session id
     * @param string $chsum
     * 		md5 checksum of imported file
     * @return array
     * 		array with:
     *      url string: writable URL
     *      fname string: writable local filename
     *      token string: PUT token
     */
    protected function restoreBackupOpen($sessid, $chsum)
    {
        $userid = Alib::getSessUserId($sessid);
        if (PEAR::isError($userid)) {
            return $userid;
        }
        $r = $this->bsOpenPut($chsum, NULL, $userid);
        if (PEAR::isError($r)) {
            return $r;
        }
        return $r;
    }


    /**
     * Restore a backup file (close put handle)
     *
     * @param string $sessid
     * 		session id
     * @param string $token
     * 		"put" token
     * @return string $token
     * 		restore token
     */
    protected function restoreBackupClosePut($sessid, $token) {
        $arr = $this->bsClosePut($token);
        if (PEAR::isError($arr)) {
            return $arr;
        }
        $fname = $arr['fname'];
        require_once('Restore.php');
        $rs = new Restore($this);
        if (PEAR::isError($rs)) {
            return $rs;
        }
        return $rs->openRestore($sessid, $fname);
    }


    /**
     * Restore a backup file (check state)
     *
     * @param string $token
     * 		restore token
     * @return array
     * 		status - fields:
     * 		token:  string - restore token
     *      status: string - working | fault | success
     *      faultString: string - description of fault
     */
    protected function restoreBackupCheck($token)
    {
        require_once('Restore.php');
        $rs = new Restore($this);
        if (PEAR::isError($rs)) {
            return $rs;
        }
        return $rs->checkRestore($token);
    }


    /**
     * Restore a backup file (close handle)
     *
     * @param string $token
     * 		restore token
     * @return array
     * 		status - fields:
     * 		token:  string - restore token
     *      status: string - working | fault | success
     */
    protected function restoreBackupClose($token) {
    	require_once('Restore.php');
    	$rs = new Restore($this);
    	if (PEAR::isError($rs)) {
    	    return $rs;
    	}
    	return $rs->closeRestore($token);
    }


    /*===================================================== auxiliary methods */
    /**
     * Dummy method - only returns Campcaster version
     *
     * @return string
     */
    public static function getVersion()
    {
        return CAMPCASTER_VERSION;
    }

    /**
     * Open upload transport (from station to hub)
     *
     * @param string $sessid
     * 		session id
     * @param string $chsum
     * 		checksum
     * @return array
     * 		hasharray with:
     *      url string: writable URL
     *      token string: PUT token
     */
    function uploadOpen($sessid, $chsum)
    {
        $owner = Alib::GetSessUserId($sessid);
        if (PEAR::isError($owner)) {
        	return $owner;
        }
        $res = $this->bsOpenPut($chsum, NULL, $owner);
        if (PEAR::isError($res)) {
        	return $res;
        }
        return array('url'=>$res['url'], 'token'=>$res['token']);
    }


    /**
     * Close upload transport
     *
     * @param string $token
     * 		transport token
     * @param string $trtype
     * 		transport type
     * @param array $pars
     * 		transport parameters
     * @return mixed
     */
    function uploadClose($token, $trtype, $pars=array())
    {
        $res = $this->bsClosePut($token);
        if (PEAR::isError($res)) {
        	return $res;
        }
        extract($res);  // fname, owner
        switch ($trtype) {
            case "audioclip":
                $mdtoken = $pars['mdpdtoken'];
                $res = $this->bsClosePut($mdtoken);
                if (PEAR::isError($res)) {
                	return $res;
                }
                $mdfname = $res['fname'];
                if ($gunid == '') {
                	$gunid = NULL;
                }
                $values = array(
                    "filename" => $pars['name'],
                    "filepath" => $fname,
                    "metadata" => $mdfname,
                    "gunid" => $pars['gunid'],
                    "filetype" => "audioclip"
                );
                $storedFile = $this->bsPutFile($values);
                if (PEAR::isError($storedFile)) {
                	return $storedFile;
                }
                $res = $storedFile->getId();
                @unlink($fname);
                @unlink($mdfname);
                break;
            case "playlist":
                if ($gunid == '') {
                	$gunid = NULL;
                }
                $values = array(
                    "filename" => $pars['name'],
                    "metadata" => $fname,
                    "gunid" => $pars['gunid'],
                    "filetype" => "playlist"
                );
                $storedFile = $this->bsPutFile($values);
                if (PEAR::isError($storedFile)) {
                	return $storedFile;
                }
                $res = $storedFile->getId();
                @unlink($fname);
                break;
            case "playlistPkg":
                $chsum = md5_file($fname);
                // importPlaylistOpen:
                $res = $this->bsOpenPut($chsum, NULL, $owner);
                if (PEAR::isError($res)) {
                	return $res;
                }
                $dest = $res['fname'];
                $token = $res['token'];
                copy($fname, $dest);
                $r = $this->importPlaylistClose($token);
                if (PEAR::isError($r)) {
                	return $r;
                }
                @unlink($fname);
                return $r;
                break;
            case "searchjob":
                $crits = file_get_contents($fname);
                $criteria = unserialize($crits);
                @unlink($fname);
                $results = $this->localSearch($criteria);
                if (PEAR::isError($results)) {
                	return $results;
                }
                $realfile = tempnam($this->accessDir, 'searchjob_');
                @chmod($realfile, 0660);
                $len = file_put_contents($realfile, serialize($results));
                $acc = BasicStor::bsAccess($realfile, '', NULL, 'download');
                if (PEAR::isError($acc)) {
                	return $acc;
                }
                $url = BasicStor::GetUrlPart()."access/".basename($acc['fname']);
                $chsum = md5_file($realfile);
                $size = filesize($realfile);
                $res = array(
                    'url'=>$url, 'token'=>$acc['token'],
                    'chsum'=>$chsum, 'size'=>$size,
                    'filename'=>$filename
                );
                return $res;
                break;
            case "metadata":
                break;
            default:
        }
        return $res;
    }


    /**
     * Open download transport
     *
     * @param string $sessid
     * 		session id
     * @param string $trtype
     * 		transport type
     * @param array $pars
     * 		transport parameters
     * @return hasharray with:
     *      url string: writable URL
     *      token string: PUT token
     */
    function downloadOpen($sessid, $trtype, $pars=array())
    {
        global $CC_CONFIG;
        switch ($trtype) {
            case "unknown":
            case "audioclip":
            case "metadata":
            case "playlist":
            case "playlistPkg":
                if (!isset($pars['gunid'])) {
                    return PEAR::raiseError("Archive::downloadOpen: gunid not set");
                }
                break;
        }
        $gunid = $pars['gunid'];
        // resolve trtype by object type:
        if ( ($trtype == 'unknown') || ($trtype == 'playlistPkg') ) {
            $trtype2 = BasicStor::GetType($gunid);
            if (PEAR::isError($trtype2)) {
            	return $trtype2;
            }
            // required with content:
            $trtype = ( ($trtype2 == 'playlist') && ($trtype == 'playlistPkg') ?
                'playlistPkg' : $trtype2);
			//return PEAR::raiseError("Archive::downloadOpen: TT=$trtype TT2=$trtype2 G=$gunid");
        }
        switch ($trtype) {
            case "audioclip":
                $res = $this->downloadRawAudioDataOpen($sessid, $gunid);
                break;
            case "metadata":
                $res = $this->downloadMetadataOpen($sessid, $gunid);
                break;
            case "playlist":
                $res = $this->accessPlaylist($sessid, $gunid);
                break;
            case "playlistPkg":
                $res = $this->bsExportPlaylistOpen($gunid);
                if (PEAR::isError($res)) {
                	return $res;
                }
                $tmpn = tempnam($CC_CONFIG['transDir'], 'plExport_');
                $plfpath = "$tmpn.lspl";
                copy($res['fname'], $plfpath);
                $res = $this->bsExportPlaylistClose($res['token']);
                if (PEAR::isError($res)) {
                	return $res;
                }
                $fname = "transported_playlist.lspl";
                $id = BasicStor::IdFromGunid($gunid);
                $acc = BasicStor::bsAccess($plfpath, 'lspl', NULL, 'download');
                if (PEAR::isError($acc)) {
                	return $acc;
                }
                $url = BasicStor::GetUrlPart()."access/".basename($acc['fname']);
                $chsum = md5_file($plfpath);
                $size = filesize($plfpath);
                $res = array(
                    'url'=>$url, 'token'=>$acc['token'],
                    'chsum'=>$chsum, 'size'=>$size,
                    'filename'=>$fname
                );
                break;
            case "searchjob":
                $res = $pars;
                break;
            case "file":
                $res = array();
                break;
            default:
                return PEAR::raiseError("Archive::downloadOpen: NotImpl ($trtype)");
        }
        if (PEAR::isError($res)) {
        	return $res;
        }
        switch ($trtype) {
            case "audioclip":
            case "metadata":
            case "playlist":
            case "playlistPkg":
                $title = $this->bsGetTitle(NULL, $gunid);
                break;
            case "searchjob":
            	$title = 'searchjob';
            	break;
            case "file":
            	$title = 'regular file';
            	break;
            default:
        }
        $res['title'] = $title;
        $res['trtype'] = $trtype;
        return $res;
    }


    /**
     * Close download transport
     *
     * @param string $token
     * 		transport token
     * @param string $trtype
     * 		transport type
     * @return array
     * 		hasharray with:
     *      url string: writable URL
     *      token string: PUT token
     */
    function downloadClose($token, $trtype)
    {
        switch ($trtype) {
            case "audioclip":
                $res = $this->downloadRawAudioDataClose($token);
                if (PEAR::isError($res)) {
                	return $res;
                }
                return $res;
            case "metadata":
                $res = $this->downloadMetadataClose($token);
                return $res;
            case "playlist":
                $res = $this->releasePlaylist(NULL/*$sessid*/, $token);
                return $res;
            case "playlistPkg":
                $res = BasicStor::bsRelease($token, 'download');
                if (PEAR::isError($res)) {
                	return $res;
                }
                $realFname = $r['realFname'];
                @unlink($realFname);
                if (preg_match("|(plExport_[^\.]+)\.lspl$|", $realFname, $va)) {
                    list(,$tmpn) = $va;
                    $tmpn = $CC_CONFIG['transDir']."/$tmpn";
                    if (file_exists($tmpn)) {
                    	@unlink($tmpn);
                    }
                }
                return $res;
            case "searchjob":
                $res = BasicStor::bsRelease($token, 'download');
                return $res;
            case "file":
                return array();
            default:
                return PEAR::raiseError("Archive::downloadClose: NotImpl ($trtype)");
        }
    }


    /**
     * Prepare hub initiated transport
     *
     * @param string $target
     * 		hostname of transport target
     * @param string $trtype
     * 		transport type
     * @param string $direction
     * 		'up' | 'down'
     * @param array $pars
     * 		transport parameters
     * @return mixed
     */
    function prepareHubInitiatedTransfer(
        $target, $trtype='file', $direction='up',$pars=array())
    {
        $tr = new Transport($this);
        $trec = TransportRecord::create($tr, $trtype, $direction,
            array_merge($pars, array('target'=>$target)));
        if (PEAR::isError($trec)) {
        	return $trec;
        }
        return TRUE;
    }


    /**
     * List hub initiated transports
     *
     * @param string $target
     * 		hostname of transport target
     * @param string $direction
     * 		'up' | 'down'
     * @param string $trtok
     * 		transport token
     * @return mixed
     */
    function listHubInitiatedTransfers(
        $target=NULL, $direction=NULL, $trtok=NULL)
    {
        $tr = new Transport($this);
        $res = $tr->getTransports($direction, $target, $trtok);
        return $res;
    }


    /**
     * Set state of hub initiated transport
     *
     * @param string $target
     * 		hostname of transport target
     * @param string $trtok
     * 		transport token
     * @param string $state
     * 		transport state
     * @return TransportRecord|PEAR_Error
     */
    function setHubInitiatedTransfer($target, $trtok, $state)
    {
        $tr = new Transport($this);
        $trec = TransportRecord::recall($tr, $trtok);
        if (PEAR::isError($trec)) {
        	return $trec;
        }
        $r = $trec->setState($state);
        if (PEAR::isError($r)) {
        	return $r;
        }
        return $trec;
    }

    /* ==================================================== auxiliary methods */

} // class LocStor
?>