<?php
/*
 * Format of search criteria: hash, 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>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>
 *  <p>
 *  Format of search/browse results: hash, with following structure:<br>
 *   <ul>
 *      <li>results : array of gunids have found</li>
 *      <li>cnt : integer - number of matching items</li>
 *   </ul>
 *
 */
define('GBERR_DENY', 40);
define('GBERR_FILEIO', 41);
define('GBERR_FILENEX', 42);
define('GBERR_FOBJNEX', 43);
define('GBERR_WRTYPE', 44);
define('GBERR_NONE', 45);
define('GBERR_AOBJNEX', 46);
define('GBERR_NOTF', 47);
define('GBERR_SESS', 48);
define('GBERR_PREF', 49);
define('GBERR_TOKEN', 50);
define('GBERR_PUT', 51);
define('GBERR_LOCK', 52);
define('GBERR_GUNID', 53);
define('GBERR_BGERR', 54);
define('GBERR_NOTIMPL', 69);

require_once(dirname(__FILE__)."/Alib.php");
require_once(dirname(__FILE__)."/StoredFile.php");
require_once(dirname(__FILE__)."/Transport.php");
require_once(dirname(__FILE__)."/Playlist.php");

//$g_metadata_xml_to_db_mapping = array(
//        "dc:format" => "format",
//        "ls:bitrate" => "bit_rate",
//    	"ls:samplerate" => "sample_rate",
//        "dcterms:extent" => "length",
//		"dc:title" => "track_title",
//		"dc:description" => "comments",
//    	"dc:type" => "genre",
//    	"dc:creator" => "artist_name",
//        "dc:source" => "album_title",
//    	"ls:channels" => "channels",
//		"ls:filename" => "name",
//		"ls:year" => "year",
//    	"ls:url" => "url",
//    	"ls:track_num" => "track_number",
//        "ls:mood" => "mood",
//        "ls:bpm" => "bpm",
//        "ls:disc_num" => "disc_number",
//        "ls:rating" => "rating",
//        "ls:encoded_by" => "encoded_by",
//        "dc:publisher" => "label",
//        "ls:composer" => "composer",
//        "ls:encoder" => "encoder",
//        "ls:crc" => "checksum",
//        "ls:lyrics" => "lyrics",
//        "ls:orchestra" => "orchestra",
//        "ls:conductor" => "conductor",
//        "ls:lyricist" => "lyricist",
//        "ls:originallyricist" => "original_lyricist",
//        "ls:radiostationname" => "radio_station_name",
//        "ls:audiofileinfourl" => "info_url",
//        "ls:artisturl" => "artist_url",
//        "ls:audiosourceurl" => "audio_source_url",
//        "ls:radiostationurl" => "radio_station_url",
//        "ls:buycdurl" => "buy_this_url",
//        "ls:isrcnumber" => "isrc_number",
//        "ls:catalognumber" => "catalog_number",
//        "ls:originalartist" => "original_artist",
//        "dc:rights" => "copyright",
//        "dcterms:temporal" => "report_datetime",
//        "dcterms:spatial" => "report_location",
//        "dcterms:entity" => "report_organization",
//        "dc:subject" => "subject",
//        "dc:contributor" => "contributor",
//        "dc:language" => "language");

/**
 * Core of Campcaster file storage module
 *
 * @package Campcaster
 * @subpackage StorageServer
 * @copyright 2010 Sourcefabric O.P.S.
 * @license http://www.gnu.org/licenses/gpl.txt
 * @see Alib
 */
class BasicStor {
    public $storId;
    private $fileTypes;

    public function __construct()
    {
        $this->filetypes = array(
            'all'=>NULL,
            'audioclip'=>'audioclip',
            'webstream'=>'webstream',
            'playlist'=>'playlist',
        );
    }


    /**
     * Store new file in the storage
     *
     * @param array $p_values
     * 		See StoredFile::Insert() for details.
     * @param boolean $copyMedia
     * 		copy the media file if true, make symlink if false
     * @return StoredFile|PEAR_Error
     *      The StoredFile that was created.
     */
    //    public function bsPutFile($p_values, $p_copyMedia=TRUE)
    //    {
    //        $storedFile = StoredFile::Insert($p_values, $p_copyMedia);
    //        return $storedFile;
    //    }


    /**
     * Rename file
     *
     * @param int $id
     * 		Virtual file's local id
     * @param string $newName
     * @return boolean|PEAR_Error
     */
    //    public function bsRenameFile($id, $newName)
    //    {
    //        switch (BasicStor::GetObjType($id)) {
    //            case "audioclip":
    //            case "playlist":
    //            case "webstream":
    //                $storedFile = StoredFile::Recall($id);
    //                if (is_null($storedFile) || PEAR::isError($storedFile)) {
    //                    // catch nonerror exception:
    //                    //if($storedFile->getCode() != GBERR_FOBJNEX)
    //                    return $storedFile;
    //                }
    //                $res = $storedFile->setName($newName);
    //                if (PEAR::isError($res)) {
    //                    return $res;
    //                }
    //                break;
    //            case "File":
    //            default:
    //        }
    //        return TRUE;
    //    }


    /**
     * Replace file. Doesn't change filetype!
     *
     * @param int $id
     * 		Virtual file's local id
     * @param string $localFilePath
     * 		Local path of media file
     * @param string $metadataFilePath
     * 		Local path of metadata file
     * @param string $mdataLoc
     * 		'file'|'string'
     * @return true|PEAR_Error
     * @exception PEAR::error
     */
    //    public function bsReplaceFile($id, $localFilePath, $metadataFilePath, $mdataLoc='file')
    //    {
    //        $storedFile = StoredFile::Recall($id);
    //        if (is_null($storedFile) || PEAR::isError($storedFile)) {
    //            return $storedFile;
    //        }
    //        if (!empty($metadataFilePath) &&
    //                ($mdataLoc!='file' || file_exists($metadataFilePath))) {
    //            $r = $storedFile->setMetadata($metadataFilePath, $mdataLoc);
    //            if (PEAR::isError($r)) {
    //                return $r;
    //            }
    //        }
    //        if (!empty($localFilePath) && file_exists($localFilePath)) {
    //            $r = $storedFile->setRawMediaData($localFilePath);
    //            if (PEAR::isError($r)) {
    //                return $r;
    //            }
    //        }
    //        return TRUE;
    //    }


    /**
     * Delete file
     *
     * @param int $id
     * 		Virtual file's local id
     * @param boolean $forced
     * 		If true don't use trash
     * @return true|PEAR_Error
     */
    //    public function bsDeleteFile($id, $forced=FALSE)
    //    {
    //        global $CC_CONFIG;
    //        // full delete:
    //        if (!$CC_CONFIG['useTrash'] || $forced) {
    //            $res = BasicStor::RemoveObj($id, $forced);
    //            return $res;
    //        }
    //
    //        $storedFile = StoredFile::Recall($id);
    //
    //        if (is_null($storedFile) || PEAR::isError($storedFile)) {
    //            return $storedFile;
    //        }
    //        if ($storedFile->isAccessed()) {
    //             return PEAR::raiseError(
    //                'Cannot delete an object that is currently accessed.'
    //            );
    //        }
    //        // move to trash:
    //        switch (BasicStor::GetObjType($id)) {
    //
    //            case "audioclip":
    //                $playLists = $storedFile->getPlaylists();
    //                $item_gunid = $storedFile->getGunid();
    //                if( $playLists != NULL) {
    //
    //                    foreach($playLists as $key=>$val) {
    //                        $playList_id = BasicStor::IdFromGunidBigInt($val["gunid"]);
    //                        $playList_titles[] = BasicStor::bsGetMetadataValue($playList_id, "dc:title");
    //                    }
    //                    return PEAR::raiseError(
    //                        'Please remove this song from all playlists: ' . join(",", $playList_titles)
    //                    );
    //                }
    //                break;
    //
    //            case "playlist":
    //                if($storedFile->isScheduled()) {
    //                     return PEAR::raiseError(
    //                        'Cannot delete an object that is scheduled to play.'
    //                    );
    //                }
    //                break;
    //
    //            case "webstream":
    //
    //                break;
    //            default:
    //        }
    //
    //        $res = $storedFile->setState('deleted');
    //        if (PEAR::isError($res)) {
    //            return $res;
    //        }
    //
    //	    return TRUE;
    //    }


