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
* @return int
* @exception PEAR::error
*/
function bsPutFile($parid, $fileName,
$mediaFileLP, $mdataFileLP, $gunid=NULL)
{
$name = "$fileName";
$id = $this->addObj($name , 'File', $parid);
$ac =& StoredFile::insert(
&$this, $id, $name, $mediaFileLP, $mdataFileLP, 'file', $gunid
);
if(PEAR::isError($ac)) return $ac;
return $id;
}
/**
* Analyze media file for internal metadata information
*
* @param id int, virt.file's local id
* @return array
*/
function bsAnalyzeFile($id)
{
$ac =& StoredFile::recall(&$this, $id);
if(PEAR::isError($ac)) return $ac;
$ia = $ac->analyzeMediaFile();
return $ia;
}
/**
* 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);
$ac =& StoredFile::recall(&$this, $id);
if(PEAR::isError($ac)){
// catch nonerror exception:
if($ac->getCode() != GBERR_FOBJNEX) return $ac;
}
$res = $ac->rename($newName);
if(PEAR::isError($res)) return $res;
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)
{
if($this->getObjType($did) !== 'Folder')
return PEAR::raiseError(
'BasicStor::moveFile: destination is not folder', GBERR_WRTYPE
);
$this->_relocateSubtree($id, $did);
}
/**
* 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)
{
if($this->getObjType($did)!=='Folder')
return PEAR::raiseError(
'GreenBox::copyFile: destination is not folder', GBERR_WRTYPE
);
return $this->_copySubtree($id, $did);
}
/**
* Delete file
*
* @param id int, virt.file's local id
* @return true or PEAR::error
*/
function bsDeleteFile($id)
{
$parid = $this->getParent($id);
$res = $this->removeObj($id);
if(PEAR::isError($res)) return $res;
return TRUE;
}
/* ----------------------------------------------------- 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='{$token}' AND type='$type'
");
if(PEAR::isError($cnt)){ return FALSE; }
return ($cnt == 1);
}
/**
* Create and return access link to media file
*
* @param realFname string, local filepath to accessed file
* @param ext string, useful filename extension for accessed file
* @param gunid int, global unique id
* @param sessid string, session id
* @param type string 'access'|'download'
* @return array with: seekable filehandle, access token
*/
function bsAccess($realFname, $ext, $gunid, $sessid='', $type='access')
{
$token = StoredFile::_createGunid();
$res = $this->dbc->query("
INSERT INTO {$this->accessTable}
(gunid, sessid, token, ext, type, ts)
VALUES
('{$gunid}', '$sessid', '$token', '$ext', '$type', now())
");
if(PEAR::isError($res)){ return $res; }
$linkFname = "{$this->accessDir}/$token.$ext";
if(!file_exists($realFname)){
return PEAR::raiseError(
"BasicStor::bsAccess: symlink create failed ($accLinkName)",
GBERR_FILEIO);
}
if(! @symlink($realFname, $linkFname)){
return PEAR::raiseError(
"BasicStor::bsAccess: symlink create failed ($linkFname)",
GBERR_FILEIO);
}
return array('fname'=>$linkFname, 'token'=>$token);
}
/**
* Release access link to media file
*
* @param token string, access token
* @param type string 'access'|'download'
* @return boolean
*/
function bsRelease($token, $type='access')
{
if(!$this->bsCheckToken($token, $type)){
return PEAR::raiseError(
"BasicStor::bsRelease: invalid token ($token)"
);
}
$ext = $this->dbc->getOne("
SELECT ext FROM {$this->accessTable}
WHERE token='{$token}' AND type='$type'
");
if(PEAR::isError($ext)){ return $ext; }
$linkFname = "{$this->accessDir}/$token.$ext";
$res = $this->dbc->query("
DELETE FROM {$this->accessTable} WHERE token='$token'
");
if(PEAR::isError($res)){ return $res; }
if(! @unlink($linkFname)){
return PEAR::raiseError(
"BasicStor::bsRelease: unlink failed ($linkFname)",
GBERR_FILEIO);
}
return TRUE;
}
/**
* Create and return downloadable URL for file
*
* @param id int, virt.file's local id
* @param part string, 'media'|'metadata'
* @return array with: downloadable URL, download token
*/
function bsOpenDownload($id, $part='media')
{
$ac =& StoredFile::recall(&$this, $id);
if(PEAR::isError($ac)) return $ac;
$gunid = $ac->gunid;
switch($part){
case"media":
$fname = $ac->_getRealRADFname();
$ext = $ac->_getExt();
break;
case"metadata":
$md = $this->bsGetMdata($id);
$fname = "{$this->bufferDir}/$gunid";
$e = FALSE;
if(!$fh = fopen($fname, "w")){ $e = TRUE; }
elseif(fwrite($fh, $md) === FALSE){ $e = TRUE; }
if($e){
return PEAR::raiseError(
"BasicStor::bsOpenDownload: can't write ($fname)",
GBERR_FILEIO);
}
fclose($fh);
$ext = "xml";
break;
}
$sessid = '';
$acc = $this->bsAccess($fname, $ext, $gunid, $sessid, 'download');
$url = $this->getUrlPart()."access/".basename($acc['fname']);
return array('url'=>$url, 'token'=>$acc['token']);
}
/**
* Discard downloadable URL
*
* @param token string, download token
* @param part string, 'media'|'metadata'
* @return boolean
*/
function bsCloseDownload($token, $part='media')
{
if($part == 'metadata'){
$gunid = $this->dbc->getOne("
SELECT gunid FROM {$this->accessTable}
WHERE token='{$token}' AND type='download'
");
if(PEAR::isError($gunid)){ return $gunid; }
$fname = "{$this->bufferDir}/$gunid";
@unlink($fname);
}
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)
{
$sessid = '';
$ext = '';
$token = StoredFile::_createGunid();
$res = $this->dbc->query("
INSERT INTO {$this->accessTable}
(gunid, sessid, token, ext, chsum, type, ts)
VALUES
('{$gunid}', '$sessid', '$token',
'$ext', '$chsum', 'put', now())
");
if(PEAR::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 insert it to the storage
*
* @param token string, PUT token
* @return string, local path of the file having been put
*/
function bsClosePut($token)
{
if(!$this->bsCheckToken($token, 'put')){
return PEAR::raiseError(
'BasicStor::bsClosePut: invalid token ($token)'
);
}
$chsum = $this->dbc->getOne("
SELECT chsum FROM {$this->accessTable}
WHERE token='{$token}'
");
$fname = "{$this->accessDir}/$token";
$md5sum = md5_file($fname);
if($chsum != $md5sum){
return PEAR::raiseError(
'BasicStor::bsClosePut: md5sum does not match (token=$token)'
);
}
$res = $this->dbc->query("
DELETE FROM {$this->accessTable} WHERE token='$token'
");
if(PEAR::isError($res)){ return $res; }
return $fname;
}
/**
* 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 PEAR::raiseError(
'GreenBox::createVersion: not implemented', GBERR_NOTIMPL
);
// ---
if($this->getObjType($did)!=='Folder')
return PEAR::raiseError(
'GreenBox::createReplica: 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(PEAR::isError($rid)) return $rid;
# $this->addMdata($this->_pathFromId($rid), 'isReplOf', $id, $sessid);
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 PEAR::raiseError(
'GreenBox::createVersion: not implemented', GBERR_NOTIMPL
);
}
/* ------------------------------------------------------------- metadata */
/**
* Update metadata tree
*
* @param id int, virt.file's local id
* @param mdataFile string, local path of metadata XML file
* @return boolean or PEAR::error
*/
function bsUpdateMetadata($id, $mdataFile)
{
$ac =& StoredFile::recall(&$this, $id);
if(PEAR::isError($ac)) return $ac;
return $ac->updateMetaData($mdataFile);
}
/**
* Update object namespace and value of one metadata record
*
* @param id int, virt.file's local id
* @param mdid int, metadata record id
* @param object string, object value, e.g. title string
* @param objns string, object namespace prefix, have to be defined
* in file's metadata (or reserved prefix)
* @return boolean or PEAR::error
* @see MetaData
*/
function bsUpdateMetadataRecord($id, $mdid, $object, $objns='_L')
{
$ac =& StoredFile::recall(&$this, $id);
if(PEAR::isError($ac)) return $ac;
return $ac->updateMetaDataRecord($mdid, $object, $objns);
}
/**
* Add single metadata record.
* TODO: NOT FINISHED
* Params could be changed!
*
* @param id int, virt.file's local id
* @param propertyName string
* @param propertyValue string
* @return boolean or PEAR::error
* @see MetaData
*/
function bsAddMetaDataRecord($id, $propertyName, $propertyValue)
{
return PEAR::raiseError(
'GreenBox::addMetaDataRecord: not implemented', GBERR_NOTIMPL
);
}
/**
* Get metadata XML tree as string
*
* @param id int, virt.file's local id
* @return string or PEAR::error
*/
function bsGetMdata($id)
{
$ac =& StoredFile::recall(&$this, $id);
if(PEAR::isError($ac)) return $ac;
return $ac->getMetaData();
}
/**
* Search in local metadata database.
* TODO: NOT FINISHED
* It will support structured queries - array of mode and query parts.
* Mode is "match all" or "match any".
* Query parts is array of [fieldname, operator, value] entities.
*
* @param searchData string, search query -
* only one SQL LIKE term supported now.
* It will be searched in all literal object values
* in metadata database
* @return array of gunid strings
*/
function bsLocalSearch($searchData)
{
$ftsrch = $searchData;
$res = $this->dbc->getCol("SELECT md.gunid as gunid
FROM {$this->filesTable} f, {$this->mdataTable} md
WHERE f.gunid=md.gunid AND md.objns='_L' AND
md.object like '%$ftsrch%'
GROUP BY md.gunid
");
if(!is_array($res)) $res = array();
return $res;
}
/* --------------------------------------------------------- info methods */
/**
* List files in folder
*
* @param id int, local id of folder
* @return array
*/
function bsListFolder($id)
{
if($this->getObjType($id)!=='Folder')
return PEAR::raiseError(
'GreenBox::listFolder: not a folder', GBERR_NOTF
);
$a = $this->getDir($id, 'id, name, type, param as target', 'name');
return $a;
}
/**
* List files in folder
*
* @param id int, local id of object
* @param relPath string, relative path
* @return array
*/
function getObjIdFromRelPath($id, $relPath='.')
{
$a = split('/', $relPath);
if($this->getObjType($id)!=='Folder') $nid = $this->getparent($id);
else $nid = $id;
foreach($a as $i=>$item){
switch($item){
case".":
break;
case"..":
$nid = $this->getparent($nid);
break;
case"":
break;
default:
$nid = $this->getObjId($item, $nid);
}
}
return $nid;
}
/* -------------------------------------------- remote repository methods */
/**
* Upload file to remote repository
*
* @param id int, virt.file's local id
* @param gunid string, global id
* @param sessid string, session id
* @return string - transfer id or PEAR::error
*/
function uploadFile($id, $gunid, $sessid='')
{
$res = $this->prepareForTransport($id, $gunid, $sessid);
if(PEAR::isError($res)) return $res;
list($mediaFile, $mdataFile, $gunid) = $res;
$tr =& new Transport(&$this->dbc, $this->config);
$res = $tr->uploadOpen($mediaFile, 'media', $sessid, $gunid);
if(PEAR::isError($res)) return $res;
$res2 = $tr->uploadOpen($mdataFile, 'metadata', $sessid, $gunid);
if(PEAR::isError($res2)) return $res2;
$res3 = $tr->getTransportStatus($res);
$res4 = $tr->getTransportStatus($res2);
# return $res;
return array($res, $res2, $res3, $res4);
}
/**
* Download file from remote repository
*
* @param gunid int, global unique id
* @param sessid string, session id
* @return string - transfer id or PEAR::error
*/
function downloadFile($gunid, $sessid='')
{
$tr =& new Transport(&$this->dbc, $this->config);
// get home dir if needed
$res = $tr->downloadOpen($sessid, 'media', $gunid,
$this->getSessUserId($sessid)
);
if(PEAR::isError($res)) return $res;
$res2 = $tr->downloadOpen($sessid, 'metadata', $gunid,
$this->getSessUserId($sessid)
);
if(PEAR::isError($res)) return $res;
$res3 = $tr->getTransportStatus($res);
$res4 = $tr->getTransportStatus($res2);
# return $res;
return array($res, $res2, $res3, $res4);
}
/**
* Method for handling interupted transports via cron
*
*/
function cronJob()
{
$tr =& new Transport(&$this->dbc, $this->config);
$ru = $tr->uploadCron();
$rd = $tr->downloadCron(&$this);
return array($ru, $rd);
}
/**
* Get status of asynchronous transfer
*
* @param transferId int, id of asynchronous transfer
* returned by uploadFile or downloadFile methods
* @param sessid string, session id
* @return string or PEAR::error
*/
function getTransferStatus($transferId, $sessid='')
{
return PEAR::raiseError(
'GreenBox::getTransferStatus: not implemented', GBERR_NOTIMPL
);
}
/**
* Prepare symlink to media file and create metadata file for transport
*
* @param id
* @param gunid
* @param sessid
* @return array
*/
function prepareForTransport($id, $gunid, $sessid='')
{
if(!$gunid) $gunid = $this->_gunidFromId($id);
else $id = $this->_idFromGunid($gunid);
$ac =& StoredFile::recallByGunid(&$this, $gunid);
if(PEAR::isError($ac)) return $ac;
$mediaTarget = $ac->_getRealRADFname();
$mediaFile = "$gunid";
$mdataFile = "$gunid.xml";
@symlink($mediaTarget, $this->transDir."/$mediaFile");
$mdata = $this->getMdata($id, $sessid);
if(PEAR::isError($mdata)) return $mdata;
if(!($fh = fopen($this->transDir."/$mdataFile", 'w'))) $res=FALSE;
else{
$res = fwrite($fh, $mdata);
fclose($fh);
}
if($res === FALSE) return PEAR::raiseError(
"GreenBox::prepareForTransport:".
" can't write metadata tmp file ($mdataFile)"
);
return array($mediaFile, $mdataFile, $gunid);
}
/**
* Insert transported file and metadata into storage.
* TODO: cals methods from LocStor - it's not good
*
* @param sessid string - session id
* @param file string - local path to filr
* @param type string - media|metadata|search
* @param gunid string - global unique id
*/
function processTransported($sessid, $file, $type, $gunid='X')
{
switch($type){
case 'media':
if(!file_exists($file)) break;
$res = $this->storeAudioClip($sessid, $gunid,
$file, '');
if(PEAR::isError($res)) return $res;
@unlink($file);
break;
case 'metadata':
case 'mdata':
if(!file_exists($file)) break;
$res = $this->updateAudioClipMetadata($sessid, $gunid,
$file);
if(PEAR::isError($res)){
// catch valid exception
if($res->getCode() == GBERR_FOBJNEX){
$res2 = $this->storeAudioClip($sessid, $gunid,
'', $file);
if(PEAR::isError($res2)) return $res2;
}else return $res;
}
@unlink($file);
break;
case 'search':
//$this->localSearch($criteria);
return PEAR::raiseError("processTranferred: search not implemented");
break;
default:
return PEAR::raiseError("processTranferred: unknown type ($type)");
break;
}
}
/**
* Search in central metadata database
*
* @param searchData string, search query - see localSearch method
* @param sessid string, session id
* @return string - job id or PEAR::error
*/
function globalSearch($searchData, $sessid='')
{
return PEAR::raiseError(
'GreenBox::globalSearch: not implemented', GBERR_NOTIMPL
);
/*
$srchid = md5($sessid.mtime());
$fh = fopen($this->transDir."/$srchid", "w");
fwrite($fh, serialize($searchData));
fclose($fh);
$res = $tr->uploadOpen($srchid, 'search', $sessid, $gunid);
if(PEAR::isError($res)) return $res;
return $res;
*/
}
/**
* Get results from asynchronous search
*
* @param transferId int, transfer id returned by
* @param sessid string, session id
* @return array with results or PEAR::error
*/
function getSearchResults($transferId, $sessid='')
{
return PEAR::raiseError(
'GreenBox::getSearchResults: not implemented', GBERR_NOTIMPL
);
}
/* =============================================== test and debug methods */
/**
* dump
*
*/
function dump($id='', $indch=' ', $ind='', $format='{name}')
{
if($id=='') $id = $this->storId;
return parent::dump($id, $indch, $ind, $format);
}
/**
*
*
*/
function dumpDir($id='', $format='$o["name"]')
{
if($id=='') $id = $this->storId;
$arr = $this->getDir($id, 'id,name');
// if($this->doDebug){ $this->debug($arr); exit; }
$arr = array_map(create_function('$o', 'return "'.$format.'";'), $arr);
return join('', $arr);
}
/**
*
*
*/
function debug($va)
{
echo"
\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->removeObj($item['id']); } parent::deleteData(); $this->initData(); } /** * testData * */ function testData($d='') { $exdir = '../../../storageServer/var/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", ''); $o[] = $this->bsPutFile($t1d12, 'file2.wav', "$exdir/ex2.wav", ''); /* */ $this->tdata['storage'] = $o; } /** * test * */ function test() { // if(PEAR::isError($p = parent::test())) return $p; $this->deleteData(); $this->testData(); $this->test_correct = " StorageRoot root test1 test1_folder1 test1_folder1_1 test1_folder1_2 file2.wav test1_folder2 file1.mp3 test2 test2_folder1 test3 test4 "; $this->test_dump = $this->dumpTree($this->storId); if($this->test_dump==$this->test_correct) { $this->test_log.="storageServer: OK\n"; return true; } else PEAR::raiseError('GreenBox::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'); } /** * install - create tables * * file states: *