config = $config;
$this->filesTable = $config['tblNamePrefix'].'files';
$this->mdataTable = $config['tblNamePrefix'].'mdata';
$this->accessTable= $config['tblNamePrefix'].'access';
$this->storageDir = $config['storageDir'];
$this->bufferDir = $config['bufferDir'];
$this->transDir = $config['transDir'];
$this->accessDir = $config['accessDir'];
$this->dbc->setErrorHandling(PEAR_ERROR_RETURN);
$this->rootId = $this->getRootNode();
$this->storId = $this->wd =
$this->getObjId('StorageRoot', $this->rootId);
$this->dbc->setErrorHandling();
}
/**
* Create new folder
*
* @param parid int, parent id
* @param folderName string, name for new folder
* @return id of new folder
* @exception PEAR::error
*/
function bsCreateFolder($parid, $folderName)
{
return $this->addObj($folderName , 'Folder', $parid);
}
/**
* Store new file in the storage
*
* @param parid int, parent id
* @param fileName string, name for new file
* @param mediaFileLP string, local path of media file
* @param mdataFileLP string, local path of metadata file
* @param gunid string, global unique id OPTIONAL
* @param ftype string, internal file type
* @param mdataLoc string 'file'|'string' (optional)
* @return int
* @exception PEAR::error
*/
function bsPutFile($parid, $fileName, $mediaFileLP, $mdataFileLP,
$gunid=NULL, $ftype='unKnown', $mdataLoc='file')
{
$name = "$fileName";
$id = $this->addObj($name , $ftype, $parid);
$ac =& StoredFile::insert(
$this, $id, $name, $mediaFileLP, $mdataFileLP, $mdataLoc,
$gunid, $ftype
);
if($this->dbc->isError($ac)){
$res = $this->removeObj($id);
return $ac;
}
if($ftype == 'playlist') $ac->setMime('application/smil');
return $id;
}
/**
* Rename file
*
* @param id int, virt.file's local id
* @param newName string
* @return boolean or PEAR::error
*/
function bsRenameFile($id, $newName)
{
$parid = $this->getParent($id);
switch($this->getObjType($id)){
case"audioclip":
case"playlist":
case"webstream":
$ac =& StoredFile::recall($this, $id);
if($this->dbc->isError($ac)){
// catch nonerror exception:
//if($ac->getCode() != GBERR_FOBJNEX)
return $ac;
}
$res = $ac->rename($newName);
if($this->dbc->isError($res)) return $res;
break;
case"File":
default:
}
return $this->renameObj($id, $newName);
}
/**
* Move file
*
* @param id int, virt.file's local id
* @param did int, destination folder local id
* @return boolean or PEAR::error
*/
function bsMoveFile($id, $did)
{
$parid = $this->getParent($id);
if($this->getObjType($did) !== 'Folder')
return $this->dbc->raiseError(
"BasicStor::moveFile: destination is not folder ($did)",
GBERR_WRTYPE
);
switch($this->getObjType($id)){
case"audioclip":
case"playlist":
case"webstream":
case"File":
case"Folder":
return $this->moveObj($id, $did);
break;
default:
return $this->dbc->raiseError(
"BasicStor::moveFile: unsupported object to move, sorry.",
GBERR_WRTYPE
);
}
}
/**
* Copy file
*
* @param id int, virt.file's local id
* @param did int, destination folder local id
* @return boolean or PEAR::error
*/
function bsCopyFile($id, $did)
{
$parid = $this->getParent($id);
if($this->getObjType($did) !== 'Folder'){
return $this->dbc->raiseError(
'BasicStor::bsCopyFile: destination is not folder',
GBERR_WRTYPE
);
}
switch($this->getObjType($id)){
case"audioclip":
case"playlist":
case"webstream":
case"File":
case"Folder":
return $this->copyObj($id, $did);
break;
default:
return $this->dbc->raiseError(
"BasicStor::moveFile: unsupported object to copy, sorry.",
GBERR_WRTYPE
);
}
}
/**
* Delete file
*
* @param id int, virt.file's local id
* @param forced boolean, unconditional delete
* @return true or PEAR::error
*/
function bsDeleteFile($id, $forced=FALSE)
{
$res = $this->removeObj($id, $forced);
return $res;
}
/* ----------------------------------------------------- put, access etc. */
/**
* Check validity of asscess/put token
*
* @param token string, access/put token
* @param type string 'put'|'access'|'download'
* @return boolean
*/
function bsCheckToken($token, $type='put')
{
$cnt = $this->dbc->getOne("
SELECT count(token) FROM {$this->accessTable}
WHERE token=x'{$token}'::bigint AND type='$type'
");
if($this->dbc->isError($cnt)){ return FALSE; }
return ($cnt == 1);
}
/**
* Get gunid from token
*
* @param token string, access/put token
* @param type string 'put'|'access'|'download'
* @return string
*/
function _gunidFromToken($token, $type='put')
{
$acc = $this->dbc->getRow("
SELECT to_hex(gunid)as gunid, ext FROM {$this->accessTable}
WHERE token=x'{$token}'::bigint AND type='$type'
");
if($this->dbc->isError($acc)){ return $acc; }
$gunid = StoredFile::_normalizeGunid($acc['gunid']);
if($this->dbc->isError($gunid)){ return $gunid; }
return $gunid;
}
/**
* Create and return access link to real file
*
* @param realFname string, local filepath to accessed file
* (NULL for only increase access counter, no symlink)
* @param ext string, useful filename extension for accessed file
* @param gunid int, global unique id
* @param type string 'access'|'download'
* @return array with: seekable filehandle, access token
*/
function bsAccess($realFname, $ext, $gunid, $type='access')
{
$token = StoredFile::_createGunid();
if(!is_null($realFname)){
$linkFname = "{$this->accessDir}/$token.$ext";
if(!file_exists($realFname)){
return $this->dbc->raiseError(
"BasicStor::bsAccess: real file not found ($realFname)",
GBERR_FILEIO);
}
if(! @symlink($realFname, $linkFname)){
return $this->dbc->raiseError(
"BasicStor::bsAccess: symlink create failed ($linkFname)",
GBERR_FILEIO);
}
}
$this->dbc->query("BEGIN");
$res = $this->dbc->query("
INSERT INTO {$this->accessTable}
(gunid, token, ext, type, ts)
VALUES
(x'{$gunid}'::bigint, x'$token'::bigint,
'$ext', '$type', now())
");
if($this->dbc->isError($res)){
$this->dbc->query("ROLLBACK"); return $res; }
$res = $this->dbc->query("
UPDATE {$this->filesTable}
SET currentlyAccessing=currentlyAccessing+1
WHERE gunid=x'{$gunid}'::bigint
");
if($this->dbc->isError($res)){
$this->dbc->query("ROLLBACK"); return $res; }
$res = $this->dbc->query("COMMIT");
if($this->dbc->isError($res)){ return $res; }
return array('fname'=>$linkFname, 'token'=>$token);
}
/**
* Release access link to real file
*
* @param token string, access token
* @param type string 'access'|'download'
* @return string, global unique ID
*/
function bsRelease($token, $type='access')
{
if(!$this->bsCheckToken($token, $type)){
return $this->dbc->raiseError(
"BasicStor::bsRelease: invalid token ($token)"
);
}
$acc = $this->dbc->getRow("
SELECT to_hex(gunid)as gunid, ext FROM {$this->accessTable}
WHERE token=x'{$token}'::bigint AND type='$type'
");
if($this->dbc->isError($acc)){ return $acc; }
$ext = $acc['ext'];
$gunid = StoredFile::_normalizeGunid($acc['gunid']);
$linkFname = "{$this->accessDir}/$token.$ext";
if(file_exists($linkFname)) if(! @unlink($linkFname)){
return $this->dbc->raiseError(
"BasicStor::bsRelease: unlink failed ($linkFname)",
GBERR_FILEIO);
}
$this->dbc->query("BEGIN");
$res = $this->dbc->query("
UPDATE {$this->filesTable}
SET currentlyAccessing=currentlyAccessing-1
WHERE gunid=x'{$gunid}'::bigint AND currentlyAccessing>0
");
if($this->dbc->isError($res)){
$this->dbc->query("ROLLBACK"); return $res; }
$res = $this->dbc->query("
DELETE FROM {$this->accessTable} WHERE token=x'$token'::bigint
");
if($this->dbc->isError($res)){
$this->dbc->query("ROLLBACK"); return $res; }
$res = $this->dbc->query("COMMIT");
if($this->dbc->isError($res)){ return $res; }
return $gunid;
}
/**
* Create and return downloadable URL for file
*
* @param id int, virt.file's local id
* @param part string, 'media'|'metadata'
* @return array with strings:
* downloadable URL, download token, chsum, size, filename
*/
function bsOpenDownload($id, $part='media')
{
$ac =& StoredFile::recall($this, $id);
if($this->dbc->isError($ac)) return $ac;
$gunid = $ac->gunid;
switch($part){
case"media":
$realfile = $ac->_getRealRADFname();
$ext = $ac->_getExt();
$filename = $ac->_getFileName();
break;
case"metadata":
$realfile = $ac->_getRealMDFname();
$ext = "xml";
$filename = $ac->_getFileName();
break;
default:
return $this->dbc->raiseError(
"BasicStor::bsOpenDownload: unknown part ($part)"
);
}
$acc = $this->bsAccess($realfile, $ext, $gunid, 'download');
if($this->dbc->isError($acc)){ return $acc; }
$url = $this->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 token string, download token
* @param part string, 'media'|'metadata'
* @return string, gunid
*/
function bsCloseDownload($token, $part='media')
{
if(!$this->bsCheckToken($token, 'download')){
return $this->dbc->raiseError(
"BasicStor::bsCloseDownload: invalid token ($token)"
);
}
return $this->bsRelease($token, 'download');
}
/**
* Create writable URL for HTTP PUT method file insert
*
* @param chsum string, md5sum of the file having been put
* @param gunid string, global unique id
* @return array with: writable URL, PUT token
*/
function bsOpenPut($chsum, $gunid)
{
$ext = '';
$token = StoredFile::_createGunid();
$res = $this->dbc->query("
DELETE FROM {$this->accessTable} WHERE token=x'$token'::bigint
");
if($this->dbc->isError($res)){ return $res; }
$res = $this->dbc->query("
INSERT INTO {$this->accessTable}
(gunid, token, ext, chsum, type, ts)
VALUES
(x'{$gunid}'::bigint, x'$token'::bigint,
'$ext', '$chsum', 'put', now())
");
if($this->dbc->isError($res)){ return $res; }
$fname = "{$this->accessDir}/$token";
touch($fname); // is it needed?
$url = $this->getUrlPart()."xmlrpc/put.php?token=$token";
return array('url'=>$url, 'token'=>$token);
}
/**
* Get file from writable URL and return local filename.
* Caller should move or unlink this file.
*
* @param token string, PUT token
* @return string, local path of the file having been put
*/
function bsClosePut($token)
{
$token = StoredFile::_normalizeGunid($token);
if(!$this->bsCheckToken($token, 'put')){
return $this->dbc->raiseError(
"BasicStor::bsClosePut: invalid token ($token)",
GBERR_TOKEN
);
}
$chsum = $this->dbc->getOne("
SELECT chsum FROM {$this->accessTable}
WHERE token=x'{$token}'::bigint
");
$res = $this->dbc->query("
DELETE FROM {$this->accessTable} WHERE token=x'$token'::bigint
");
if($this->dbc->isError($res)){ return $res; }
$fname = "{$this->accessDir}/$token";
$md5sum = md5_file($fname);
if($chsum != $md5sum){
if(file_exists($fname)) @unlink($fname);
return $this->dbc->raiseError(
"BasicStor::bsClosePut: md5sum does not match (token=$token)",
GBERR_PUT
);
}
return $fname;
}
/**
* Check uploaded file
*
* @param token string, put token
* @return hash, (
* status: boolean,
* size: int - filesize
* expectedsum: string - expected checksum
* realsum: string - checksum of uploaded file
* )
*/
function bsCheckPut($token)
{
if(!$this->bsCheckToken($token, 'put')){
return $this->dbc->raiseError(
"BasicStor::bsClosePut: invalid token ($token)"
);
}
$chsum = $this->dbc->getOne("
SELECT chsum FROM {$this->accessTable}
WHERE token=x'{$token}'::bigint
");
if($this->dbc->isError($chsum)){ return $chsum; }
$fname = "{$this->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
*/
function getUrlPart()
{
$host = $this->config['storageUrlHost'];
$port = $this->config['storageUrlPort'];
$path = $this->config['storageUrlPath'];
return "http://$host:$port$path/";
}
/* ---------------------------------------------- replicas, versions etc. */
/**
* Create replica.
* TODO: NOT FINISHED
*
* @param id int, virt.file's local id
* @param did int, destination folder local id
* @param replicaName string, name of new replica
* @return int, local id of new object
*/
function bsCreateReplica($id, $did, $replicaName)
{
return $this->dbc->raiseError(
'BasicStor::bsCreateReplica: not implemented', GBERR_NOTIMPL
);
// ---
if($this->getObjType($did) !== 'Folder')
return $this->dbc->raiseError(
'BasicStor::bsCreateReplica: dest is not folder', GBERR_WRTYPE
);
if($replicaName=='') $replicaName = $this->getObjName($id);
while(($exid = $this->getObjId($replicaName, $did))<>'')
{ $replicaName.='_R'; }
$rid = $this->addObj($replicaName , 'Replica', $did, 0, $id);
if($this->dbc->isError($rid)) return $rid;
# $this->addMdata($this->_pathFromId($rid), 'isReplOf', $id);
return $rid;
}
/**
* Create version.
* TODO: NOT FINISHED
*
* @param id int, virt.file's local id
* @param did int, destination folder local id
* @param versionLabel string, name of new version
* @return int, local id of new object
*/
function bsCreateVersion($id, $did, $versionLabel)
{
return $this->dbc->raiseError(
'BasicStor::bsCreateVersion: not implemented', GBERR_NOTIMPL
);
}
/* ------------------------------------------------------------- metadata */
/**
* Replace metadata with new XML file or string
*
* @param id int, virt.file's local id
* @param mdata string, local path of metadata XML file
* @param mdataLoc string 'file'|'string'
* @return boolean or PEAR::error
*/
function bsReplaceMetadata($id, $mdata, $mdataLoc='file')
{
$ac =& StoredFile::recall($this, $id);
if($this->dbc->isError($ac)) return $ac;
return $ac->replaceMetaData($mdata, $mdataLoc);
}
/**
* Get metadata as XML string
*
* @param id int, virt.file's local id
* @return string or PEAR::error
*/
function bsGetMetadata($id)
{
$ac =& StoredFile::recall($this, $id);
if($this->dbc->isError($ac)) return $ac;
return $ac->getMetaData();
}
/**
* Get metadata element value
*
* @param id int, virt.file's local id
* @param category string, metadata element name
* @param lang string, optional xml:lang value for select language version
* @return array of matching records (as hash {id, value, attrs})
* @see Metadata::getMetadataValue
*/
function bsGetMetadataValue($id, $category, $lang=NULL)
{
$ac =& StoredFile::recall($this, $id);
if($this->dbc->isError($ac)) return $ac;
return $ac->md->getMetadataValue($category, $lang);
}
/**
* Set metadata element value
*
* @param id int, virt.file's local id
* @param category string, metadata element identification (e.g. dc:title)
* @param value string/NULL value to store, if NULL then delete record
* @param lang string, optional xml:lang value for select language version
* @param mid int, metadata record id (OPTIONAL on unique elements)
* @param container string, container element name for insert
* @return boolean
*/
function bsSetMetadataValue(
$id, $category, $value, $lang=NULL, $mid=NULL, $container='metadata')
{
$ac =& StoredFile::recall($this, $id);
if($this->dbc->isError($ac)) return $ac;
$res = $ac->md->setMetadataValue(
$category, $value, $lang, $mid, $container);
if($this->dbc->isError($res)) return $res;
$r = $ac->md->regenerateXmlFile();
if($this->dbc->isError($r)) return $r;
return $res;
}
/**
* Search in local metadata database.
*
* @param criteria hash, with following structure:
*
\n"; print_r($va); #exit; } /** * deleteData * * @return void */ function deleteData() { // $this->dbc->query("DELETE FROM {$this->filesTable}"); $ids = $this->dbc->getAll("SELECT id FROM {$this->filesTable}"); if(is_array($ids)) foreach($ids as $i=>$item){ $this->bsDeleteFile($item['id'], TRUE); } parent::deleteData(); $this->initData(); } /** * testData * */ function testData($d='') { $exdir = dirname(__FILE__).'/tests'; $o[] = $this->addSubj('test1', 'a'); $o[] = $this->addSubj('test2', 'a'); $o[] = $this->addSubj('test3', 'a'); $o[] = $this->addSubj('test4', 'a'); $o[] = $t1hd = $this->getObjId('test1', $this->storId); $o[] = $t1d1 = $this->bsCreateFolder($t1hd, 'test1_folder1'); $o[] = $this->bsCreateFolder($t1hd, 'test1_folder2'); $o[] = $this->bsCreateFolder($t1d1, 'test1_folder1_1'); $o[] = $t1d12 = $this->bsCreateFolder($t1d1, 'test1_folder1_2'); $o[] = $t2hd = $this->getObjId('test2', $this->storId); $o[] = $this->bsCreateFolder($t2hd, 'test2_folder1'); $o[] = $this->bsPutFile($t1hd, 'file1.mp3', "$exdir/ex1.mp3", '', NULL, 'audioclip'); $o[] = $this->bsPutFile($t1d12, 'file2.wav', "$exdir/ex2.wav", '', NULL, 'audioclip'); /* */ $this->tdata['storage'] = $o; } /** * test * */ function test() { $this->test_log = ''; // if($this->dbc->isError($p = parent::test())) return $p; $this->deleteData(); $this->testData(); $this->test_correct = " StorageRoot root test1 file1.mp3 public test1_folder1 test1_folder1_1 test1_folder1_2 file2.wav test1_folder2 test2 public test2_folder1 test3 public test4 public "; $this->test_dump = $this->dumpTree($this->storId, ' ', ' ', '{name}'); if($this->test_dump==$this->test_correct) { $this->test_log.="# BasicStor::test: OK\n"; return true; } else $this->dbc->raiseError('BasicStor::test:', 1, PEAR_ERROR_DIE, '%s'. "\ncorrect:\n.{$this->test_correct}.\n". "dump:\n.{$this->test_dump}.\n\n"); } /** * initData - initialize * */ function initData() { $this->rootId = $this->getRootNode(); $this->storId = $this->wd = $this->addObj('StorageRoot', 'Folder', $this->rootId); $rootUid = parent::addSubj('root', $this->config['tmpRootPass']); $res = $this->addPerm($rootUid, '_all', $this->rootId, 'A'); $fid = $this->bsCreateFolder($this->storId, 'root'); if(!$this->config['isArchive']){ $stPrefGr = parent::addSubj($this->config['StationPrefsGr']); $this->addSubj2Gr('root', $this->config['StationPrefsGr']); $stPrefGr = parent::addSubj($this->config['AllGr']); $this->addSubj2Gr('root', $this->config['AllGr']); } } /** * install - create tables * * file states: *