    /* ----------------------------------------------------- put, access etc. */
    /**
     * Check validity of access/put token
     *
     * @param string $token
     * 		Access/put token
     * @param string $type
     * 		'put'|'access'|'download'
     * @return boolean
     */
    public static function bsCheckToken($token, $type='put')
    {
        global $CC_CONFIG, $CC_DBC;
        $cnt = $CC_DBC->getOne("
            SELECT count(token) FROM ".$CC_CONFIG['accessTable']."
            WHERE token=x'{$token}'::bigint AND type='$type'
        ");
        if (PEAR::isError($cnt)) {
            return FALSE;
        }
        return ($cnt == 1);
    }


    /**
     * Create and return access link to real file
     *
     * @param string $realFname
     * 		Local filepath to accessed file
     *      (NULL for only increase access counter, no symlink)
     * @param string $ext
     * 		Useful filename extension for accessed file
     * @param int $gunid
     * 		Global unique id
     *      (NULL for special files such exported playlists)
     * @param string $type
     * 		'access'|'download'
     * @param int $parent
     * 		parent token (recursive access/release)
     * @param int $owner
     * 		Local user id - owner of token
     * @return array
     * 		array with: seekable filehandle, access token
     */
    public static function bsAccess($realFname, $ext, $gunid, $type='access',
    $parent='0', $owner=NULL)
    {
        global $CC_CONFIG, $CC_DBC;
        if (!is_null($gunid)) {
            $gunid = StoredFile::NormalizeGunid($gunid);
        }
        $token = StoredFile::CreateGunid();
        if (!is_null($realFname)) {
            $linkFname = $CC_CONFIG['accessDir']."/$token.$ext";
            //broken links are ignored by the player, do not worry about it here
            /*            if (!is_file($realFname) && !is_link($realFname)) {
            return PEAR::raiseError(
            "BasicStor::bsAccess: real file not found ($realFname)",
            GBERR_FILEIO);
            }
            */
            if (! @symlink($realFname, $linkFname)) {
                return PEAR::raiseError(
                    "BasicStor::bsAccess: symlink create failed ($linkFname)",
                GBERR_FILEIO);
            }
        } else {
            $linkFname = NULL;
        }
        $escapedExt = pg_escape_string($ext);
        $escapedType = pg_escape_string($type);
        $CC_DBC->query("BEGIN");
        $gunidSql = (is_null($gunid) ? "NULL" : "x'{$gunid}'::bigint" );
        $ownerSql = (is_null($owner) ? "NULL" : "$owner" );
        $res = $CC_DBC->query("
            INSERT INTO ".$CC_CONFIG['accessTable']."
                (gunid, token, ext, type, parent, owner, ts)
            VALUES
                ($gunidSql, x'$token'::bigint,
                '$escapedExt', '$escapedType', x'{$parent}'::bigint, $ownerSql, now())
        ");
        if (PEAR::isError($res)) {
            $CC_DBC->query("ROLLBACK");
            return $res;
        }
        if (!is_null($gunid)) {
            $res = $CC_DBC->query("
                UPDATE ".$CC_CONFIG['filesTable']."
                SET currentlyAccessing=currentlyAccessing+1, mtime=now()
                WHERE gunid=x'{$gunid}'::bigint
            ");
        }
        if (PEAR::isError($res)) {
            $CC_DBC->query("ROLLBACK");
            return $res;
        }
        $res = $CC_DBC->query("COMMIT");
        if (PEAR::isError($res)) {
            return $res;
        }
        return array('fname'=>$linkFname, 'token'=>$token);
    }


    /**
     * Release access link to real file
     *
     * @param string $token
     * 		Access token
     * @param string $type
     * 		'access'|'download'
     * @return array
     *      gunid: string, global unique ID or real pathname of special file
     *      owner: int, local subject id of token owner
     *      realFname: string, real local pathname of accessed file
     */
    public static function bsRelease($token, $type='access')
    {
        global $CC_CONFIG, $CC_DBC;
        if (!BasicStor::bsCheckToken($token, $type)) {
            return PEAR::raiseError(
             "BasicStor::bsRelease: invalid token ($token)"
            );
        }
        $acc = $CC_DBC->getRow("
            SELECT to_hex(gunid)as gunid, ext, owner FROM ".$CC_CONFIG['accessTable']."
            WHERE token=x'{$token}'::bigint AND type='$type'
        ");
        if (PEAR::isError($acc)) {
            return $acc;
        }
        $ext = $acc['ext'];
        $owner = $acc['owner'];
        $linkFname = $CC_CONFIG['accessDir']."/$token.$ext";
        $realFname = readlink($linkFname);
        if (file_exists($linkFname)) {
            if(! @unlink($linkFname)){
                return PEAR::raiseError(
                    "BasicStor::bsRelease: unlink failed ($linkFname)",
                GBERR_FILEIO);
            }
        }
        $CC_DBC->query("BEGIN");
        if (!is_null($acc['gunid'])) {
            $gunid = StoredFile::NormalizeGunid($acc['gunid']);
            $res = $CC_DBC->query("
                UPDATE ".$CC_CONFIG['filesTable']."
                SET currentlyAccessing=currentlyAccessing-1, mtime=now()
                WHERE gunid=x'{$gunid}'::bigint AND currentlyAccessing>0
            ");
            if (PEAR::isError($res)) {
                $CC_DBC->query("ROLLBACK");
                return $res;
            }
        }
        $res = $CC_DBC->query("
            DELETE FROM ".$CC_CONFIG['accessTable']." WHERE token=x'$token'::bigint
        ");
        if (PEAR::isError($res)) {
            $CC_DBC->query("ROLLBACK");
            return $res;
        }
        $res = $CC_DBC->query("COMMIT");
        if (PEAR::isError($res)) {
            return $res;
        }
        $res = array(
            'gunid' => (isset($gunid) ? $gunid : NULL ),
            'realFname' => $realFname,
            'owner' => $owner,
        );
        return $res;
    }


    /**
     * Create and return downloadable URL for file
     *
     * @param int $id
     * 		Virtual file's local id
     * @param string $part
     * 		'media'|'metadata'
     * @param int $parent
     * 		parent token (recursive access/release)
     * @return array
     * 		array with strings:
     *      downloadable URL, download token, chsum, size, filename
     */
    //    public function bsOpenDownload($id, $part='media')
    //    {
    //        $storedFile = StoredFile::Recall($id);
    //        if (is_null($storedFile) || PEAR::isError($storedFile)) {
    //            return $storedFile;
    //        }
    //        $gunid = $storedFile->gunid;
    //        switch ($part) {
    //            case "media":
    //                $realfile = $storedFile->getRealFileName();
    //                $ext = $storedFile->getFileExtension();
    //                $filename = $storedFile->getName();
    //                break;
    //            case "metadata":
    //                $realfile = $storedFile->getRealMetadataFileName();
    //                $ext = "xml";
    //                $filename = $storedFile->getName();
    //                break;
    //            default:
    //                return PEAR::raiseError(
    //                 "BasicStor::bsOpenDownload: unknown part ($part)"
    //                );
    //        }
    //        $acc = BasicStor::bsAccess($realfile, $ext, $gunid, 'download');
    //        if (PEAR::isError($acc)) {
    //            return $acc;
    //        }
    //        $url = BasicStor::GetUrlPart()."access/".basename($acc['fname']);
    //        $chsum = md5_file($realfile);
    //        $size = filesize($realfile);
    //        return array(
    //            'url'=>$url, 'token'=>$acc['token'],
    //            'chsum'=>$chsum, 'size'=>$size,
    //            'filename'=>$filename
    //        );
    //    }


    /**
     * Discard downloadable URL
     *
     * @param string $token
     * 		Download token
     * @param string $part
     * 		'media'|'metadata'
     * @return string
     * 		gunid
     */
    //    public function bsCloseDownload($token, $part='media')
    //    {
    //        if (!BasicStor::bsCheckToken($token, 'download')) {
    //            return PEAR::raiseError(
    //             "BasicStor::bsCloseDownload: invalid token ($token)"
    //            );
    //        }
    //        $r = BasicStor::bsRelease($token, 'download');
    //        if (PEAR::isError($r)){
    //            return $r;
    //        }
    //        return (is_null($r['gunid']) ? $r['realFname'] : $r['gunid']);
    //    }


    /**
     * Create writable URL for HTTP PUT method file insert
     *
     * @param string $chsum
     * 		md5sum of the file having been put
     * @param string $gunid
     * 		global unique id
     *      (NULL for special files such imported playlists)
     * @param int $owner
     * 		local user id - owner of token
     * @return array
     * 		array with:
     *      url string: writable URL
     *      fname string: writable local filename
     *      token string: PUT token
     */
    //    public function bsOpenPut($chsum, $gunid, $owner=NULL)
    //    {
    //        global $CC_CONFIG, $CC_DBC;
    //        if (!is_null($gunid)) {
    //            $gunid = StoredFile::NormalizeGunid($gunid);
    //        }
    //        $escapedChsum = pg_escape_string($chsum);
    //        $token = StoredFile::CreateGunid();
    //        $res = $CC_DBC->query("DELETE FROM ".$CC_CONFIG['accessTable']
    //            ." WHERE token=x'$token'::bigint");
    //        if (PEAR::isError($res)) {
    //            return $res;
    //        }
    //        $gunidSql = (is_null($gunid) ? "NULL" : "x'{$gunid}'::bigint" );
    //        $ownerSql = (is_null($owner) ? "NULL" : "$owner" );
    //        $res = $CC_DBC->query("
    //            INSERT INTO ".$CC_CONFIG['accessTable']."
    //                (gunid, token, ext, chsum, type, owner, ts)
    //            VALUES
    //                ($gunidSql, x'$token'::bigint,
    //                    '', '$escapedChsum', 'put', $ownerSql, now())");
    //        if (PEAR::isError($res)) {
    //            return $res;
    //        }
    //        $fname = $CC_CONFIG['accessDir']."/$token";
    //        touch($fname);      // is it needed?
    //        $url = BasicStor::GetUrlPart()."xmlrpc/put.php?token=$token";
    //        return array('url'=>$url, 'fname'=>$fname, 'token'=>$token);
    //    }


    /**
     * Get file from writable URL and return local filename.
     * Caller should move or unlink this file.
     *
     * @param string $token
     * 		PUT token
     * @return array
     * 		hash with fields:
     *      fname string, local path of the file having been put
     *      owner int, local subject id - owner of token
     */
    //    public function bsClosePut($token)
    //    {
    //        global $CC_CONFIG, $CC_DBC;
    //        $token = StoredFile::NormalizeGunid($token);
    //
    //        if (!BasicStor::bsCheckToken($token, 'put')) {
    //            return PEAR::raiseError(
    //                "BasicStor::bsClosePut: invalid token ($token)",
    //                GBERR_TOKEN);
    //        }
    //        $row = $CC_DBC->getRow(
    //            "SELECT chsum, owner FROM ".$CC_CONFIG['accessTable']
    //            ." WHERE token=x'{$token}'::bigint");
    //        if (PEAR::isError($row)) {
    //            return $row;
    //        }
    //        $fname = $CC_CONFIG['accessDir']."/$token";
    //        $md5sum = md5_file($fname);
    //
    //        $chsum = $row['chsum'];
    //        $owner = $row['owner'];
    //        $error = null;
    //        if ( (trim($chsum) != '') && ($chsum != $md5sum) ) {
    //            // Delete the file if the checksums do not match.
    //            if (file_exists($fname)) {
    //                @unlink($fname);
    //            }
    //            $error = new PEAR_Error(
    //                 "BasicStor::bsClosePut: md5sum does not match (token=$token)".
    //                 " [$chsum/$md5sum]",
    //                 GBERR_PUT);
    //        } else {
    //            // Remember the MD5 sum
    //            $storedFile = StoredFile::RecallByToken($token);
    //            if (!is_null($storedFile) && !PEAR::isError($storedFile)) {
    //                $storedFile->setMd5($md5sum);
    //            } else {
    //#                $error = $storedFile;
    //            }
    //        }
    //
    //        // Delete entry from access table.
    //        $res = $CC_DBC->query("DELETE FROM ".$CC_CONFIG['accessTable']
    //            ." WHERE token=x'$token'::bigint");
    //        if (PEAR::isError($error)) {
    //            return $error;
    //        } elseif (PEAR::isError($res)) {
    //            return $res;
    //        }
    //
    //        return array('fname'=>$fname, 'owner'=>$owner);
    //    }


    /**
     * Check uploaded file
     *
     * @param string $token
     * 		"Put" token
     * @return array
     * 		hash, (
     *      status: boolean,
     *      size: int - filesize
     *      expectedsum: string - expected checksum
     *      realsum: string - checksum of uploaded file
     *   	)
     */
    //    public function bsCheckPut($token)
    //    {
    //        global $CC_CONFIG, $CC_DBC;
    //        if (!BasicStor::bsCheckToken($token, 'put')) {
    //            return PEAR::raiseError(
    //             "BasicStor::bsCheckPut: invalid token ($token)"
    //            );
    //        }
    //        $chsum = $CC_DBC->getOne("
    //            SELECT chsum FROM ".$CC_CONFIG['accessTable']."
    //            WHERE token=x'{$token}'::bigint
    //            ");
    //        if (PEAR::isError($chsum)) {
    //            return $chsum;
    //        }
    //        $fname = $CC_CONFIG['accessDir']."/$token";
    //        $md5sum = md5_file($fname);
    //        $size = filesize($fname);
    //        $status = ($chsum == $md5sum);
    //        return array(
    //            'status'=>$status, 'size'=>$size,
    //            'expectedsum'=>$chsum,
    //            'realsum'=>$md5sum,
    //        );
    //    }


    /**
     * Return starting part of storageServer URL
     *
     * @return string
     * 		URL
     */
    //    public static function GetUrlPart()
    //    {
    //        global $CC_CONFIG;
    //        $host = $CC_CONFIG['storageUrlHost'];
    //        $port = $CC_CONFIG['storageUrlPort'];
    //        $path = $CC_CONFIG['storageUrlPath'];
    //        return "http://$host:$port$path/";
    //    }


    /**
     * Get tokens by type
     *
     * @param string $type
     * 		access|put|render etc.
     * @return array
     * 		array of tokens
     */
    //    public static function GetTokensByType($type)
    //    {
    //        global $CC_CONFIG, $CC_DBC;
    //        $res = $CC_DBC->query(
    //            "SELECT TO_HEX(token) AS token FROM ".$CC_CONFIG['accessTable']." WHERE type=?",
    //            array($type));
    //        while ($row = $res->fetchRow()) {
    //             $r[] = $row['token'];
    //        }
    //        return $r;
    //    }


    /* ----------------------------------------------------- metadata methods */

    /**
     * Replace metadata with new XML file or string
     *
     * @param int $id
     * 		Virtual file's local id
     * @param string $mdata
     * 		Local path of metadata XML file
     * @param string $mdataLoc
     * 		'file'|'string'
     * @return boolean|PEAR_Error
     */
    //    public function bsReplaceMetadata($id, $mdata, $mdataLoc='file')
    //    {
    //        $storedFile = StoredFile::Recall($id);
    //        if (is_null($storedFile) || PEAR::isError($storedFile)) {
    //            return $storedFile;
    //        }
    //        return $storedFile->setMetadata($mdata, $mdataLoc);
    //    }


    /**
     * Get metadata as XML string
     *
     * @param int $id
     * 		Virtual file's local id
     * @return string|PEAR_Error
     */
    //    public function bsGetMetadata($id)
    //    {
    //        $storedFile = StoredFile::Recall($id);
    //        if (is_null($storedFile) || PEAR::isError($storedFile)) {
    //            return $storedFile;
    //        }
    //        return $storedFile->getMetadata();
    //    }


    /**
     * Get dc:title (if exists)
     *
     * @param int $id
     * 		Virtual file's local id
     * @param string $gunid
     * 		Virtual file's gunid, optional, used only if not
     *      null, id is then ignored
     * @return string|PEAR_Error
     */
    //    public function bsGetTitle($id, $gunid=NULL)
    //    {
    //        if (is_null($gunid)) {
    //            $storedFile = StoredFile::Recall($id);
    //        } else {
    //            $storedFile = StoredFile::RecallByGunid($gunid);
    //        }
    //        if (is_null($storedFile) || PEAR::isError($storedFile)) {
    //            return $storedFile;
    //        }
    //        $r = $storedFile->md["title"];
    //        $title = (empty($r) ? 'unknown' : $r);
    //        return $title;
    //    }


    /**
     * Get metadata element value
     *
     * @param int $id
     * 		Virtual file's local id
     * @param string|array|null $category
     * 		metadata element name, or array of metadata element names,
     * 		if null is passed, all metadata values for the given ID will
     * 		be fetched.
     * @return string|array
     * 		If a string is passed in for $category, a string is returned,
     * 		if an array is passed, an array is returned.
     * @see Metadata::getMetadataValue
     */
    //    public function bsGetMetadataValue($id, $category = null)
    //    {
    //        if (!is_numeric($id)) {
    //            return null;
    //        }
    //        $storedFile = StoredFile::Recall($id);
    //        if (is_null($storedFile) || PEAR::isError($storedFile)) {
    //            return $storedFile;
    //        }
    //        if (is_null($category)) {
    //            return $storedFile->md;
    //        } elseif (is_array($category)) {
    //            $values = array();
    //			      foreach ($category as $tmpCat) {
    //				        $values[$tmpCat] = $storedFile->md[$tmpCat];
    //			      }
    //			      return $values;
    //        } else {
    //            return $storedFile->md[$category];
    //        }
    //    }


    /**
     * Convert XML name to database column name.  Used for backwards compatibility
     * with old code.
     *
     * @param string $p_category
     * @return string|null
     */
    //    public static function xmlCategoryToDbColumn($p_category)
    //    {
    //        global $g_metadata_xml_to_db_mapping;
    //        if (array_key_exists($p_category, $g_metadata_xml_to_db_mapping)) {
    //            return $g_metadata_xml_to_db_mapping[$p_category];
    //        }
    //        return null;
    //    }


    /**
     * Convert database column name to XML name.
     *
     * @param string $p_dbColumn
     * @return string|null
     */
    //    public static function dbColumnToXmlCatagory($p_dbColumn)
    //    {
    //        global $g_metadata_xml_to_db_mapping;
    //        $str = array_search($p_dbColumn, $g_metadata_xml_to_db_mapping);
    //        // make return value consistent with xmlCategoryToDbColumn()
    //        if ($str === FALSE) {
    //            $str = null;
    //        }
    //        return $str;
    //    }

    /**
     * Set metadata element value
     *
     * @param int|StoredFile $id
     * 		Database ID of file
     * @param string $category
     * 		Metadata element identification (e.g. dc:title)
     * @param string $value
     * 		value to store, if NULL then delete record
     * @return boolean
     */
    //    public static function bsSetMetadataValue($p_id, $p_category, $p_value)
    //    {
    //      global $CC_CONFIG, $CC_DBC;
    //      if (!is_string($p_category) || is_array($p_value)) {
    //        return FALSE;
    //      }
    //      if (is_a($p_id, "StoredFile")) {
    //        $p_id  = $p_id->getId();
    //      }
    //      if ($p_category == 'dcterms:extent') {
    //        $p_value = BasicStor::NormalizeExtent($p_value);
    //      }
    //      $columnName = BasicStor::xmlCategoryToDbColumn($p_category); // Get column name
    //
    //      if (!is_null($columnName)) {
    //        $escapedValue = pg_escape_string($p_value);
    //        $sql = "UPDATE ".$CC_CONFIG["filesTable"]
    //             ." SET $columnName='$escapedValue'"
    //             ." WHERE id=$p_id";
    //        //var_dump($sql);
    //        $res = $CC_DBC->query($sql);
    //        if (PEAR::isError($res)) {
    //          return $res;
    //        }
    //      }
    //      return TRUE;
    //    }


    /**
     * Normalize time value to hh:mm:ss:dddddd format
     *
     * @param mixed $v
     * 		value to normalize
     * @return string
     */
    //    private static function NormalizeExtent($v)
    //    {
    //        if (!preg_match("|^\d{2}:\d{2}:\d{2}.\d{6}$|", $v)) {
    //            $s = Playlist::playlistTimeToSeconds($v);
    //            $t = Playlist::secondsToPlaylistTime($s);
    //            return $t;
    //        }
    //        return $v;
    //    }


    /**
     * Set metadata values in 'batch' mode
     *
     * @param int|StoredFile $id
     * 		Database ID of file or StoredFile object
     * @param array $values
     * 		array of key/value pairs
     *      (e.g. 'dc:title'=>'New title')
     * @return boolean
     */
    //    public static function bsSetMetadataBatch($id, $values)
    //    {
    //      global $CC_CONFIG, $CC_DBC;
    //      if (!is_array($values)) {
    //          $values = array($values);
    //      }
    //      if (count($values) == 0) {
    //        return true;
    //      }
    //      if (is_a($id, "StoredFile")) {
    //          $storedFile =& $id;
    //      } else {
    //          $storedFile = StoredFile::Recall($id);
    //          if (is_null($storedFile) || PEAR::isError($storedFile)) {
    //              return $storedFile;
    //          }
    //      }
    //      foreach ($values as $category => $oneValue) {
    //        $columnName = BasicStor::xmlCategoryToDbColumn($category);
    //        if (!is_null($columnName)) {
    //          if ($category == 'dcterms:extent') {
    //            $oneValue = BasicStor::NormalizeExtent($oneValue);
    //          }
    //          // Since track_number is an integer, you cannot set
    //          // it to be the empty string, so we NULL it instead.
    //          if ($columnName == 'track_number' && empty($oneValue)) {
    //            $sqlPart = "$columnName = NULL";
    //          } elseif (($columnName == 'length') && (strlen($oneValue) > 8)) {
    //            // Postgres doesnt like it if you try to store really large hour
    //            // values.  TODO: We need to fix the underlying problem of getting the
    //            // right values.
    //            $parts = explode(':', $oneValue);
    //            $hour = intval($parts[0]);
    //            if ($hour > 24) {
    //              continue;
    //            } else {
    //              $sqlPart = "$columnName = '$oneValue'";
    //            }
    //          } else {
    //            $escapedValue = pg_escape_string($oneValue);
    //            $sqlPart = "$columnName = '$escapedValue'";
    //          }
    //          $sqlValues[] = $sqlPart;
    //        }
    //      }
    //      if (count($sqlValues)==0) {
    //        return TRUE;
    //      }
    //      $sql = "UPDATE ".$CC_CONFIG["filesTable"]
    //           ." SET ".join(",", $sqlValues)
    //           ." WHERE id=$id";
    //      $CC_DBC->query($sql);
    //      return TRUE;
    //    }

    /**
     * Method returning array with where-parts of sql queries
     *
     * @param array $conditions
     * 		See 'conditions' field in search criteria format
     *      definition in class documentation
     * @return array
     * 		array of strings - WHERE-parts of SQL queries
     */
    private function _makeWhereArr($conditions)
    {
        $ops = array('full'=>"='%s'", 'partial'=>"ILIKE '%%%s%%'",
            'prefix'=>"ILIKE '%s%%'", '<'=>"< '%s'", '='=>"= '%s'",
            '>'=>"> '%s'", '<='=>"<= '%s'", '>='=>">= '%s'"
            );
            $whereArr = array();
            if (is_array($conditions)) {
                foreach ($conditions as $cond) {
                    $columnName = StoredFile::xmlCategoryToDbColumn($cond['cat']);
                    $op = strtolower($cond['op']);
                    $value = $cond['val'];
                    if (!empty($value)) {
                        $splittedQn = XML_Util::splitQualifiedName($catQn);
                        $catNs = $splittedQn['namespace'];
                        $cat = $splittedQn['localPart'];
                        $opVal = sprintf($ops[$op], pg_escape_string($value));
                        // retype for timestamp value
                        if ($cat == 'mtime') {
                            switch ($op) {
                                case 'partial':
                                case 'prefix':
                                    break;
                                default:
                                    $retype = "::timestamp with time zone";
                                    $opVal = "$retype $opVal$retype";
                            }
                        }
                        $sqlCond = " {$columnName} {$opVal}\n";
                        $whereArr[] = $sqlCond;
                    }
                }
            }
            return $whereArr;
    }

    /**
     * Search in local metadata database.
     *
     * @param array $criteria
     * 	 has the 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>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>
     * @param int $limit
     * 		limit for result arrays (0 means unlimited)
     * @param int $offset
     * 		starting point (0 means without offset)
     * @return array
     * 		array of hashes, fields:
     *       cnt : integer - number of matching gunids
     *              of files have been found
     *       results : array of hashes:
     *          gunid: string
     *          type: string - audioclip | playlist | webstream
     *          title: string - dc:title from metadata
     *          creator: string - dc:creator from metadata
     *          source: string - dc:source from metadata
     *          length: string - dcterms:extent in extent format
     */
    public function bsLocalSearch($criteria, $limit=0, $offset=0)
    {
        global $CC_CONFIG, $CC_DBC;

        // Input values
        $filetype = (isset($criteria['filetype']) ? $criteria['filetype'] : 'all');
        $filetype = strtolower($filetype);
        if (!array_key_exists($filetype, $this->filetypes)) {
            return PEAR::raiseError(__FILE__.":".__LINE__.': unknown filetype in search criteria');
        }
        $filetype = $this->filetypes[$filetype];
        $operator = (isset($criteria['operator']) ? $criteria['operator'] : 'and');
        $operator = strtolower($operator);
        $conditions = (isset($criteria['conditions']) ? $criteria['conditions'] : array());

        // Create the WHERE clause - this is the actual search part
        $whereArr = $this->_makeWhereArr($conditions);

        // Metadata values to fetch
        $metadataNames = array('dc:creator', 'dc:source', 'ls:track_num', 'dc:title', 'dcterms:extent');

        // Order by clause
        $orderby = TRUE;
        $orderByAllowedValues = array('dc:creator', 'dc:source', 'dc:title', 'dcterms:extent', "ls:track_num");
        $orderByDefaults = array('dc:creator', 'dc:source', 'dc:title');
        if ((!isset($criteria['orderby']))
        || (is_array($criteria['orderby']) && (count($criteria['orderby'])==0))) {
            // default ORDER BY
            // PaulB: track number removed because it doesnt work yet because
            // if track_num is not an integer (e.g. bad metadata like "1/20",
            // or if the field is blank) the SQL statement gives an error.
            //$orderbyQns  = array('dc:creator', 'dc:source', 'ls:track_num', 'dc:title');
            $orderbyQns = $orderByDefaults;
        } else {
            // ORDER BY clause is given in the parameters.

            // Convert the parameter to an array if it isnt already.
            $orderbyQns = $criteria['orderby'];
            if (!is_array($orderbyQns)) {
                $orderbyQns = array($orderbyQns);
            }

            // Check that it has valid ORDER BY values, if not, revert
            // to the default ORDER BY values.
            foreach ($orderbyQns as $metadataTag) {
                if (!in_array($metadataTag, $orderByAllowedValues)) {
                    $orderbyQns = $orderByDefaults;
                    break;
                }
            }
        }

        $descA = (isset($criteria['desc']) ? $criteria['desc'] : NULL);
        if (!is_array($descA)) {
            $descA = array($descA);
        }

        $orderBySql = array();
        // $dataName contains the names of the metadata columns we want to
        // fetch.  It is indexed numerically starting from 1, and the value
        // in the array is the qualified name with ":" replaced with "_".
        // e.g. "dc:creator" becomes "dc_creator".
        foreach ($orderbyQns as $xmlTag) {
            $columnName = StoredFile::xmlCategoryToDbColumn($xmlTag);
            $orderBySql[] = $columnName;
        }

        // Build WHERE clause
        $whereClause = "";
        if (!is_null($filetype)) {
            $whereClause .= "WHERE (ftype='$filetype')";
        }
        else {
            $whereClause .= "WHERE (ftype is NOT NULL)";
        }
        if (count($whereArr) != 0) {
            if ($operator == 'and') {
                $whereClause .= " AND ((".join(") AND (", $whereArr)."))";
            } else {
                $whereClause .= " AND ((".join(") OR (", $whereArr)."))";
            }
        }

        // Final query

        //"dcterms:extent" => "length",
        //"dc:title" => "track_title",
        //"dc:creator" => "artist_name",
        //dc:description

        global $g_metadata_xml_to_db_mapping;
        $plSelect = "SELECT ";
        $fileSelect = "SELECT ";
        $_SESSION["br"] = "";
        foreach ($g_metadata_xml_to_db_mapping as $key => $val){
            $_SESSION["br"] .= "key: ".$key." value:".$val.", ";
            if($key === "dc:title"){
                $plSelect .= "name AS ".$val.", ";
                $fileSelect .= $val.", ";
            }
            else if ($key === "dc:creator"){
                $plSelect .= "creator AS ".$val.", ";
                $fileSelect .= $val.", ";
            }
            else if ($key === "dcterms:extent"){
                $plSelect .= "length, ";
                $fileSelect .= "length, ";
            }
            else if ($key === "dc:description"){
                $plSelect .= "text(description) AS ".$val.", ";
                $fileSelect .= $val.", ";
            }
            else {
                $plSelect .= "NULL AS ".$val.", ";
                $fileSelect .= $val.", ";
            }
        }

        $sql = "SELECT * FROM ((".$plSelect."PL.id, 'playlist' AS ftype
                FROM ".$CC_CONFIG["playListTable"]." AS PL
				LEFT JOIN ".$CC_CONFIG['playListTimeView']." PLT ON PL.id = PLT.id)

                UNION

                (".$fileSelect."id, ftype FROM ".$CC_CONFIG["filesTable"]." AS FILES)) AS RESULTS ";

        $sql .= $whereClause;

        if ($orderby) {
            $sql .= " ORDER BY ".join(",", $orderBySql);
        }

        $_SESSION["debugsql"] = $sql;

        $res = $CC_DBC->getAll($sql);
        if (PEAR::isError($res)) {
            return $res;
        }
        if (!is_array($res)) {
            $res = array();
        }

        $count = count($res);
        $_SESSION["br"] .= "  COUNT: ".$count;

        $res = array_slice($res, $offset != 0 ? $offset : 0, $limit != 0 ? $limit : 10);

        $eres = array();
        foreach ($res as $it) {
            $eres[] = array(
            	'id' => $it['id'],
                'type' => strtolower($it['ftype']),
                'title' => $it['track_title'],
                'creator' => $it['artist_name'],
                'duration' => $it['length'],
                'source' => $it['album_title'],
                'track_num' => $it['track_number'],
            );
        }
        return array('results'=>$eres, 'cnt'=>$count);
    }


    /**
     * Return values of specified metadata category
     *
     * @param string $category
     * 		metadata category name with or without namespace prefix (dc:title, author)
     * @param int $limit
     * 		limit for result arrays (0 means unlimited)
     * @param int $offset
     * 		starting point (0 means without offset)
     * @param array $criteria
     * 		see bsLocalSearch method
     * @return array
     * 		hash, fields:
     *       results : array with found values
     *       cnt : integer - number of matching values
     */
    public function bsBrowseCategory($category, $limit=0, $offset=0, $criteria=NULL)
    {
        global $CC_CONFIG, $CC_DBC;

        $pl_cat = array(
            "dcterms:extent" => "length",
    		"dc:title" => "name",
        	"dc:creator" => "creator",
        	"dc:description" => "description"
        	);

        	$category = strtolower($category);
        	$columnName = StoredFile::xmlCategoryToDbColumn($category);
        	if (is_null($columnName)) {
        	    return new PEAR_Error(__FILE__.":".__LINE__." -- could not map XML category to DB column.");
        	}
        	$sql = "SELECT DISTINCT $columnName FROM ".$CC_CONFIG["filesTable"];
        	$limitPart = ($limit != 0 ? " LIMIT $limit" : '' ).
        	($offset != 0 ? " OFFSET $offset" : '' );
        	$countRowsSql = "SELECT COUNT(DISTINCT $columnName) FROM ".$CC_CONFIG["filesTable"];

        	//$_SESSION["br"]  = "in Browse Category: ".$category;
        	$cnt = $CC_DBC->GetOne($countRowsSql);
        	if (PEAR::isError($cnt)) {
        	    return $cnt;
        	}
        	$res = $CC_DBC->getCol($sql.$limitPart);
        	if (PEAR::isError($res)) {
        	    return $res;
        	}
        	if (!is_array($res)) {
        	    $res = array();
        	}

        	if (array_key_exists($category, $pl_cat) && $category !== "dcterms:extent") {
        	    $columnName = $pl_cat[$category];

        	    $sql = "SELECT DISTINCT $columnName FROM ".$CC_CONFIG["playListTable"];
        	    $limitPart = ($limit != 0 ? " LIMIT $limit" : '' ).
        	    ($offset != 0 ? " OFFSET $offset" : '' );
        	    $countRowsSql = "SELECT COUNT(DISTINCT $columnName) FROM ".$CC_CONFIG["playListTable"];

        	    $pl_cnt = $CC_DBC->GetOne($countRowsSql);
        	    if (PEAR::isError($cnt)) {
        	        return $cnt;
        	    }
        	    $pl_res = $CC_DBC->getCol($sql.$limitPart);
        	    if (PEAR::isError($res)) {
        	        return $pl_res;
        	    }
        	    if (!is_array($pl_res)) {
        	        $pl_res = array();
        	    }

        	    $res = array_merge($res, $pl_res);
        	    $res = array_slice($res, 0, $limit);
        	    $cnt = $cnt + $pl_cnt;
        	}
        	else if ($category === "dcterms:extent") {
        	    $columnName = $pl_cat[$category];

        	    $limitPart = ($limit != 0 ? " LIMIT $limit" : '' ).
        	    ($offset != 0 ? " OFFSET $offset" : '' );

        	    $sql = "SELECT DISTINCT length AS $columnName FROM ".$CC_CONFIG["playListTimeView"];

        	    $countRowsSql = "SELECT COUNT(DISTINCT length) FROM ".$CC_CONFIG["playListTimeView"];

        	    $pl_cnt = $CC_DBC->GetOne($countRowsSql);
        	    if (PEAR::isError($cnt)) {
                	return $cnt;
        	    }
        	    $pl_res = $CC_DBC->getCol($sql.$limitPart);
        	    if (PEAR::isError($res)) {
                	return $pl_res;
        	    }
        	    if (!is_array($pl_res)) {
                	$pl_res = array();
        	    }

        	    $res = array_merge($res, $pl_res);
        	    $res = array_slice($res, 0, $limit);
        	    $cnt = $cnt + $pl_cnt;
        	}

        	return array('results'=>$res, 'cnt'=>$cnt);
    }


    /* ---------------------------------------------------- methods4playlists */

    /**
     * Create a tarfile with playlist export - playlist and all matching
     * sub-playlists and media files (if desired)
     *
     * @param array $plids
     * 		Array of strings, playlist global unique IDs (one gunid is accepted too)
     * @param string $type
     * 		Playlist format, possible values: lspl | smil | m3u
     * @param boolean $withContent
     * 		if true, export related files too
     * @return array
     * 		hasharray with  fields:
     *      fname string: readable fname,
     *      token string: access token
     */
    //    public function bsExportPlaylistOpen($plids, $type='lspl', $withContent=TRUE)
    //    {
    //        global $CC_CONFIG;
    //        if (!is_array($plids)) {
    //            $plids = array($plids);
    //        }
    //        $gunids = array();
    //        foreach ($plids as $plid) {
    //            $pl = StoredFile::RecallByGunid($plid);
    //            if (is_null($pl) || PEAR::isError($pl)) {
    //                return $pl;
    //            }
    //            if ($withContent) {
    //                $gunidsX = $pl->export();
    //                if (PEAR::isError($gunidsX)) {
    //                    return $gunidsX;
    //                }
    //            } else {
    //                $gunidsX = array(array('gunid'=>$plid, 'type'=>'playlist'));
    //            }
    //            $gunids = array_merge($gunids, $gunidsX);
    //        }
    //        $plExts = array('lspl'=>"lspl", 'smil'=>"smil", 'm3u'=>"m3u");
    //        $plExt = (isset($plExts[$type]) ? $plExts[$type] : "xml" );
    //        $res = array();
    //        $tmpn = tempnam($CC_CONFIG['bufferDir'], 'plExport_');
    //        $tmpf = "$tmpn.tar";
    //        $tmpd = "$tmpn.dir";
    //        mkdir($tmpd);
    //        $tmpdp = "$tmpn.dir/playlist";
    //        mkdir($tmpdp);
    //        if ($withContent) {
    //            $tmpdc = "$tmpn.dir/audioClip";
    //            mkdir($tmpdc);
    //        }
    //        foreach ($gunids as $i => $it) {
    //            $storedFile = StoredFile::RecallByGunid($it['gunid']);
    //            if (is_null($storedFile) || PEAR::isError($storedFile)) {
    //                return $storedFile;
    //            }
    ////            $MDfname = $storedFile->md->getFileName();
    //            $MDfname = $storedFile->md["name"];
    //            if (PEAR::isError($MDfname)) {
    //                return $MDfname;
    //            }
    //            if (file_exists($MDfname)) {
    //                switch ($it['type']) {
    //	                case "playlist":
    //	                    $storedFile = $r = StoredFile::RecallByGunid($it['gunid']);
    //	                    switch ($type) {
    //	                        case "smil":
    //	                            $string = $r = $storedFile->outputToSmil();
    //	                            break;
    //	                        case "m3u":
    //	                            $string = $r = $storedFile->outputToM3u();
    //	                            break;
    //	                        default:
    ////	                            $string = $r = $storedFile->md->genXmlDoc();
    //	                    }
    //	                    if (PEAR::isError($r)) {
    //	                        return $r;
    //	                    }
    //	                    $r = BasicStor::WriteStringToFile($string, "$tmpdp/{$it['gunid']}.$plExt");
    //	                    if (PEAR::isError($r)) {
    //	                        return $r;
    //	                    }
    //	                    break;
    //	                default:
    //	                    copy($MDfname, "$tmpdc/{$it['gunid']}.xml"); break;
    //                } // switch
    //            } // if file_exists()
    //            $RADfname = $storedFile->getRealFileName();
    //            if (PEAR::isError($RADfname)) {
    //                return $RADfname;
    //            }
    //            $RADext = $storedFile->getFileExtension();
    //            if (PEAR::isError($RADext)) {
    //                return $RADext;
    //            }
    //            if (file_exists($RADfname)) {
    //                copy($RADfname, "$tmpdc/{$it['gunid']}.$RADext");
    //            }
    //        }
    //        if (count($plids)==1) {
    //            copy("$tmpdp/$plid.$plExt", "$tmpd/exportedPlaylist.$plExt");
    //        }
    //        $res = `cd $tmpd; tar cf $tmpf * --remove-files`;
    //        @rmdir($tmpdc);
    //        @rmdir($tmpdp);
    //        @rmdir($tmpd);
    //        unlink($tmpn);
    //        $acc = BasicStor::bsAccess($tmpf, 'tar', NULL/*gunid*/, 'access');
    //        if (PEAR::isError($acc)) {
    //            return $acc;
    //        }
    //        return $acc;
    //    }


    /**
     * Close playlist export previously opened by the bsExportPlaylistOpen
     * method
     *
     * @param string $token
     * 		Access token obtained from bsExportPlaylistOpen method call.
     * @return true/PEAR_Error
     */
    //    public function bsExportPlaylistClose($token)
    //    {
    //        $r = BasicStor::bsRelease($token, 'access');
    //        if (PEAR::isError($r)) {
    //            return $r;
    //        }
    //        $file = $r['realFname'];
    //        if (file_exists($file)) {
    //            if(! @unlink($file)){
    //                return PEAR::raiseError(
    //                    "BasicStor::bsExportPlaylistClose: unlink failed ($file)",
    //                    GBERR_FILEIO);
    //            }
    //        }
    //        return TRUE;
    //    }


    /**
     * Import playlist in LS Archive format
     *
     * @param string $plid
     * 		Playlist gunid
     * @param string $aPath
     * 		Absolute path part of imported file (e.g. /home/user/campcaster)
     * @param string $rPath
     * 		Relative path/filename part of imported file (e.g. playlists/playlist_1.smil)
     * @param string $ext
     * 		Playlist extension (determines type of import)
     * @param array $gunids
     * 		Hash relation from filenames to gunids
     * @param int $subjid
     * 		Local subject (user) id (id of user doing the import)
     * @return int
     * 		Result file local id (or error object)
     */
    //    public function bsImportPlaylistRaw($plid, $aPath, $rPath, $ext, &$gunids, $subjid)
    //    {
    //        $id = BasicStor::IdFromGunid($plid);
    //        if (!is_null($id)) {
    //            return $id;
    //        }
    //        $path = realpath("$aPath/$rPath");
    //        if (FALSE === $path) {
    //            return PEAR::raiseError(
    //                "BasicStor::bsImportPlaylistRaw: file doesn't exist ($aPath/$rPath)"
    //            );
    //        }
    //        switch ($ext) {
    //            case "xml":
    //            case "lspl":
    //                $fname = $plid;
    //                $values = array(
    //                    "filename" => $fname,
    //                    "metadata" => $path,
    //                    "gunid" => $plid,
    //                    "filetype" => "playlist"
    //                );
    //                $storedFile = StoredFile::Insert($values);
    //                $res = $storedFile->getId();
    //                break;
    //            case "smil":
    //                require_once("SmilPlaylist.php");
    //                $res = SmilPlaylist::import($this, $aPath, $rPath, $gunids, $plid, $subjid);
    //                if (PEAR::isError($res)) {
    //                    break;
    //                }
    //                $res = $res->getId();
    //                break;
    //            case "m3u":
    //                require_once("M3uPlaylist.php");
    //                $res = M3uPlaylist::import($this, $aPath, $rPath, $gunids, $plid, $subjid);
    //                if (PEAR::isError($res)) {
    //                    break;
    //                }
    //                $res = $res->getId();
    //                break;
    //            default:
    //                $res = PEAR::raiseError(
    //                    "BasicStor::importPlaylistRaw: unknown playlist format".
    //                    " (gunid:$plid, format:$ext)"
    //                );
    //                break;
    //        }
    //        if (!PEAR::isError($res)) {
    //            $gunids[basename($rPath)] = $plid;
    //        }
    //        return $res;
    //    }


    /**
     * Import playlist in LS Archive format
     *
     * @param string $fpath
     * 		Imported file pathname
     * @param int $subjid
     * 		Local subject (user) id (id of user doing the import)
     * @return int
     * 		Result file local id (or error object)
     */
    //    public function bsImportPlaylist($fpath, $subjid)
    //    {
    //        global $CC_CONFIG;
    //        // untar:
    //        $tmpn = tempnam($CC_CONFIG['bufferDir'], 'plImport_');
    //        $tmpd = "$tmpn.dir";
    //        $tmpdc = "$tmpd/audioClip";
    //        $tmpdp = "$tmpd/playlist";
    //        mkdir($tmpd);
    //        $res = `cd $tmpd; tar xf $fpath`;
    //        // clips:
    //        $d = @dir($tmpdc);
    //        $entries = array();
    //        $gunids = array();
    //        if ($d !== false) {
    //            while (false !== ($entry = $d->read())) {
    //                if (preg_match("|^([0-9a-fA-F]{16})\.(.*)$|", $entry, $va)) {
    //                    list(,$gunid, $ext) = $va;
    //                    switch ($ext) {
    //                        case"xml":
    //                            $entries[$gunid]['metadata'] = $entry;
    //                            break;
    //                        default:
    //                            $entries[$gunid]['rawMedia'] = $entry;
    //                            $entries[$gunid]['rawMediaExt'] = $ext;
    //                            $gunids[$entry] = $gunid;
    //                            break;
    //                    }
    //                }
    //            }
    //            $d->close();
    //        }
    //        $res = TRUE;
    //        foreach ($entries as $gunid => $it) {
    //            $rawMedia = "$tmpdc/{$it['rawMedia']}";
    //            if (!file_exists($rawMedia)) {
    //                $rawMedia = NULL;
    //            }
    //            $metadata = "$tmpdc/{$it['metadata']}";
    //            if (!file_exists($metadata)) {
    //                $metadata = NULL;
    //            }
    //            $f = StoredFile::RecallByGunid($gunid);
    //            if (!PEAR::isError($f)) {
    //              $exists = $f->existsFile();
    //              if ( $exists ) {
    //                $res = $f->delete();
    //              }
    //            }
    //            if (!PEAR::isError($res) ) {
    //                $values = array(
    //                    "filename" => $gunid,
    //                    "filepath" => $rawMedia,
    //                    "metadata" => $metadata,
    //                    "gunid" => $gunid,
    //                    "filetype" => "audioclip"
    //                );
    //                $storedFile = StoredFile::Insert($values);
    //                $res = $storedFile->getId();
    //            }
    //            @unlink("$tmpdc/{$it['rawMedia']}");
    //            @unlink("$tmpdc/{$it['metadata']}");
    //            if (PEAR::isError($res)) {
    //                break;
    //            }
    //        }
    //        // playlists:
    //        $d = @dir($tmpdp);
    //        if ($d !== false) {
    //            while ((!PEAR::isError($res)) && false !== ($entry = $d->read())) {
    //                if (preg_match("|^([0-9a-fA-F]{16})\.(.*)$|", $entry, $va)) {
    //                    list(,$gunid, $ext) = $va;
    //                    $res = $this->bsImportPlaylistRaw($gunid,
    //                        $tmpdp, $entry, $ext, $gunids, $subjid);
    //                    unlink("$tmpdp/$entry");
    //                    if (PEAR::isError($res)) {
    //                        break;
    //                    }
    //                }
    //            }
    //            $d->close();
    //        }
    //        //@rmdir($tmpdc); @rmdir($tmpdp); @rmdir($tmpd);
    //        @system("rm -rf $tmpdc");
    //        @system("rm -rf $tmpdp");
    //        @system("rm -rf $tmpd");
    //        @unlink($tmpn);
    //        return $res;
    //    }


    /* --------------------------------------------------------- info methods */

    /**
     * Analyze media file for internal metadata information
     *
     * @param int $id
     * 		Virtual file's local id
     * @return array
     */
    //    public function bsAnalyzeFile($id)
    //    {
    //        $storedFile = StoredFile::Recall($id);
    //        if (is_null($storedFile) || PEAR::isError($storedFile)) {
    //            return $storedFile;
    //        }
    //        $ia = $storedFile->analyzeFile();
    //        return $ia;
    //    }


    /**
     * Check if file exists in the storage
     *
     * @param int $id
     * 		Local id
     * @param string $ftype
     * 		Internal file type
     * @param boolean $byGunid
     * 		select file by gunid (id is then ignored)
     * @return boolean
     */
    //    public function bsExistsFile($id, $ftype=NULL, $byGunid=FALSE)
    //    {
    //        if ($byGunid) {
    //            $storedFile = StoredFile::RecallByGunid($id);
    //        } else {
    //            $storedFile = StoredFile::Recall($id);
    //        }
    //        if (is_null($storedFile)) {
    //            return $storedFile;
    //        }
    //        if (PEAR::isError($storedFile)) {
    //            // catch some exceptions
    //            switch ($storedFile->getCode()) {
    //                case GBERR_FILENEX:
    //                case GBERR_FOBJNEX:
    //                    return FALSE;
    //                    break;
    //                default:
    //                	return $storedFile;
    //            }
    //        }
    //        $realFtype = BasicStor::GetType($storedFile->gunid);
    //        if (!is_null($ftype) && (
    //            (strtolower($realFtype) != strtolower($ftype))
    //            // webstreams are subset of audioclips
    //            && !($realFtype == 'webstream' && $ftype == 'audioclip')
    //        )) {
    //            return FALSE;
    //        }
    //        return TRUE;
    //    }


    /* ---------------------------------------------------- redefined methods */
    /**
     * Get object type by id.
     *
     * @param int $oid
     * 		Local object id
     * @return string|PEAR_Error
     */
    //    public static function GetObjType($p_id)
    //    {
    //		    $type = "unknown";
    //		    $f = StoredFile::Recall($p_id);
    //		    return $f->getType();

    //        $gunid = BasicStor::GunidFromId($oid);
    //        if (PEAR::isError($gunid)) {
    //            return $gunid;
    //        }
    //        $ftype = BasicStor::GetType($gunid);
    //        if (PEAR::isError($ftype)) {
    //            return $ftype;
    //        }
    //        if (!is_null($ftype)) {
    //            $type = $ftype;
    //        }
    //        return $type;
    //    }


    /**
     * Add new user
     *
     * @param string $login
     * @param string $pass
     * @param string $realname
     * @return int|PEAR_Error
     */
    public static function addSubj($login, $pass=NULL, $realname='')
    {
        global $CC_CONFIG;
        $uid = Subjects::AddSubj($login, $pass, $realname);
        if (PEAR::isError($uid)) {
            return $uid;
        }
        if (Subjects::IsGroup($uid) === FALSE) {
            $res = Alib::AddPerm($uid, '_all', '0', 'A');
            if (PEAR::isError($res)) {
                return $res;
            }
            $res = Subjects::AddSubjectToGroup($login, $CC_CONFIG['StationPrefsGr']);
            if (PEAR::isError($res)) {
                return $res;
            }
            //                $res = Subjects::AddSubjectToGroup($login, $CC_CONFIG['AllGr']);
            //                if (PEAR::isError($res)) {
            //                    return $res;
            //                }
            }
            return $uid;
    }


    /**
     * Remove user by login
     *
     * @param string $login
     * @return boolean|PEAR_Error
     */
    public function removeSubj($login)
    {
        global $CC_CONFIG, $CC_DBC;
        if (FALSE !== array_search($login, $CC_CONFIG['sysSubjs'])) {
            return $CC_DBC->raiseError(
                "BasicStor::removeSubj: cannot remove system user/group");
        }
        $uid = Subjects::GetSubjId($login);
        if (PEAR::isError($uid)) {
            return $uid;
        }
        $res = $CC_DBC->query("
            DELETE FROM ".$CC_CONFIG['accessTable']." WHERE owner=$uid
        ");
        if (PEAR::isError($res)) {
            return $res;
        }
        $res = Alib::RemoveSubj($login);
        if (PEAR::isError($res)) {
            return $res;
        }
        return TRUE;
    }


    /**
     * Authenticate and create session
     *
     * @param string $login
     * @param string $pass
     * @return boolean|sessionId|PEAR_Error
     */
    function login($login, $pass)
    {
        $r = Alib::Login($login, $pass);
        return $r;
    }


    /* ================================================== "protected" methods */
    /**
     * Check authorization - auxiliary method
     *
     * @param array $acts
     * 		Array of actions
     * @param array $pars
     * 		Array of parameters - e.g. ids
     * @param string $sessid
     * 		Session id
     * @return true|PEAR_Error
     */
    public static function Authorize($acts, $pars, $sessid='')
    {
        $userid = Alib::GetSessUserId($sessid);
        if (PEAR::isError($userid)) {
            return $userid;
        }
        if (is_null($userid)) {
            return PEAR::raiseError(
                "BasicStor::Authorize: invalid session", GBERR_DENY);
        }
        if (!is_array($pars)) {
            $pars = array($pars);
        }
        if (!is_array($acts)) {
            $acts = array($acts);
        }
        $perm = true;
        //        foreach ($acts as $i => $action) {
        //            $res = Alib::CheckPerm($userid, $action, $pars[$i]);
        //            if (PEAR::isError($res)) {
        //                return $res;
        //            }
        //            $perm = $perm && $res;
        //        }
        if ($perm) {
            return TRUE;
        }
        $adesc = "[".join(',',$acts)."]";
        return PEAR::raiseError(
            "BasicStor::$adesc: access denied", GBERR_DENY);
    }


    /**
     * Get local id from global id (in hex).
     *
     * @param string $p_gunid
     * 		Global id
     * @return int
     * 		Local id
     */
    //    public static function IdFromGunid($p_gunid)
    //    {
    //        global $CC_DBC;
    //        global $CC_CONFIG;
    //        return $CC_DBC->getOne("SELECT id FROM ".$CC_CONFIG['filesTable']." WHERE gunid=x'$p_gunid'::bigint");
    //    }

    /**
     * Get local id from global id (big int).
     *
     * @param string $p_gunid
     * 		Global id
     * @return int
     * 		Local id
     */
    //    public static function IdFromGunidBigInt($p_gunid)
    //    {
    //        global $CC_DBC;
    //        global $CC_CONFIG;
    //        return $CC_DBC->getOne("SELECT id FROM ".$CC_CONFIG['filesTable']." WHERE gunid='$p_gunid'");
    //    }


    /**
     * Get global id from local id
     *
     * @param int $p_id
     * 		Local id
     * @return string
     * 		Global id
     */
    //    public static function GunidFromId($p_id)
    //    {
    //        global $CC_CONFIG;
    //        global $CC_DBC;
    //        if (!is_numeric($p_id)) {
    //            return NULL;
    //        }
    //        $gunid = $CC_DBC->getOne("
    //            SELECT to_hex(gunid)as gunid FROM ".$CC_CONFIG['filesTable']."
    //            WHERE id='$p_id'
    //        ");
    //        if (PEAR::isError($gunid)) {
    //            return $gunid;
    //        }
    //        if (is_null($gunid)) {
    //            return NULL;
    //        }
    //        return StoredFile::NormalizeGunid($gunid);
    //    }


    /**
     * Get storage-internal file type
     *
     * @param string $p_gunid
     * 		Global unique id of file
     * @return string
     */
    //    public static function GetType($p_gunid)
    //    {
    //        global $CC_CONFIG;
    //        global $CC_DBC;
    //        $ftype = $CC_DBC->getOne("
    //            SELECT ftype FROM ".$CC_CONFIG['filesTable']."
    //            WHERE gunid=x'$p_gunid'::bigint
    //        ");
    //        return $ftype;
    //    }


    /**
     * Check gunid format
     *
     * @param string $p_gunid
     * 		Global unique ID
     * @return boolean
     */
    //    protected static function CheckGunid($p_gunid)
    //    {
    //        $res = preg_match("|^([0-9a-fA-F]{16})?$|", $p_gunid);
    //        return $res;
    //    }

    /**
     * Set playlist edit flag
     *
     * @param string $p_playlistId
     * 		Playlist unique ID
     * @param boolean $p_val
     * 		Set/clear of edit flag
     * @param string $p_sessid
     * 		Session id
     * @param int $p_subjid
     * 		Subject id (if sessid is not specified)
     * @return boolean
     * 		previous state
     */
    public function setEditFlag($p_playlistId, $p_val=TRUE, $p_sessid=NULL, $p_subjid=NULL)
    {
        if (!is_null($p_sessid)) {
            $p_subjid = Alib::GetSessUserId($p_sessid);
            if (PEAR::isError($p_subjid)) {
                return $p_subjid;
            }
        }
        $pl = Playlist::Recall($p_playlistId);
        if (is_null($pl) || PEAR::isError($pl)) {
            return $pl;
        }
        $state = $pl->getState();
        if ($p_val) {
            $r = $pl->setState('edited', $p_subjid);
        } else {
            $r = $pl->setState('ready', 'NULL');
        }
        if (PEAR::isError($r)) {
            return $r;
        }
        return ($state == 'edited');
    }


    /**
     * Check if playlist is marked as edited
     *
     * @param string $p_playlistId
     * 		Playlist global unique ID
     * @return FALSE|int
     * 		ID of user editing it
     */
    public function isEdited($p_playlistId)
    {
        $pl = Playlist::Recall($p_playlistId);
        if (is_null($pl) || PEAR::isError($pl)) {
            return $pl;
        }
        if (!$pl->isEdited($p_playlistId)) {
            return FALSE;
        }
        return $pl->isEditedBy($p_playlistId);
    }


    /* ---------------------------------------- redefined "protected" methods */
    /**
     * Copy virtual file.
     * Redefined from parent class.
     *
     * @return int
     * 		New object local id
     */
    //    protected static function CopyObj($id, $newParid, $after=NULL)
    //    {
    //        switch (BasicStor::GetObjType($id)) {
    //            case "audioclip":
    //            case "playlist":
    //            case "webstream":
    //                $storedFile = StoredFile::Recall($id);
    //                if (is_null($storedFile) || PEAR::isError($storedFile)) {
    //                    return $storedFile;
    //                }
    //                $ac2 = StoredFile::CopyOf($storedFile, $nid);
    //                //$ac2->setName(M2tree::GetObjName($nid));
    //                break;
    //            case "File":
    //            default:
    //        }
    //        return $nid;
    //    }


    /**
     * Remove virtual file.<br>
     * Redefined from parent class.
     *
     * @param int $id
     * 		Local id of removed object
     * @param boolean $forced
     * 		Unconditional delete
     * @return true|PEAR_Error
     */
    //    public static function RemoveObj($id, $forced=FALSE)
    //    {
    //        $ot = BasicStor::GetObjType($id);
    //        if (PEAR::isError($ot)) {
    //            return $ot;
    //        }
    //        switch ($ot) {
    //            case "audioclip":
    //            case "playlist":
    //            case "webstream":
    //                $storedFile = StoredFile::Recall($id);
    //                if (is_null($storedFile)) {
    //                    return TRUE;
    //                }
    //                if (PEAR::isError($storedFile)) {
    //                    return $storedFile;
    //                }
    //                if ($storedFile->isEdited() && !$forced) {
    //                    return PEAR::raiseError(
    //                        'BasicStor::RemoveObj(): is edited'
    //                    );
    //                }
    //                if ($storedFile->isAccessed() && !$forced) {
    //                    return PEAR::raiseError(
    //                        'BasicStor::RemoveObj(): is accessed'
    //                    );
    //                }
    //                $storedFile->delete();
    //                break;
    //            case "File":
    ////            case "Folder":
    ////            case "Replica":
    //                break;
    //            default:
    //                return PEAR::raiseError(
    //                    "BasicStor::bsDeleteFile: unknown obj type ($ot)"
    //                );
    //        }
    //        $res = Alib::RemoveObj($id);
    //        if (PEAR::isError($res)) {
    //            return $res;
    //        }
    //        return TRUE;
    //    }


    /* ========================================================= misc methods */
    /**
     * Write string to file
     *
     * @param string $str
     * 		string to be written to file
     * @param string $fname
     * 		pathname to file
     * @return TRUE|raiseError
     */
    private static function WriteStringToFile($p_str, $p_fname)
    {
        $fp = @fopen($p_fname, "w");
        if ($fp === FALSE) {
            return PEAR::raiseError(
                "BasicStor::WriteStringToFile: cannot open file ($p_fname)"
            );
        }
        fwrite($fp, $p_str);
        fclose($fp);
        return TRUE;
    }


    /* =============================================== test and debug methods */

    /**
     *
     *
     */
    public function debug($va)
    {
        echo"<pre>\n";
        print_r($va);
    }


    /**
     * deleteFiles
     *
     * @return void
     */
    //    private function deleteFiles()
    //    {
    //        global $CC_CONFIG, $CC_DBC;
    //        $ids = $CC_DBC->getAll("SELECT id FROM ".$CC_CONFIG['filesTable']);
    //        if (is_array($ids)) {
    //            foreach ($ids as $i => $item) {
    //              $f = StoredFile::Recall($item['id']);
    //              $f->delete();
    //            }
    //        }
    //    }


    /**
     * Aux logging for debug
     *
     * @param string $msg - log message
     */
    public function debugLog($msg)
    {
        global $CC_CONFIG, $CC_DBC;
        $fp = fopen($CC_CONFIG['storageDir']."/log", "a") or die("Can't write to log\n");
        fputs($fp, date("H:i:s").">$msg<\n");
        fclose($fp);
    }

    } // class BasicStor
    ?>