diff --git a/.zfproject.xml b/.zfproject.xml index cffca41f9..c9da28565 100644 --- a/.zfproject.xml +++ b/.zfproject.xml @@ -100,6 +100,10 @@ + + + + @@ -325,6 +329,12 @@ + + + + + + @@ -370,6 +380,7 @@ + diff --git a/LICENSE_3RD_PARTY b/LICENSE_3RD_PARTY index e616578a1..9a89df2d2 100644 --- a/LICENSE_3RD_PARTY +++ b/LICENSE_3RD_PARTY @@ -28,6 +28,10 @@ Linked code: - Note: Only used for development, not needed to run Airtime. - License: LGPLv3 + * Soundcloud php api wrapper + - https://github.com/mptre/php-soundcloud/blob/master/Services/Soundcloud.php + - License: MIT + ---------------- Non-linked code: ---------------- diff --git a/application/configs/ACL.php b/application/configs/ACL.php index e6fd9c72e..26f1905b3 100644 --- a/application/configs/ACL.php +++ b/application/configs/ACL.php @@ -21,7 +21,8 @@ $ccAcl->add(new Zend_Acl_Resource('library')) ->add(new Zend_Acl_Resource('nowplaying')) ->add(new Zend_Acl_Resource('search')) ->add(new Zend_Acl_Resource('dashboard')) - ->add(new Zend_Acl_Resource('preference')); + ->add(new Zend_Acl_Resource('preference')) + ->add(new Zend_Acl_Resource('recorder')); /** Creating permissions */ $ccAcl->allow('G', 'index') @@ -29,13 +30,13 @@ $ccAcl->allow('G', 'index') ->allow('G', 'error') ->allow('G', 'nowplaying') ->allow('G', 'api') + ->allow('G', 'recorder') ->allow('G', 'schedule') ->allow('G', 'dashboard') ->allow('H', 'library') ->allow('H', 'search') ->allow('H', 'plupload') ->allow('H', 'playlist') - ->allow('H', 'sideplaylist') ->allow('A', 'user') ->allow('A', 'preference'); diff --git a/application/controllers/RecorderController.php b/application/controllers/RecorderController.php new file mode 100644 index 000000000..1f33beb93 --- /dev/null +++ b/application/controllers/RecorderController.php @@ -0,0 +1,32 @@ +_helper->getHelper('contextSwitch'); + $ajaxContext->addActionContext('get-show-schedule', 'json') + ->initContext(); + } + + public function indexAction() + { + // action body + } + + public function getShowScheduleAction() + { + //$from = $this->_getParam("from"); + //$to = $this->_getParam("to"); + + $today_timestamp = date("Y-m-d H:i:s"); + + $this->view->shows = Show::getShows($today_timestamp, null, $excludeInstance=NULL, $onlyRecord=TRUE); + } + + +} + + + diff --git a/application/controllers/plugins/Acl_plugin.php b/application/controllers/plugins/Acl_plugin.php index b0f0dd029..e3682ba11 100644 --- a/application/controllers/plugins/Acl_plugin.php +++ b/application/controllers/plugins/Acl_plugin.php @@ -110,10 +110,11 @@ class Zend_Controller_Plugin_Acl extends Zend_Controller_Plugin_Abstract { $controller = strtolower($request->getControllerName()); - if ($controller == 'api'){ - $this->setRoleName("G"); - - } else if (!Zend_Auth::getInstance()->hasIdentity()){ + if ($controller == 'api' || $controller == 'recorder'){ + + $this->setRoleName("G"); + } + else if (!Zend_Auth::getInstance()->hasIdentity()){ if ($controller !== 'login') { diff --git a/application/models/AccessRecur.php b/application/models/AccessRecur.php deleted file mode 100644 index ff18f11f7..000000000 --- a/application/models/AccessRecur.php +++ /dev/null @@ -1,183 +0,0 @@ -ls =& $ls; - $this->sessid = $sessid; - } - - - public static function accessPlaylist(&$ls, $sessid, $plid, $parent='0') - { - $ppa = new AccessRecur($ls, $sessid); - $r = $ls->accessPlaylist($sessid, $plid, FALSE, $parent); - if (PEAR::isError($r)) { - return $r; - } - $plRes = $r; - $r = StoredFile::RecallByGunid($plid); - if (is_null($r) || PEAR::isError($r)) { - return $r; - } - $ac = $r; - $r = $ac->md->genPhpArray(); - if (PEAR::isError($r)) { - return $r; - } - $pla = $r; - $r = $ppa->processPlaylist($pla, $plRes['token']); - if (PEAR::isError($r)) { - return $r; - } - $plRes['content'] = $r; - return $plRes; - } - - - public static function releasePlaylist(&$ls, $sessid, $token) - { - global $CC_CONFIG, $CC_DBC; - $ppa = new AccessRecur($ls, $sessid); - $r = $CC_DBC->getAll(" - SELECT to_hex(token)as token2, to_hex(gunid)as gunid - FROM ".$CC_CONFIG['accessTable']." - WHERE parent=x'{$token}'::bigint - "); - if (PEAR::isError($r)) { - return $r; - } - $arr = $r; - foreach ($arr as $i => $item) { - extract($item); // token2, gunid - $r = BasicStor::GetType($gunid); - if (PEAR::isError($r)) { - return $r; - } - $ftype = $r; - # echo "$ftype/$token2\n"; - switch (strtolower($ftype)) { - case "audioclip": - $r = $ppa->ls->releaseRawAudioData($ppa->sessid, $token2); - if (PEAR::isError($r)) { - return $r; - } - # var_dump($r); - break; - case "playlist": - $r = $ppa->releasePlaylist($ppa->ls, $ppa->sessid, $token2); - if (PEAR::isError($r)) { - return $r; - } - # var_dump($r); - break; - default: - } - } - $r = $ppa->ls->releasePlaylist($ppa->sessid, $token, FALSE); - if (PEAR::isError($r)) { - return $r; - } - return $r; - } - - - private function processPlaylist($pla, $parent) - { - $res = array(); - foreach ($pla['children'] as $ple) { - switch ($ple['elementname']) { - case "playlistElement": - $r = $this->processPlaylistElement($ple, $parent); - if (PEAR::isError($r)) { - return $r; - } - // $res = array_merge($res, $r); - $res[] = $r; - break; - default: - } - } - return $res; - } - - - private function processAudioClip($gunid, $parent) - { - $r = $this->ls->accessRawAudioData($this->sessid, $gunid, $parent); - if (PEAR::isError($r)) { - return $r; - } - return $r; - } - - - private function processPlaylistElement($ple, $parent='0') - { - foreach ($ple['children'] as $ac) { - switch ($ac['elementname']) { - case "audioClip": - $r = $this->processAudioClip($ac['attrs']['id'], $parent); - if (PEAR::isError($r)) { - return $r; - } - return $r; - case "playlist": - // if(empty($ac['children'])){ - $r = $this->accessPlaylist($this->ls, $this->sessid, - $ac['attrs']['id'], $parent); - if (PEAR::isError($r)) { - if ($r->getCode() != GBERR_NOTF) { - return $r; - } else { - $r = $this->processPlaylist($ac, $parent); - if (PEAR::isError($r)) { - return $r; - } - $r = array( - 'content' => $r, - 'url' => NULL, - 'token' => NULL, - 'chsum' => NULL, - 'size' => NULL, - 'warning' => 'inline playlist?', - ); - } - } - return $r; - /* - }else{ - $r = $this->processPlaylist($ac, $parent); - if(PEAR::isError($r)) return $r; - $res = array( - 'content' => $r, - 'url' => NULL, - 'token' => NULL, - 'chsum' => NULL, - 'size' => NULL, - 'warning' => 'inline playlist', - ); - return $res; - } - */ - break; - default: - } - } - return array(); - } - -} // class AccessRecur diff --git a/application/models/Alib.php b/application/models/Alib.php index 751ab3996..6fd294f67 100644 --- a/application/models/Alib.php +++ b/application/models/Alib.php @@ -19,33 +19,6 @@ class Alib { /* ----------------------------------------------- session/authentication */ /* -------------------------------------------------------- authorization */ - /** - * Insert permission record - * - * @param int $sid - * local user/group id - * @param string $action - * @param int $oid - * local object id - * @param string $type - * 'A'|'D' (allow/deny) - * @return int - * local permission id - */ - public static function AddPerm($sid, $action, $oid, $type='A') - { - global $CC_CONFIG, $CC_DBC; - $permid = $CC_DBC->nextId($CC_CONFIG['permSequence']); - $sql = "INSERT INTO ".$CC_CONFIG['permTable']." (permid, subj, action, obj, type)" - ." VALUES ($permid, $sid, '$action', $oid, '$type')"; - $r = $CC_DBC->query($sql); - if (PEAR::isError($r)) { - return($r); - } - return $permid; - } // fn addPerm - - /** * Remove permission record * diff --git a/application/models/Backup.php b/application/models/Backup.php deleted file mode 100755 index 6b5c33c80..000000000 --- a/application/models/Backup.php +++ /dev/null @@ -1,486 +0,0 @@ -gb =& $gb; - $this->token = null; - $this->logFile = $CC_CONFIG['bufferDir'].'/'.ACCESS_TYPE.'.log'; - $this->addLogItem("-I- ".date("Ymd-H:i:s")." construct\n"); - } - - - /** - * Open a backup - * Create a backup file (tarball) - * - * @param string $sessid - * @param array $criteria - * struct - see search criteria - * @return array - * hasharray with field: - * token string: backup token - */ - public function openBackup($sessid, $criteria='') - { - if ($this->loglevel=='debug') { - $this->addLogItem("-I- ".date("Ymd-H:i:s")." openBackup - sessid:$sessid\n"); - } - $this->sessid = $sessid; - $this->criteria = $criteria; - - // get ids (and real filenames) which files match with criteria - $srch = $this->gb->localSearch($this->criteria,$this->sessid); - if (PEAR::isError($srch)) { - return $srch; - } - $this->setIDs($srch); - - // get real filenames - if (is_array($this->ids)) { - $this->setFilenames(); - - $this->setEnviroment(true); - - // write a status file - file_put_contents($this->statusFile, 'working'); - - // save the metafile to tmpdir - $hostname = trim(`hostname`); - $ctime = time(); - $ctime_f = date("Ymd-H:i:s"); - file_put_contents("{$this->tmpDirMeta}/storage.xml", - "\n". - "\n" - ); - - // copy all file to tmpdir - $this->copyAllFiles(); - - // do everything - $this->doIt(); - - return array('token'=>$this->token); - } else { - return false; - } - } - - - /** - * Check the status of backup. - * - * @param unknown $token - * @return array - * status : string - susccess | working | fault - * faultString: string - description of fault - * token : stirng - backup token - * url : string - access url - * tmpfile : string - access filename - */ - public function checkBackup($token) - { - global $CC_CONFIG; - if ($this->loglevel=='debug') { - $this->addLogItem("-I- ".date("Ymd-H:i:s")." checkBackup - token:$token\n"); - } - $this->token = $token; - $this->setEnviroment(); - $status = file_get_contents($this->statusFile); - if (strpos($status,'fault')!==false) { - list($status,$faultString) = explode('|',$status); - } - switch ($status) { - case 'success': - $r['url'] = BasicStor::GetUrlPart()."access/$token.".BACKUP_EXT; - $r['tmpfile'] = $CC_CONFIG['accessDir']."/$token.".BACKUP_EXT; - case 'working': - case 'fault': - $r['status'] = $status; - $r['faultString'] = $faultString; - $r['token'] = $token; - break; - } - return $r; - } - - - /** - * Close a backup - * - * @param unknown $token - * @return boolean - */ - public function closeBackup($token) - { - if ($this->loglevel=='debug') { - $this->addLogItem("-I- ".date("Ymd-H:i:s")." closeBackup - token:$token\n"); - } - # post procedures - $this->token = $token; - $this->setEnviroment(); - BasicStor::bsRelease($token, ACCESS_TYPE); - Backup::rRmDir($this->tmpDir); - unlink($this->statusFile); - unlink($this->tmpFile); - if (is_file($this->tmpName)) { - unlink($this->tmpName); - } - return !is_file($this->tmpFile); - } - - - /** - * list of unclosed backups - * - * @param string $stat - * if this parameter is not set, then return with all unclosed backups - * @return array of hasharray with field: - * status : string - susccess | working | fault - * token : stirng - backup token - * url : string - access url - */ - public function listBackups($stat='') - { - if ($this->loglevel=='debug') { - $this->addLogItem("-I- ".date("Ymd-H:i:s")." listBackups - stat:$stat\n"); - } - // open temporary dir - $tokens = BasicStor::GetTokensByType(ACCESS_TYPE); - // echo 'tokens:'; print_r($tokens); echo ''; - foreach ($tokens as $token) { - $st = $this->checkBackup($token); - if ($stat=='' || $st['status']==$stat) { - $r[] = $st; - } - } - return $r; - } - - - /** - * Set the ids from searchResult - * - * @param array $searchResult : array of gunids - */ - private function setIDs($searchResult) - { - if ($this->loglevel=='debug') { - $this->addLogItem("-I- ".date("Ymd-H:i:s")." setIDs\n"); - } - if (is_array($searchResult['results'])) { - $this->ids = $searchResult['results']; - } else { - $this->addLogItem("-E- ".date("Ymd-H:i:s")." setIDs - the parameter is not array!\n"); - return PEAR::raiseError('The IDs variable isn\'t array.'); - } - } - - - /** - * Set the filenames from ids. - * - */ - private function setFilenames() - { - // if ($this->loglevel=='debug') { - // $this->addLogItem("-I- ".date("Ymd-H:i:s")." setFilenames\n"); - // } - // if (is_array($this->ids)) { - // foreach ($this->ids as $i => $item) { - // $gunid = $item['gunid']; - // // get a stored file object of this gunid - // $sf = StoredFile::RecallByGunid($gunid); - // if (is_null($sf) || PEAR::isError($sf)) { - // return $sf; - // } - // $lid = BasicStor::IdFromGunid($gunid); - // if (($res = BasicStor::Authorize('read', $lid, $this->sessid)) !== TRUE) { - // $this->addLogItem("-E- ".date("Ymd-H:i:s")." setFilenames - authorize gunid:$gunid\n"); - // return PEAR::raiseError('Backup::setFilenames : Authorize ... error.'); - // } - // // if the file is a playlist then it has only a meta file - // if (strtolower($sf->md->format) != 'playlist') { - // $this->filenames[] = array( - // 'filename' => $sf->getRealFileName(), - // 'format' => $sf->md->format - // ); - // } - // $this->filenames[] = array( - // 'filename' => $sf->getRealMetadataFileName(), - // 'format' => $sf->md->format - // ); - // if ($this->loglevel=='debug') { - // $this->addLogItem("-I- ".date("Ymd-H:i:s")." setFilenames - add file: {$sf->md->format}|".$sf->getRealMetadataFileName()."\n"); - // } - // } - // return $this->filenames; - // } else { - // $this->addLogItem("-E- ".date("Ymd-H:i:s")." setFilenames - The IDs variable isn't array.\n"); - // return PEAR::raiseError('Backup::setFilenames : The IDs variable isn\'t array.'); - // } - } - - - /** - * Create the tarball - call the shell script - * - */ - private function doIt() - { - if ($this->loglevel=='debug') { - $this->addLogItem("-I- ".date("Ymd-H:i:s")." doIt\n"); - } - $command = dirname(__FILe__)."/../bin/backup.sh" - ." {$this->tmpDir}" - ." {$this->tmpFile}" - ." {$this->statusFile}" - ." >> {$this->logFile} &"; - $res = system("$command"); - sleep(2); - if ($this->loglevel=='debug') { - $this->addLogItem("-I- ".date("Ymd-H:i:s")." doIt - command:$command\n"); - } - } - - - /** - * Copy the real files into the tmp dirs to tar they. - * - */ - private function copyAllFiles() - { - if ($this->loglevel=='debug') { - $this->addLogItem("-I- ".date("Ymd-H:i:s")." copyAllFiles\n"); - } - //echo 'this->filenames:'; print_r($this->filenames); echo ''; - if (is_array($this->filenames)) { - foreach ($this->filenames as $v) { - # get the filename from full path - $fn = substr($v['filename'],strrpos($v['filename'],'/')); - switch (strtolower($v['format'])) { - case 'playlist': - # if playlist then copy to the playlist dir - copy($v['filename'],$this->tmpDirPlaylist.$fn); - break; - case 'audioclip': - # if audioclip then copy to the audioclip dir - copy($v['filename'],$this->tmpDirClip.$fn); - break; - } - } - } - } - - - /** - * Figure out the enviroment to the backup - * - */ - private function setEnviroment($createDir=false) - { - global $CC_CONFIG; - if ($this->loglevel=='debug') { - $this->addLogItem("-I- ".date("Ymd-H:i:s")." setEnviroment - createDirs:$createDir\n"); - } - // create temporary directories - if (is_null($this->token) && $createDir) { - $this->tmpName = tempnam($CC_CONFIG['bufferDir'], ACCESS_TYPE.'_'); - $this->tmpFile = $this->tmpName.'.'.BACKUP_EXT; - $this->tmpDir = $this->tmpName.'.dir'; - $this->tmpDirPlaylist = $this->tmpDir. '/playlist'; - $this->tmpDirClip = $this->tmpDir. '/audioClip'; - $this->tmpDirMeta = $this->tmpDir. '/meta-inf'; - touch($this->tmpFile); - mkdir($this->tmpDir); - mkdir($this->tmpDirPlaylist); - mkdir($this->tmpDirClip); - mkdir($this->tmpDirMeta); - $this->genToken(); - } else { - $symlink = $CC_CONFIG['accessDir'].'/'.$this->token.'.'.BACKUP_EXT; - if (is_link($symlink) && is_file(readlink($symlink))) { - $this->tmpName = str_replace('.tar','',readlink($symlink)); - $this->tmpFile = $this->tmpName.'.'.BACKUP_EXT; - $this->tmpDir = $this->tmpName.'.dir'; - $this->tmpDirPlaylist = $this->tmpDir. '/playlist'; - $this->tmpDirClip = $this->tmpDir. '/audioClip'; - $this->tmpDirMeta = $this->tmpDir. '/meta-inf'; - } else { - $this->addLogItem("-E- ".date("Ymd-H:i:s")." setEnviroment - Corrupt symbolic link.\n"); - return false; - } - } - $this->statusFile = $CC_CONFIG['accessDir'].'/'.$this->token.'.'.BACKUP_EXT.'.status'; - if ($this->loglevel=='debug') { - $this->addLogItem("this->tmpName: $this->tmpName\n"); - $this->addLogItem("this->tmpFile: $this->tmpFile\n"); - $this->addLogItem("this->tmpDir: $this->tmpDir\n"); - $this->addLogItem("this->tmpDirPlaylist: $this->tmpDirPlaylist\n"); - $this->addLogItem("this->tmpDirClip: $this->tmpDirClip\n"); - $this->addLogItem("this->tmpDirMeta: $this->tmpDirMeta\n"); - $this->addLogItem("this->token: $this->token\n"); - $this->addLogItem("this->statusFile: $this->statusFile\n"); - } - } - - - /** - * Generate a new token. - * @return void - */ - private function genToken() - { - $acc = BasicStor::bsAccess($this->tmpFile, BACKUP_EXT, null, ACCESS_TYPE); - if (PEAR::isError($acc)) { - return $acc; - } - $this->token = $acc['token']; - } - - - /** - * Add a line to the logfile. - * - * @param string $item - * the new row of log file - */ - private function addLogItem($item) - { - $f = fopen($this->logFile,'a'); - fwrite($f,$item); - fclose($f); - } - - - /** - * Delete a directory recursive - * - * @param string $dirname - * path of dir. - */ - private static function rRmDir($dirname) - { - if (is_dir($dirname)) { - $dir_handle = opendir($dirname); - } - while ($file = readdir($dir_handle)) { - if ( ($file != ".") && ($file != "..") ) { - if (!is_dir($dirname."/".$file)) { - unlink ($dirname."/".$file); - } else { - Backup::rRmDir($dirname."/".$file); - } - } - } - closedir($dir_handle); - rmdir($dirname); - return true; - } - -} // classs Backup diff --git a/application/models/BasicStor.php b/application/models/BasicStor.php index b26b6ef80..e9a93ca04 100644 --- a/application/models/BasicStor.php +++ b/application/models/BasicStor.php @@ -56,55 +56,8 @@ 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 Airtime file storage module * @@ -129,93 +82,6 @@ class BasicStor { } - /** - * 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; - // } - - - /* ----------------------------------------------------- put, access etc. */ /** * Check validity of access/put token @@ -391,531 +257,8 @@ class BasicStor { } - /** - * 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 * @@ -1280,446 +623,6 @@ class BasicStor { /* ---------------------------------------------------- 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/airtime) - * @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; - } - - /* ================================================== "protected" methods */ /** * Check authorization - auxiliary method @@ -1758,98 +661,6 @@ class BasicStor { } - /** - * 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 * @@ -1947,25 +758,6 @@ class BasicStor { 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 * diff --git a/application/models/GreenBox.php b/application/models/GreenBox.php deleted file mode 100644 index 6aa58a334..000000000 --- a/application/models/GreenBox.php +++ /dev/null @@ -1,1695 +0,0 @@ -"; -} -require_once("BasicStor.php"); -if (isset($WHITE_SCREEN_OF_DEATH) && $WHITE_SCREEN_OF_DEATH) { - echo __FILE__.':line '.__LINE__.": Loaded BasicStor
"; -} -require_once("Playlist.php"); -require_once("Renderer.php"); -require_once('Prefs.php'); -require_once("Backup.php"); -require_once('Restore.php'); -require_once("Transport.php"); - -/** - * GreenBox class - * - * File storage module. - * - * @package Airtime - * @subpackage StorageServer - * @copyright 2010 Sourcefabric O.P.S. - * @license http://www.gnu.org/licenses/gpl.txt - */ -class GreenBox extends BasicStor { - - /* ====================================================== storage methods */ - - /** - * Store new file in the storage - * - * @param string $fileName - * The name for the new file. - * @param string $mediaFileLP - * Local path of the media file - * @param string $mdataFileLP - * Local path of the metadata file - * @param string $sessid - * Session id - * @param string $gunid - * Global unique id - * @param string $ftype - * Internal file type - * @return int - * ID of the StoredFile that was created. - */ - public function putFile($p_values, $p_sessionId='') - { - if (($res = BasicStor::Authorize('write', null, $p_sessionId)) !== TRUE) { - return $res; - } - $storedFile = StoredFile::Insert($p_values); - return $storedFile; - } // fn putFile - - - /** - * Store new webstream - * - * @param string $fileName - * Name for new file - * @param string $mdataFileLP - * Local path of metadata file - * @param string $sessid - * Session id - * @param string $gunid - * Global unique id - * @param string $url - * Webstream url - * @return int - * @exception PEAR::error - */ - public function storeWebstream($fileName, $mdataFileLP, $sessid='', - $gunid=NULL, $url) - { - if (($res = BasicStor::Authorize('write', null, $sessid)) !== TRUE) { - return $res; - } - if (!file_exists($mdataFileLP)) { - $mdataFileLP = dirname(__FILE__).'/emptyWebstream.xml'; - } - $values = array( - "filename" => $fileName, - "metadata" => $mdataFileLP, - "gunid" => $gunid, - "filetype" => "webstream" - ); - $storedFile = StoredFile::Insert($values); - if (PEAR::isError($storedFile)) { - return $storedFile; - } - $r = $storedFile->setMetadataValue('ls:url', $url); - if (PEAR::isError($r)) { - return $r; - } - return $oid; - } // fn storeWebstream - - - /** - * Access stored file - increase access counter - * - * @param int $id - * virt.file's local id - * @param string $sessid - * session id - * @return string access token - */ -// function accessFile($id, $sessid='') -// { -// if (($res = BasicStor::Authorize('read', $id, $sessid)) !== TRUE) { -// return $res; -// } -// $gunid = BasicStor::GunidFromId($id); -// $r = BasicStor::bsAccess(NULL, '', $gunid, 'access'); -// if (PEAR::isError($r)) { -// return $r; -// } -// $token = $r['token']; -// return $token; -// } // fn accessFile - - - /** - * Release stored file - decrease access counter - * - * @param string $token - * access token - * @param string $sessid - * session id - * @return boolean - */ -// function releaseFile($token, $sessid='') -// { -// $r = BasicStor::bsRelease($token, 'access'); -// if (PEAR::isError($r)) { -// return $r; -// } -// return FALSE; -// } // fn releaseFile - - - /** - * Analyze media file for internal metadata information - * - * @param int $id - * Virtual file's local id - * @param string $sessid - * Session id - * @return array - */ -// public function analyzeFile($id, $sessid='') -// { -// if (($res = BasicStor::Authorize('read', $id, $sessid)) !== TRUE) { -// return $res; -// } -// return $this->bsAnalyzeFile($id); -// } - - - /** - * Rename file - * - * @param int $id - * Virtual file's local id - * @param string $newName - * @param string $sessid - * Session id - * @return boolean|PEAR_Error - */ -// public function renameFile($id, $newName, $sessid='') -// { -// if (($res = BasicStor::Authorize('write', $id, $sessid)) !== TRUE) { -// return $res; -// } -// return $this->bsRenameFile($id, $newName); -// } - - - /** - * Replace file. Doesn't change filetype! - * - * @param int $id - * virt.file's local id - * @param string $mediaFileLP - * local path of media file - * @param string $mdataFileLP - * local path of metadata file - * @param string $sessid - * session id - * @return TRUE|PEAR_Error - */ -// public function replaceFile($id, $mediaFileLP, $mdataFileLP, $sessid='') -// { -// if (($res = BasicStor::Authorize('write', $id, $sessid)) !== TRUE) { -// return $res; -// } -// return $this->bsReplaceFile($id, $mediaFileLP, $mdataFileLP); -// } - - - /** - * Delete file - * - * @param int $id - * local id - * @param int $sessid - * @param boolean $forced - * if true don't use trash -- now ignored - * @return true|PEAR_Error - */ - public function deleteFile($id, $sessid='', $forced=FALSE) - { - if (($res = BasicStor::Authorize('write', $id, $sessid)) !== TRUE) { - return $res; - } - $f = StoredFile::Recall($id); - return $f->delete(true); - } - - - /* ------------------------------------------------------------- metadata */ - - /** - * Replace metadata with new XML file or string - * - * @param int $id - * Virtual file's local id - * @param string $mdata - * XML string or local path of metadata XML file - * @param string $mdataLoc - * metadata location: 'file'|'string' - * @param string $sessid - * session id - * @return boolean|PEAR_Error - */ -// public function replaceMetadata($id, $mdata, $mdataLoc='file', $sessid='') -// { -// if (($res = BasicStor::Authorize('write', $id, $sessid)) !== TRUE) { -// return $res; -// } -// return $this->bsReplaceMetadata($id, $mdata, $mdataLoc); -// } // fn replaceMetadata - - - /** - * Get metadata XML tree as string - * - * @param int $id - * Virtual file's local id - * @param string $sessid - * session id - * @return string|PEAR_Error - * @todo rename this function to "getMetadata" - */ - public function getMetadata($id, $sessid='') - { - if (($res = BasicStor::Authorize('read', $id, $sessid)) !== TRUE) { - return $res; - } - $f = StoredFile::Recall($id); - return $f->getMetadata(); - } - - - /** - * Return metadata as hierarchical PHP hash-array - * - * If xml:lang attribute is specified in metadata category, - * array of metadata values indexed by xml:lang values - * is presented instead of one plain metadata value. - * - * @param int $id - * local object id - * @param string $sessid - * session ID - * @return array - */ - public function getMetadataArray($id, $sessid) - { - if (($res = BasicStor::Authorize('read', $id, $sessid)) !== TRUE) { - return $res; - } - $storedFile = StoredFile::Recall($id); - if (is_null($storedFile) || PEAR::isError($storedFile)) { - return $storedFile; - } - return $storedFile->md; - -// $arr = $storedFile->md->genPhpArray(); - $md = FALSE; - foreach ($arr['children'] as $i=>$a) { - if ($a['elementname'] == 'metadata'){ - $md = $arr['children'][$i]; - break; - } - } - if ($md === FALSE) { - return PEAR::raiseError( - "GreenBox::getMetadataArray: no metadata container found" - ); - } - $res = array(); - foreach ($md['children'] as $el) { - $lang = isset($el['attrs']['xml:lang']) ? $el['attrs']['xml:lang'] : ""; - $category = $el['elementname']; - if ($lang == "") { - $res[$category] = $el['content']; - } else { - $res[$category][$lang] = $el['content']; - } - } - return $res; - } - - - /** - * Get metadata element value - * - * @param int $id - * Virtual file's local id - * @param string $category - * metadata element name - * @param string $sessid - * session id - * @param string $lang - * xml:lang value for select language version - * @param string $deflang - * xml:lang for default language - * @return array of matching records as hash with fields: - *
    - *
  • mid int, local metadata record id
  • - *
  • value string, element value
  • - *
  • attrs hasharray of element's attributes indexed by - * qualified name (e.g. xml:lang)
  • - *
- */ - public function getMetadataValue($id, $category, $sessid='', - $lang=NULL, $deflang=NULL) - { - if (!is_numeric($id)) { - return null; - } - if (($res = BasicStor::Authorize('read', $id, $sessid)) !== TRUE) { - return $res; - } - $f = StoredFile::Recall($id); - return $f->getMetadataValue($category); - } - - - /** - * Set metadata element value - * - * @param int $id - * Virtual file's local id - * @param string $category - * Metadata element identification (e.g. dc:title) - * @param string $sessid - * Session id - * @param string $value - * The value to store, if NULL then delete record - * @return boolean - */ - public function setMetadataValue($id, $category, $sessid, $value) - { - if (($res = BasicStor::Authorize('write', $id, $sessid)) !== TRUE) { - return $res; - } - $f = StoredFile::Recall($id); - return $f->setMetadataValue($category, $value); - } // fn setMetadataValue - - - /** - * Search in local metadata database. - * - * @param array $criteria - * with following structure:
- *
    - *
  • filetype - string, type of searched files, - * meaningful values: 'audioclip', 'webstream', 'playlist', 'all'
  • - *
  • 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 - * "conditions" field) - *
  • - *
  • limit : int - limit for result arrays (0 means unlimited)
  • - *
  • offset : int - starting point (0 means without offset)
  • - *
  • orderby : string - metadata category for sorting (optional) - * or array of strings for multicolumn orderby - * [default: dc:creator, dc:source, dc:title] - *
  • - *
  • desc : boolean - flag for descending order (optional) - * or array of boolean for multicolumn orderby - * (it corresponds to elements of orderby field) - * [default: all ascending] - *
  • - *
  • conditions - array of hashes with structure: - *
      - *
    • cat - string, metadata category name
    • - *
    • op - string, operator - meaningful values: - * 'full', 'partial', 'prefix', '=', '<', - * '<=', '>', '>='
    • - *
    • val - string, search value
    • - *
    - *
  • - *
- * @param string $sessid - * session id - * @return 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
    • - *
    • length: string - dcterms:extent in extent format
    • - *
    - *
  • - *
- * @see BasicStor::bsLocalSearch - */ - public function localSearch($criteria, $sessid='') - { - $limit = intval(isset($criteria['limit']) ? $criteria['limit'] : 0); - $offset = intval(isset($criteria['offset']) ? $criteria['offset'] : 0); - return $this->bsLocalSearch($criteria, $limit, $offset); - } // fn localSearch - - - /** - * Return values of specified metadata category - * - * @param string $category - * metadata category name - * with or without namespace prefix (dc:title, author) - * @param array $criteria - * see localSearch method - * @param string $sessid - * @return array, fields: - * results : array with found values - * cnt : integer - number of matching values - * @see BasicStor::bsBrowseCategory - */ - public function browseCategory($category, $criteria = null, $sessid = '') - { - $limit = 0; - $offset = 0; - if (!is_null($criteria)) { - $limit = intval(isset($criteria['limit']) ? $criteria['limit'] : 0); - $offset = intval(isset($criteria['offset']) ? $criteria['offset'] : 0); - } - $res = $this->bsBrowseCategory($category, $limit, $offset, $criteria); - return $res; - } // fn browseCategory - - - /*====================================================== playlist methods */ - /** - * Create a new empty playlist. - * - * @param string $fname - * human readable menmonic file name - * @param string $sessid - * session ID - * @return int - * local id of created playlist - */ - public function createPlaylist($fname, $sessid='') - { - $pl = new Playlist(); - $pl = $pl->create($fname); - - return $pl; - } // fn createPlaylist - - public function setPLMetadataValue($id, $category, $value, $lang=NULL, $mid=NULL) - { - $pl = Playlist::Recall($id); - - if($pl === FALSE) - return FALSE; - - $res = $pl->setPLMetaData($category, $value, $lang); - - return $res; - } - - public function getPLMetadataValue($id, $category, $langId=NULL) - { - $pl = Playlist::Recall($id); - - if($pl === FALSE) - return FALSE; - - $res = $pl->getPLMetaData($category); - - return $res; - } - - /** - * Return playlist as XML string - * - * @param int $id - * local object id - * @param string $sessid - * session ID - * @return string - * XML - */ -// function getPlaylistXml($id, $sessid) -// { -// return $this->getMetadata($id, $sessid); -// } // fn getPlaylistXml - - - /** - * Return playlist as hierarchical PHP hash-array - * - * @param int $id - * local object id - * @param string $sessid - * session ID - * @return array - */ - public function getPlaylistArray($id) - { - $pl = Playlist::Recall($id); - if ($pl === FALSE) { - return FALSE; - } - - $res = $pl->getContents(); - - if(is_null($res)) - return array(); - - return $res; - } // fn getPlaylistArray - - - /** - * Mark playlist as edited and return edit token - * - * @param int $id - * local object id - * @param string $sessid - * session ID - * @return string - * playlist access token - */ - public function lockPlaylistForEdit($id, $sessid) { - $pl = Playlist::Recall($id); - - if($pl === FALSE) - return; - - $res = $pl->lock($sessid); - - return $res; - } - - - /** - * clear edit flag. - * - * @param string $sessid - * session ID - * @return string gunid - */ - public function releaseLockedPlaylist($id, $sessid) { - $pl = Playlist::Recall($id); - - if($pl === FALSE) - return FALSE; - - $res = $pl->unlock($sessid); - return $res; - } - - - /** - * Add audioclip specified by local id to the playlist - * - * @param string $token - * playlist access token - * @param string $acId - * local ID of added file - * @param string $sessid - * session ID - * @param string $fadeIn - * in time format hh:mm:ss.ssssss - * @param string $fadeOut - * in time format hh:mm:ss.ssssss - * @param string $length - * length in extent format - - * for webstream (or for overrule length of audioclip) - * @param string $clipstart - * optional clipstart time format hh:mm:ss.ssssss - relative to begin - * @param string $clipend - * optional $clipend time format hh:mm:ss.ssssss - relative to begin - * @return boolean, true if added. - */ - public function addAudioClipToPlaylist($id, $acId, $pos=NULL, $fadeIn=NULL, $fadeOut=NULL, $cliplength=NULL, $cueIn=NULL, $cueOut=NULL) - { - $pl = Playlist::Recall($id); - if ($pl === FALSE) { - return FALSE; - } - - $res = $pl->addAudioClip($acId, $pos, $fadeIn, $fadeOut, $cliplength, $cueIn, $cueOut); - - return $res; - } // fn addAudioClipToPlaylist - - - /** - * Remove audioclip from playlist - * - * @param string $id - * playlist id - * @param int $pos - * position of element in playlist to delete. - * @return boolean, true if deleted. - * @todo rename this function to "deleteAudioClipFromPlaylist" - */ - public function delAudioClipFromPlaylist($id, $pos) - { - $pl = Playlist::Recall($id); - if ($pl === FALSE) { - return FALSE; - } - - $res = $pl->delAudioClip($pos); - if($res === FALSE) - return FALSE; - - return TRUE; - } - - /** - * Move audioClip to the new position in the playlist. - * - * This method may change id attributes of playlistElements and/or - * fadeInfo. - * - * @param string $id - * playlist id - * @param id $oldPos - * old position in playlist - * @param int $newPos - * new position in playlist - * @return boolean - */ - public function moveAudioClipInPlaylist($id, $oldPos, $newPos) - { - $pl = Playlist::Recall($id); - if ($pl === FALSE) { - return FALSE; - } - - $res = $pl->moveAudioClip($oldPos, $newPos); - - return $res; - } - - /** - * Change fadeInfo values - * - * @param string $id - * playlist id - * @param string $fadeIn - * in time format hh:mm:ss.ssssss - * @param string $fadeOut - * in time format hh:mm:ss.ssssss - * @return boolean - */ - public function changeFadeInfo($id, $pos, $fadeIn, $fadeOut) - { - $pl = Playlist::Recall($id); - if ($pl === FALSE) { - return FALSE; - } - - $res = $pl->changeFadeInfo($pos, $fadeIn, $fadeOut); - - return $res; - } - - /** - * Change cueIn/cueOut values for playlist element - * - * @param string $id - * playlist id - * @param string $cueIn - * in time format hh:mm:ss.ssssss - * @param string $cueOut - * in time format hh:mm:ss.ssssss - * relative to begin - * @param sessid $string - * session ID - * @return boolean or pear error object - */ - public function changeClipLength($id, $pos, $cueIn, $cueOut) - { - $pl = Playlist::Recall($id); - if ($pl === FALSE) { - return FALSE; - } - - $res = $pl->changeClipLength($pos, $cueIn, $cueOut); - - return $res; - } - - /** - * Delete a Playlist metafile. - * - * @param int $id - * local id - * @param string $sessid - * session ID - * @return boolean - */ - public function deletePlaylist($id) - { - return Playlist::Delete($id); - - } - - /** - * Find info about clip at specified offset in playlist. - * - * @param string $sessid - * session id - * @param string $plid - * playlist global unique id - * @param string $offset - * current playtime (hh:mm:ss.ssssss) - * @param int $distance - * 0=current clip; 1=next clip ... - * @return array of matching clip info: - *
    - *
  • gunid string, global unique id of clip
  • - *
  • elapsed string, already played time of clip
  • - *
  • remaining string, remaining time of clip
  • - *
  • duration string, total playlength of clip
  • - *
- */ - public function displayPlaylistClipAtOffset($sessid, $plid, $offset, $distance=0) - { - $pl = Playlist::Recall($plid); - if (is_null($pl) || PEAR::isError($pl)) { - return $pl; - } - $res = $pl->displayPlaylistClipAtOffset($offset, $distance); - if (PEAR::isError($res)) { - return $res; - } - $res['title'] = NULL; - $f = StoredFile::RecallByGunid($res['gunid']); - if (PEAR::isError($f)) { - return $f; - } - $res['title'] = $f->getMetadataValue("dc:title"); - $res['playlist_title'] = NULL; - $pl = Playlist::Recall($plid); - $res['playlist'] = $pl->getName(); - - return $res; - } - - - /** - * Create a tarfile with playlist export - playlist and all matching - * sub-playlists and media files (if desired) - * - * @param string $sessid - * string, session ID - * @param array $plids - * array of strings, playlist global unique IDs - * (one gunid is accepted too) - * @param string $type - * playlist format, values: lspl | smil | m3u - * @param boolean $standalone - * if only playlist should be exported or - * with all related files - * @return hasharray with fields: - * fname string: readable fname, - * token string: access token - */ - public function exportPlaylistOpen($sessid, $plids, $type='lspl', $standalone=FALSE) - { - return $this->bsExportPlaylistOpen($plids, $type, !$standalone); - } // fn exportPlaylistOpen - - - /** - * Close playlist export previously opened by the exportPlaylistOpen method - * - * @param string $token - * access token obtained from exportPlaylistOpen - * method call - * @return TRUE|PEAR_Error - */ - public function exportPlaylistClose($token) - { - return $this->bsExportPlaylistClose($token); - } // fn exportPlaylistClose - - - /** - * Open writable handle for import playlist in LS Archive format - * - * @param string $sessid - * session id - * @param string $chsum - * md5 checksum of imported file - * @return hasharray with: - * fname string: writable local filename - * token string: put token - */ - public function importPlaylistOpen($sessid, $chsum='') - { - $userid = GreenBox::GetSessUserId($sessid); - if (PEAR::isError($userid)) { - return $userid; - } - $r = $this->bsOpenPut($chsum, NULL, $userid); - if (PEAR::isError($r)) { - return $r; - } - return $r; - } // fn importPlaylistOpen - - - /** - * Close import-handle and import playlist - * - * @param string $token - * import token obtained by importPlaylistOpen method - * @return int - * result file local id (or error object) - */ - public function importPlaylistClose($token) - { - $arr = $this->bsClosePut($token); - if (PEAR::isError($arr)) { - return $arr; - } - $fname = $arr['fname']; - $owner = $arr['owner']; - $res = $this->bsImportPlaylist($fname, $owner); - if (file_exists($fname)) { - @unlink($fname); - } - return $res; - } // fn importPlaylistClose - - - /** - * Check whether a Playlist metafile with the given playlist ID exists. - * - * @param int $id - * local id - * @param string $sessid - * session ID - * @return boolean - */ - public function existsPlaylist($id) - { - $pl = Playlist::Recall($id); - if ($pl === FALSE) { - return FALSE; - } - - return TRUE; - } // fn existsPlaylist - - - /** - * Check whether a Playlist metafile with the given playlist ID - * is available for editing, i.e., exists and is not marked as - * beeing edited. - * - * @param int $id - * local id - * @param string $sessid - * session ID - * @return TRUE|int - * id of user editing it - */ - public function playlistIsAvailable($id, $sessid) - { - $pl = Playlist::Recall($id); - if ($pl === FALSE) { - return FALSE; - } - - $res = $pl->isEdited(); - - if($res !== FALSE) - return $res; - - return TRUE; - } // fn playlistIsAvailable - - - /* ---------------------------------------------- time conversion methods */ - /** - * Convert playlist time value to float seconds - * - * @param string $plt - * playlist time value (HH:mm:ss.dddddd) - * @return int - * seconds - */ - public function playlistTimeToSeconds($plt) - { - return Playlist::playlistTimeToSeconds($plt); - } - - - /** - * Convert float seconds value to playlist time format - * - * @param int $s0 - * seconds - * @return string - * time in playlist time format (HH:mm:ss.dddddd) - */ - public static function secondsToPlaylistTime($s0) - { - return Playlist::secondsToPlaylistTime($s0); - } // fn secondsToPlaylistTime - - - /* ------------------------------------------------------- render methods */ - /** - * Render playlist to ogg file (open handle) - * - * @param string $sessid - * session id - * @param string $plid - * playlist gunid - * @return string $token - * render token - */ - public function renderPlaylistToFileOpen($sessid, $plid) - { - $r = Renderer::rnRender2FileOpen($this, $plid); - if (PEAR::isError($r)) { - return $r; - } - return $r; - } // fn renderPlaylistToFileOpen - - - /** - * Render playlist to ogg file (check results) - * - * @param string $token - * render token - * @return hasharray: - * status : string - susccess | working | fault - * tmpfile : string - filepath to result temporary file - */ - public function renderPlaylistToFileCheck($token) - { - $r = Renderer::rnRender2FileCheck($this, $token); - if (PEAR::isError($r)) { - return $r; - } - return array('status'=>$r['status'], 'tmpfile'=>$r['tmpfile']); - } // fn renderPlaylistToFileCheck - - - /** - * Render playlist to ogg file (list results) - * - * @param string $status - * success | working | fault - * if this parameter is not set, then return with all unclosed - * @return array of hasharray: - * status : string - susccess | working | fault - * tmpfile : string - filepath to result temporary file - */ - public function renderPlaylistToFileList($status='') - { - return Renderer::rnRender2FileList($this, $status); - } // fn renderPlaylistToFileList - - - /** - * Render playlist to ogg file (close handle) - * - * @param string $token - * render token - * @return boolean - * status - */ - public function renderPlaylistToFileClose($token) - { - $r = Renderer::rnRender2FileClose($this, $token); - if (PEAR::isError($r)) { - return $r; - } - return array(TRUE); - } // fn renderPlaylistToFileClose - - - /** - * Render playlist to storage media clip (open handle) - * - * @param string $sessid - * session id - * @param string $plid - * playlist gunid - * @return string - * render token - */ - public function renderPlaylistToStorageOpen($sessid, $plid) - { - $owner = GreenBox::getSessUserId($sessid); - if (PEAR::isError($owner)) { - return $owner; - } - $r = Renderer::rnRender2FileOpen($this, $plid, $owner); - if (PEAR::isError($r)) { - return $r; - } - return $r; - } // fn renderPlaylistToStorageOpen - - - /** - * Render playlist to storage media clip (check results) - * - * @param string $token - * render token - * @return hasharray: - * status : string - susccess | working | fault - * gunid : string - gunid of result file - */ - public function renderPlaylistToStorageCheck($token) - { - $r = Renderer::rnRender2StorageCheck($this, $token); - if (PEAR::isError($r)) { - return $r; - } - return $r; - } // fn renderPlaylistToStorageCheck - - - /** - * Render playlist to RSS file (open handle) - * - * @param string $sessid - * session id - * @param string $plid - * playlist gunid - * @return string - * render token - */ - public function renderPlaylistToRSSOpen($sessid, $plid) - { - $token = '123456789abcdeff'; - $fakeFile = $CC_CONFIG['accessDir']."/$token.rss"; - file_put_contents($fakeFile, "fake rendered file"); - return array('token'=>$token); - } // fn renderPlaylistToRSSOpen - - - /** - * Render playlist to RSS file (check results) - * - * @param string $token - * render token - * @return hasharray: - * status : string - susccess | working | fault - * tmpfile : string - filepath to result temporary file - */ - public function renderPlaylistToRSSCheck($token) - { - $fakeFile = $CC_CONFIG['accessDir']."/$token.rss"; - if ($token != '123456789abcdeff' || !file_exists($fakeFile)){ - return PEAR::raiseError( - "renderPlaylistToRSSCheck: invalid token ($token)" - ); - } - return array( - 'status'=> 'success', - 'tmpfile' => $fakeFile, - ); - } // fn renderPlaylistToRSSCheck - - - /** - * Render playlist to RSS file (list results) - * - * @param string $status - * success | working | fault - * @return array of hasharray: - * status : string - susccess | working | fault - * tmpfile : string - filepath to result temporary file - */ -// function renderPlaylistToRSSList($status='') -// { -// $dummytokens = array ('123456789abcdeff'); -// foreach ($dummytokens as $token) { -// $r[] = $this->renderPlaylistToRSSCheck($token); -// } -// return $r; -// } // fn renderPlaylistToRSSList - - - /** - * Render playlist to RSS file (close handle) - * - * @param string $token - * render token - * @return boolean - * status - */ - public function renderPlaylistToRSSClose($token) - { - if ($token != '123456789abcdeff'){ - return PEAR::raiseError( - "GreenBox::renderPlaylistToRSSClose: invalid token" - ); - } - $fakeFile = $CC_CONFIG['accessDir']."/$token.rss"; - unlink($fakeFile); - return TRUE; - } // fn renderPlaylistToRSSClose - - - /*================================================= storage admin methods */ - /* ------------------------------------------------------- backup methods */ - /** - * Create backup of storage (open handle) - * - * @param string $sessid - * session id - * @param struct $criteria - * see search criteria - * @return array - * token : string - backup token - */ - public function createBackupOpen($sessid, $criteria='') - { - $bu = new Backup($this); - if (PEAR::isError($bu)) { - return $bu; - } - return $bu->openBackup($sessid,$criteria); - } // fn createBackupOpen - - - /** - * Create backup of storage (check results) - * - * @param string $token - * backup token - * @return hasharray with field: - * status : string - susccess | working | fault - * faultString: string - description of fault - * token : stirng - backup token - * url : string - access url - */ - public function createBackupCheck($token) - { - $bu = new Backup($this); - if (PEAR::isError($bu)) { - return $bu; - } - return $bu->checkBackup($token); - } // fn createBackupCheck - - - /** - * Create backup of storage (list results) - * - * @param string $sessid - * session id - * @param string $stat - * if this parameter is not set, then return with all unclosed backups - * @return array of hasharray with field: - * status : string - susccess | working | fault - * token : stirng - backup token - * url : string - access url - */ - public function createBackupList($sessid, $stat='') - { - $bu = new Backup($this); - if (PEAR::isError($bu)) { - return $bu; - } - return $bu->listBackups($stat); - } // fn createBackupList - - - /** - * Create backup of storage (close handle) - * - * @param string $token - * backup token - * @return boolean - * status - */ - public function createBackupClose($token) - { - $bu = new Backup($this); - if (PEAR::isError($bu)) { - return $bu; - } - return $bu->closeBackup($token); - } // fn createBackupClose - - - /* ===================================================== restore funcitons*/ - /** - * Restore a backup file - * - * @param string $sessid - * session id - * @param string $filename - * backup file path - * @return string - * restore token - */ - public function backupRestoreOpen($sessid, $filename) - { - $rs = new Restore($this); - if (PEAR::isError($rs)) { - return $rs; - } - return $rs->openRestore($sessid,$filename); - } // fn backupRestoreOpen - - - /** - * Check status of backup restore - * - * @param string $token - * restore token - * @return hasharray - * fields: - * token: string - restore token - * status: string - working | fault | success - * faultString: string - description of fault - */ - public function backupRestoreCheck($token) - { - $rs = new Restore($this); - if (PEAR::isError($rs)) { - return $rs; - } - return $rs->checkRestore($token); - } // fn backupRestoreCheck - - - /** - * Close a restore procedure - * - * @param string $token - * restore token - * @return boolean - * is success - */ - public function backupRestoreClose($token) { - $rs = new Restore($this); - if (PEAR::isError($rs)) { - return $rs; - } - return $rs->closeRestore($token); - } // fn backupRestoreClose - - /* ============================================== methods for preferences */ - - /** - * Read preference record by session id - * - * @param string $sessid - * session id - * @param string $key - * preference key - * @return string - * preference value - */ - public function loadPref($sessid, $key) - { - $pr = new Prefs($this); - $res = $pr->loadPref($sessid, $key); - return $res; - } // fn loadPref - - - /** - * Save preference record by session id - * - * @param string $sessid - * session id - * @param string $key - * preference key - * @param string $value - * preference value - * @return boolean - */ - public function savePref($sessid, $key, $value) - { - $pr = new Prefs($this); - $res = $pr->savePref($sessid, $key, $value); - return $res; - } // fn savePref - - - /** - * Delete preference record by session id - * - * @param string $sessid - * session id - * @param string $key - * preference key - * @return boolean - */ - public function delPref($sessid, $key) - { - $pr = new Prefs($this); - $res = $pr->delPref($sessid, $key); - return $res; - } // fn delPref - - - /** - * Read group preference record - * - * @param string $sessid - * session id - * @param string $group - * group name - * @param string $key - * preference key - * @return string - * preference value - */ - public function loadGroupPref($group, $key) - { - $pr = new Prefs($this); - $res = $pr->loadGroupPref($group, $key); - return $res; - } // fn loadGroupPref - - - /** - * Save group preference record - * - * @param string $sessid - * session id - * @param string $group - * group name - * @param string $key - * preference key - * @param string $value - * preference value - * @return boolean - */ - public function saveGroupPref($sessid, $group, $key, $value) - { - $pr = new Prefs($this); - $res = $pr->saveGroupPref($sessid, $group, $key, $value); - return $res; - } // fn saveGroupPref - - - /** - * Delete group preference record - * - * @param string $sessid - * session id - * @param string $group - * group name - * @param string $key - * preference key - * @return boolean - */ - public function delGroupPref($sessid, $group, $key) - { - $pr = new Prefs($this); - $res = $pr->delGroupPref($sessid, $group, $key); - return $res; - } // fn delGroupPref - - - /* =================================================== networking methods */ - /* ------------------------------------------------------- common methods */ - /** - * Common "check" method for transports - * - * @param string $trtok - * transport token - * @return array with fields: - * trtype: string - audioclip | playlist | search | file - * state: string - transport state - * direction: string - up | down - * expectedsize: int - file size in bytes - * realsize: int - currently transported bytes - * expectedchsum: string - orginal file checksum - * realchsum: string - transported file checksum - * ... ? - */ - public function getTransportInfo($trtok) - { - $tr = new Transport($this); - return $tr->getTransportInfo($trtok); - } // fn getTransportInfo - - - /** - * Turn transports on/off, optionaly return current state. - * - * @param string $sessid - * session id - * @param boolean $onOff - * optional (if not used, current state is returned) - * @return boolean - * previous state - */ - public function turnOnOffTransports($sessid, $onOff=NULL) - { - $tr = new Transport($this); - return $tr->turnOnOffTransports($sessid, $onOff); - } // fn turnOnOffTransports - - - /** - * Pause, resume or cancel transport - * - * @param string $trtok - * transport token - * @param string $action - * pause | resume | cancel - * @return string - * resulting transport state - */ - public function doTransportAction($trtok, $action) - { - $tr = new Transport($this); - $res = $tr->doTransportAction($trtok, $action); - return $res; - } // fn doTransportAction - - - /* ------------------------ methods for ls-archive-format file transports */ - /** - * Open async file transfer from local storageServer to network hub, - * file should be ls-archive-format file. - * - * @param string $filePath - * local path to uploaded file - * @return string - * transport token - */ - public function uploadFile2Hub($filePath) - { - $tr = new Transport($this); - return $tr->uploadFile2Hub($filePath); - } // fn uploadFile2Hub - - - /** - * Get list of prepared transfers initiated by hub - * - * @return array of structs/hasharrays with fields: - * trtok: string transport token - * ... ? - */ - public function getHubInitiatedTransfers() - { - $tr = new Transport($this); - return $tr->getHubInitiatedTransfers(); - } // fn getHubInitiatedTransfers - - - /** - * Start of download initiated by hub - * - * @param string $trtok - * transport token obtained from - * the getHubInitiatedTransfers method - * @return string - * transport token - */ - public function startHubInitiatedTransfer($trtok) - { - $tr = new Transport($this); - return $tr->startHubInitiatedTransfer($trtok); - } // fn startHubInitiatedTransfer - - - /* ------------- special methods for audioClip/webstream object transport */ - - /** - * Start upload of audioClip/webstream/playlist from local storageServer - * to hub - * - * @param string $gunid - * global unique id of object being transported - * @param boolean $withContent - * if true, transport playlist content too - * @return string - * transport token - * @todo rename this function "uploadToHub" - */ - public function upload2Hub($gunid, $withContent=FALSE) - { - $tr = new Transport($this); - return $tr->upload2Hub($gunid, $withContent); - } // fn upload2Hub - - - /** - * Start download of audioClip/webstream/playlist from hub to local - * storageServer - * - * @param string $sessid - * session id - * @param string $gunid - * global unique id of playlist being transported - * @param boolean $withContent - * if true, transport playlist content too - * @return string - * transport token - */ - public function downloadFromHub($sessid, $gunid, $withContent=TRUE){ - $uid = GreenBox::getSessUserId($sessid); - if (PEAR::isError($uid)) { - return $uid; - } - $tr = new Transport($this); - return $tr->downloadFromHub($uid, $gunid, $withContent); - } // fn downloadFromHub - - - /* ------------------------------------------------ global-search methods */ - /** - * Start search job on network hub - * - * @param array $criteria - * criteria format (see localSearch) - * @return string - * transport token - */ - public function globalSearch($criteria) - { - $tr = new Transport($this); - //return $tr->globalSearch($criteria); - return $tr->remoteSearch($criteria); - } - - - /** - * Get results from search job on network hub - * - * @param string $trtok - * transport token - * @param boolean $andClose - * if TRUE, close transport token - * @return array - * search result format (see localSearch) - */ -// public function getSearchResults($trtok, $andClose=TRUE) -// { -// $tr = new Transport($this); -// return $tr->getSearchResults($trtok, $andClose); -// } // fn getSearchResults - - - /* ========================================================= info methods */ - /** - * Check if file gunid exists in the storage and - * user have permission to read it - * - * @param string $sessid - * session id - * @param string $gunid - * @param string $ftype - * internal file type - * @return string|PEAR_Error - */ - public function existsFile($sessid, $gunid, $ftype=NULL) - { - if (($res = BasicStor::Authorize('read', $id, $sessid)) !== TRUE) { - return $res; - } - $f = StoredFile::RecallByGunid($gunid); - return $f->existsFile(); - } // fn existsFile - - - /* ==================================================== redefined methods */ - /** - * Get user id from session id - * - * This redefinition only simulate old (bad) behaviour - returns NULL - * for wrong sessid (code ALIBERR_NOTEXISTS). - * HtmlUI depends on it. - * - * @param string $sessid - * @return int|null|PEAR_Error - */ - public static function GetSessUserId($sessid) - { - $r = Alib::GetSessUserId($sessid); - if (PEAR::isError($r)) { - if ($r->getCode() == ALIBERR_NOTEXISTS) { - return NULL; - } else { - return $r; - } - } - return $r; - } // fn getSessUserId - - - /** - * Change user password. - * - * ('superuser mode'= superuser is changing some password without - * knowledge of the old password) - * - * @param string $login - * @param string $oldpass - * old password - * (should be null or empty for 'superuser mode') - * @param string $pass - * @param string $sessid - * session id, required for 'superuser mode' - * @return boolean/err - */ - public function passwd($login, $oldpass=null, $pass='', $sessid='') - { - if (is_null($oldpass) || ($oldpass == '') ) { - if (($res = BasicStor::Authorize('subjects', $this->rootId, $sessid)) !== TRUE) { - sleep(2); - return $res; - } else { - $oldpass = null; - } - } else { - if (FALSE === Subjects::Authenticate($login, $oldpass)) { - sleep(2); - return PEAR::raiseError( - "GreenBox::passwd: access denied (oldpass)", GBERR_DENY); - } - } - $res = Subjects::Passwd($login, $oldpass, $pass); - if (PEAR::isError($res)) { - return $res; - } - return TRUE; - } // fn passwd - - - /** - * Insert permission record - * - * @param int $sid - * local user/group id - * @param string $action - * @param int $oid - * local object id - * @param char $type - * 'A'|'D' (allow/deny) - * @param string $sessid - * session id - * @return int - * local permission id - */ - public function addPerm($sid, $action, $oid, $type='A', $sessid='') - { - if (($res = BasicStor::Authorize('editPerms', $oid, $sessid)) !== TRUE) { - return $res; - } - return Alib::AddPerm($sid, $action, $oid, $type); - } // fn addPerm - - -} // class GreenBox diff --git a/application/models/LocStor.php b/application/models/LocStor.php deleted file mode 100644 index 4a5f5cf5b..000000000 --- a/application/models/LocStor.php +++ /dev/null @@ -1,1748 +0,0 @@ -"; -} -require_once("BasicStor.php"); -if (isset($WHITE_SCREEN_OF_DEATH) && $WHITE_SCREEN_OF_DEATH) { - echo __FILE__.':line '.__LINE__.": Loaded BasicStor
"; -} -require_once("Transport.php"); -if (isset($WHITE_SCREEN_OF_DEATH) && $WHITE_SCREEN_OF_DEATH) { - echo __FILE__.':line '.__LINE__.": Loaded Transport
"; -} - -/** - * LocStor class - * - * Local storage interface - * - * @package Airtime - * @subpackage StorageServer - * @copyright 2010 Sourcefabric O.P.S. - * @license http://www.gnu.org/licenses/gpl.txt - */ -class LocStor extends BasicStor { - - /* ---------------------------------------------------------------- store */ - - /** - * Store or replace existing audio clip. - * - * Sending a file to the storage server is a 3 step process: - * 1) Call storeAudioClipOpen - * 2) Upload the file to the URL specified - * 3) Call storeAudioClipClose - * - * @param string $sessid - * session id - * @param string $gunid - * global unique id - * @param string $metadata - * metadata XML string - * @param string $fname - * human readable menmonic file name - * with extension corresponding to filetype - * @param string $chsum - * md5 checksum of media file - * @param string $ftype - * audioclip | playlist | webstream - * @return array - * {url:writable URL for HTTP PUT, token:access token} - */ - protected function storeAudioClipOpen($sessid, $gunid, $metadata, - $fname, $chsum, $ftype='audioclip') - { - // Check the gunid format - if (!BasicStor::CheckGunid($gunid)) { - return PEAR::raiseError( - "LocStor::storeAudioClipOpen: Wrong gunid ($gunid)" - ); - } - - // Check if we already have this file. - $duplicate = StoredFile::RecallByMd5($chsum); - if (!empty($chsum) && $duplicate) { - return PEAR::raiseError( - "LocStor::storeAudioClipOpen: Duplicate file" - ." - Matched MD5 ($chsum) against '".$duplicate->getName()."'", - 888); - } - - // Check if specified gunid exists. - $storedFile =& StoredFile::RecallByGunid($gunid); - if (!is_null($storedFile) && !PEAR::isError($storedFile)) { - // gunid exists - do replace - $oid = $storedFile->getId(); - if (($res = BasicStor::Authorize('write', $oid, $sessid)) !== TRUE) { - return $res; - } - if ($storedFile->isAccessed()) { - return PEAR::raiseError( - 'LocStor::storeAudioClipOpen: is accessed' - ); - } - $res = $storedFile->replace($oid, $storedFile->getName(), '', $metadata, 'string'); - if (PEAR::isError($res)) { - return $res; - } - } else { - // gunid doesn't exist - do insert: - $tmpFname = uniqid(); - if (($res = BasicStor::Authorize('write', null, $sessid)) !== TRUE) { - return $res; - } - $values = array( - "metadata" => $metadata, - "gunid" => $gunid, - "filetype" => $ftype); - $storedFile =& StoredFile::Insert($values); - if (PEAR::isError($storedFile)) { - return $storedFile; - } - if (PEAR::isError($res)) { - return $res; - } - } - $res = $storedFile->setState('incomplete'); - if (PEAR::isError($res)) { - return $res; - } - if ($fname == '') { - $fname = "newFile"; - } - $storedFile->setName($fname); - return $this->bsOpenPut($chsum, $storedFile->gunid); - } - - - /** - * Store or replace existing audio clip - * - * @param string $sessid - * @param string $token - * @return string gunid|PEAR_Error - */ - protected function storeAudioClipClose($sessid, $token) - { - $storedFile =& StoredFile::RecallByToken($token); - if (is_null($storedFile) || PEAR::isError($storedFile)) { - return $storedFile; - } - $arr = $this->bsClosePut($token); - if (PEAR::isError($arr)) { - $storedFile->delete(); - return $arr; - } - $fname = $arr['fname']; - $res = $storedFile->setRawMediaData($fname); - if (PEAR::isError($res)) { - return $res; - } - if (file_exists($fname)) { - @unlink($fname); - } - $res = $storedFile->setState('ready'); - if (PEAR::isError($res)) { - return $res; - } - return $storedFile->gunid; - } - - - /** - * Check uploaded file - * - * @param string $token - * "put" token - * @return array - * hash, (status: boolean, size: int - filesize) - */ - protected function uploadCheck($token) - { - return $this->bsCheckPut($token); - } - - - /** - * Store webstream - * - * @param string $sessid - * session id - * @param string $gunid - * global unique id - * @param string $metadata - * metadata XML string - * @param string $fname - * human readable menmonic file name with extension corresponding to filetype - * @param string $url - * webstream url - * @return string - * gunid - */ - protected function storeWebstream($sessid, $gunid, $metadata, $fname, $url) - { - $a = $this->storeAudioClipOpen( - $sessid, $gunid, $metadata, $fname, md5(''), 'webstream'); - if (PEAR::isError($a)) { - return $a; - } - $gunid = $this->storeAudioClipClose($sessid, $a['token']); - if (PEAR::isError($gunid)) { - return $gunid; - } - $storedFile =& StoredFile::RecallByGunid($gunid); - if (is_null($storedFile) || PEAR::isError($storedFile)) { - return $storedFile; - } - $r = $storedFile->setMetadataValue('ls:url', $url); - if (PEAR::isError($r)) { - return $r; - } - return $gunid; - } - - - /* --------------------------------------------------------------- access */ - /** - * Make access to audio clip - * - * @param string $sessid - * @param string $gunid - * @param int $parent - * parent token - * @return array - * with: seekable filehandle, access token - */ - public function accessRawAudioData($sessid, $gunid, $parent='0') - { - $storedFile =& StoredFile::RecallByGunid($gunid); - if (is_null($storedFile) || PEAR::isError($storedFile)) { - return $storedFile; - } - if (($res = BasicStor::Authorize('read', $storedFile->getId(), $sessid)) !== TRUE) { - return $res; - } - return $storedFile->accessRawMediaData($parent); - } - - - /** - * Release access to audio clip - * - * @param string $sessid - * @param string $token - * access token - * @return boolean|PEAR_Error - */ - public function releaseRawAudioData($sessid, $token) - { - $storedFile =& StoredFile::RecallByToken($token); - if (is_null($storedFile) || PEAR::isError($storedFile)) { - return $storedFile; - } - return $storedFile->releaseRawMediaData($token); - } - - - /* ------------------------------------------------------------- download */ - /** - * Create and return downloadable URL for audio file - * - * @param string $sessid - * session id - * @param string $gunid - * global unique id - * @return array - * array with strings: - * downloadable URL, download token, chsum, size, filename - */ - protected function downloadRawAudioDataOpen($sessid, $gunid) - { - $ex = $this->existsAudioClip($sessid, $gunid); - if (PEAR::isError($ex)) { - return $ex; - } - $media = StoredFile::RecallByGunid($gunid); - $id = $media->getId(); - if (is_null($id) || !$ex) { - return PEAR::raiseError( - "LocStor::downloadRawAudioDataOpen: gunid not found ($gunid)", - GBERR_NOTF - ); - } - if (($res = BasicStor::Authorize('read', $id, $sessid)) !== TRUE) { - return $res; - } - return $this->bsOpenDownload($id); - } - - - /** - * Discard downloadable URL for audio file - * - * @param string $token - * download token - * @return string - * gunid - */ - protected function downloadRawAudioDataClose($token) - { - return $this->bsCloseDownload($token); - } - - - /** - * Create and return downloadable URL for metadata - * - * @param string $sessid - * session id - * @param string $gunid - * global unique id - * @return array - * array with strings: - * downloadable URL, download token, chsum, filename - */ - protected function downloadMetadataOpen($sessid, $gunid) - { - // $res = $this->existsAudioClip($sessid, $gunid); - // if(PEAR::isError($res)) return $res; - $media = StoredFile::RecallByGunid($gunid); - $id = $media->getGunid(); - if (is_null($id)) { - return PEAR::raiseError( - "LocStor::downloadMetadataOpen: gunid not found ($gunid)" - ); - } - if (($res = BasicStor::Authorize('read', $id, $sessid)) !== TRUE) { - return $res; - } - $res = $this->bsOpenDownload($id, 'metadata'); - #unset($res['filename']); - return $res; - } - - - /** - * Discard downloadable URL for metadata - * - * @param string $token - * download token - * @return string - * gunid - */ - protected function downloadMetadataClose($token) - { - return $this->bsCloseDownload($token, 'metadata'); - } - - - /** - * Return metadata as XML - * - * @param string $sessid - * @param string $gunid - * @return string|PEAR_Error - */ - protected function getAudioClip($sessid, $gunid) - { - $storedFile =& StoredFile::RecallByGunid($gunid); - if (is_null($storedFile) || PEAR::isError($storedFile)) { - return $storedFile; - } - if (($res = BasicStor::Authorize('read', $storedFile->getId(), $sessid)) !== TRUE) { - return $res; - } - $md = $storedFile->getMetadata(); - if (PEAR::isError($md)) { - return $md; - } - return $md; - } - - - /* ------------------------------------------------------- search, browse */ - - /** - * Search in metadata database - * - * @param string $sessid - * @param array $criteria - * with following structure:
- *
    - *
  • filetype - string, type of searched files, - * meaningful values: 'audioclip', 'webstream', 'playlist', 'all'
  • - *
  • 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 - * "conditions" field) - *
  • - *
  • limit : int - limit for result arrays (0 means unlimited)
  • - *
  • offset : int - starting point (0 means without offset)
  • - *
  • orderby : string - metadata category for sorting (optional) - * or array of strings for multicolumn orderby - * [default: dc:creator, dc:source, dc:title] - *
  • - *
  • desc : boolean - flag for descending order (optional) - * or array of boolean for multicolumn orderby - * (it corresponds to elements of orderby field) - * [default: all ascending] - *
  • - *
  • conditions - array of hashes with structure: - *
      - *
    • cat - string, metadata category name
    • - *
    • op - string, operator - meaningful values: - * 'full', 'partial', 'prefix', '=', '<', - * '<=', '>', '>='
    • - *
    • val - string, search value
    • - *
    - *
  • - *
- * @return 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
    • - *
    - *
  • - *
- * @see BasicStor::localSearch - */ - public function searchMetadata($sessid, $criteria) - { - if (($res = BasicStor::Authorize('read', $this->storId, $sessid)) !== TRUE) { - return $res; - } - $criteria['resultMode'] = 'xmlrpc'; - $res = $this->localSearch($criteria, $sessid); - return $res; - } - - - /** - * @param array $criteria - * @param mixed $sessid - * This variable isnt used. - * @return unknown - */ - public function localSearch($criteria, $sessid='') - { - $limit = intval(isset($criteria['limit']) ? $criteria['limit'] : 0); - $offset = intval(isset($criteria['offset']) ? $criteria['offset'] : 0); - $res = $this->bsLocalSearch($criteria, $limit, $offset); - return $res; - } - - - /** - * Return values of specified metadata category - * - * @param string $category - * metadata category name - * with or without namespace prefix (dc:title, author) - * @param hash $criteria - * see searchMetadata method - * @param string $sessid - * @return array - * hash, fields: - * results : array with found values - * cnt : integer - number of matching values - * @see BasicStor::bsBrowseCategory - */ - protected function browseCategory($category, $criteria=NULL, $sessid='') - { - $limit = intval(isset($criteria['limit']) ? $criteria['limit'] : 0); - $offset = intval(isset($criteria['offset']) ? $criteria['offset'] : 0); - $res = $this->bsBrowseCategory($category, $limit, $offset, $criteria); - return $res; - } - - - /* ----------------------------------------------------------------- etc. */ - /** - * Check if audio clip exists - * - * @param string $sessid - * @param string $gunid - * @return boolean - */ - protected function existsAudioClip($sessid, $gunid) - { - $ex = $this->existsFile($sessid, $gunid, 'audioclip'); - // webstreams are subset of audioclips - moved to BasicStor - // if($ex === FALSE ){ - // $ex = $this->existsFile($sessid, $gunid, 'webstream'); - // } - if ($ex === FALSE ) { - return FALSE; - } - if (PEAR::isError($ex)) { - return $ex; - } - $storedFile =& StoredFile::RecallByGunid($gunid); - if (is_null($storedFile) || PEAR::isError($storedFile)) { - return $storedFile; - } - return $storedFile->exists(); - } - - - /** - * Check if file exists in the storage - * - * @param string $sessid - * @param string $gunid - * @param string $ftype - * internal file type - * @return boolean - */ - protected function existsFile($sessid, $gunid, $ftype=NULL) - { - if (($res = BasicStor::Authorize('read', $id, $sessid)) !== TRUE) { - return $res; - } - $f = StoredFile::RecallByGunid($gunid); - if (PEAR::isError($f)) { - return FALSE; - } - return $f->existsFile(); - } - - - /** - * Delete existing audio clip - * - * @param string $sessid - * @param string $gunid - * @param boolean $forced - * if true, don't use trash - * @return boolean|PEAR_Error - */ - protected function deleteAudioClip($sessid, $gunid, $forced=FALSE) - { - $storedFile =& StoredFile::RecallByGunid($gunid); - if (is_null($storedFile)) { - return TRUE; - } - if (PEAR::isError($storedFile)) { - if ($storedFile->getCode()==GBERR_FOBJNEX && $forced) { - return TRUE; - } - return $storedFile; - } - if (($res = BasicStor::Authorize('write', $storedFile->getId(), $sessid)) !== TRUE) { - return $res; - } - $res = $storedFile->delete(); - if (PEAR::isError($res)) { - return $res; - } - return TRUE; - } - - - /** - * Update existing audio clip metadata - * - * @param string $sessid - * @param string $gunid - * @param string $metadata - * metadata XML string - * @return boolean|PEAR_Error - */ - protected function updateAudioClipMetadata($sessid, $gunid, $metadata) - { - $storedFile =& StoredFile::RecallByGunid($gunid); - if (is_null($storedFile) || PEAR::isError($storedFile)) { - return $storedFile; - } - if (($res = BasicStor::Authorize('write', $storedFile->getId(), $sessid)) !== TRUE) { - return $res; - } - return $storedFile->setMetadata($metadata, 'string'); - } - - - /*====================================================== playlist methods */ - /** - * Create a new empty playlist. - * - * @param string $sessid - * session ID - * @param string $playlistId - * playlist global unique ID - * @param string $fname - * human readable mnemonic file name - * @return string - * playlist global unique ID - */ - public function createPlaylist($sessid, $playlistId, $fname) - { - $ex = $this->existsPlaylist($sessid, $playlistId); - if (PEAR::isError($ex)) { - return $ex; - } - if ($ex) { - return PEAR::raiseError( - 'LocStor::createPlaylist: already exists' - ); - } - $tmpFname = uniqid(''); - if (($res = BasicStor::Authorize('write', null, $sessid)) !== TRUE) { - return $res; - } - $values = array( - "metadata" => dirname(__FILE__).'/emptyPlaylist.xml', - "gunid" => $playlistId, - "filetype" => "playlist"); - // This is all wrong now. - $storedFile = StoredFile::Insert($values); - if ($fname == '') { - $fname = "newFile.xml"; - } - $storedFile->setName($fname); - $storedFile->setState('ready'); - $storedFile->setMime('application/smil'); - return $storedFile->gunid; - } - - - /** - * Open a Playlist metafile for editing. - * Open readable URL and mark file as beeing edited. - * - * @param string $sessid - * session ID - * @param string $playlistId - * playlist global unique ID - * @return struct - * {url:readable URL for HTTP GET, token:access token, chsum:checksum} - */ - public function editPlaylist($sessid, $playlistId) - { - $ex = $this->existsPlaylist($sessid, $playlistId); - if (PEAR::isError($ex)) { - return $ex; - } - if (!$ex) { - return PEAR::raiseError( - 'LocStor::editPlaylist: playlist not exists' - ); - } - if ($this->isEdited($playlistId) !== FALSE) { - return PEAR::raiseError( - 'LocStor::editPlaylist: playlist already edited' - ); - } - $storedFile =& StoredFile::RecallByGunid($playlistId); - if (is_null($storedFile) || PEAR::isError($storedFile)) { - return $storedFile; - } - $id = $storedFile->getId(); - if (($res = BasicStor::Authorize('write', $id, $sessid)) !== TRUE) { - return $res; - } - $res = $this->bsOpenDownload($id, 'metadata'); - if (PEAR::isError($res)) { - return $res; - } - $r = $this->setEditFlag($playlistId, TRUE, $sessid); - if (PEAR::isError($r)) { - return $r; - } - unset($res['filename']); - return $res; - } - - - /** - * Store a new Playlist metafile in place of the old one. - * - * @param string $sessid - * session ID - * @param string $playlistToken - * playlist access token - * @param string $newPlaylist - * new playlist as XML string - * @return string - * playlistId - */ - protected function savePlaylist($sessid, $playlistToken, $newPlaylist) - { - $playlistId = $this->bsCloseDownload($playlistToken, 'metadata'); - if (PEAR::isError($playlistId)) { - return $playlistId; - } - $storedFile =& StoredFile::RecallByGunid($playlistId); - if (is_null($storedFile) || PEAR::isError($storedFile)) { - return $storedFile; - } - $res = $storedFile->setMetadata($newPlaylist, 'string', 'playlist'); - if (PEAR::isError($res)) { - return $res; - } - $r = $this->setEditFlag($playlistId, FALSE, $sessid); - if (PEAR::isError($r)) { - return $r; - } - return $playlistId; - } - - - /** - * RollBack playlist changes to the locked state - * - * @param string $playlistToken - * playlist access token - * @param string $sessid - * session ID - * @return string - * gunid of playlist - */ - public function revertEditedPlaylist($playlistToken, $sessid='') - { - $gunid = $this->bsCloseDownload($playlistToken, 'metadata'); - if (PEAR::isError($gunid)) { - return $gunid; - } - $storedFile =& StoredFile::RecallByGunid($gunid); - if (is_null($storedFile) || PEAR::isError($storedFile)) { - return $storedFile; - } - $id = $storedFile->getId(); - $mdata = $storedFile->getMetadata(); - if (PEAR::isError($mdata)) { - return $mdata; - } - $res = $storedFile->setMetadata($mdata, 'string'); - if (PEAR::isError($res)) { - return $res; - } - $this->setEditFlag($gunid, FALSE, $sessid); - return $gunid; - } - - - /** - * Delete a Playlist metafile. - * - * @param string $sessid - * session ID - * @param string $playlistId - * playlist global unique ID - * @param boolean $forced - * if true don't use trash - * @return boolean - */ - public function deletePlaylist($sessid, $playlistId, $forced=FALSE) - { - $ex = $this->existsPlaylist($sessid, $playlistId); - if (PEAR::isError($ex)) { - return $ex; - } - if (!$ex) { - if ($forced) { - return TRUE; - } - return PEAR::raiseError( - 'LocStor::deletePlaylist: playlist not exists', - GBERR_FILENEX - ); - } - $storedFile =& StoredFile::RecallByGunid($playlistId); - if (is_null($storedFile) || PEAR::isError($storedFile)) { - return $storedFile; - } - if (($res = BasicStor::Authorize('write', $storedFile->getId(), $sessid)) !== TRUE) { - return $res; - } - $res = $storedFile->delete(); - if (PEAR::isError($res)) { - return $res; - } - return TRUE; - } - - - /** - * Access (read) a Playlist metafile. - * - * @param string $sessid - * session ID - * @param string $playlistId - * playlist global unique ID - * @param boolean $recursive - * flag for recursive access content inside playlist - * @param int $parent - * parent token - * @return struct { - * url: readable URL for HTTP GET, - * token: access token, - * chsum: checksum, - * content: array of structs - recursive access (optional) - * filename: string mnemonic filename - * } - */ - public function accessPlaylist($sessid, $playlistId, $recursive=FALSE, $parent='0') - // { - // if ($recursive) { - // require_once("AccessRecur.php"); - // $r = AccessRecur::accessPlaylist($this, $sessid, $playlistId); - // if (PEAR::isError($r)) { - // return $r; - // } - // return $r; - // } - // $ex = $this->existsPlaylist($sessid, $playlistId); - // if (PEAR::isError($ex)) { - // return $ex; - // } - // if (!$ex) { - // return PEAR::raiseError( - // "LocStor::accessPlaylist: playlist not found ($playlistId)", - // GBERR_NOTF - // ); - // } - // $id = BasicStor::IdFromGunid($playlistId); - // if (($res = BasicStor::Authorize('read', $id, $sessid)) !== TRUE) { - // return $res; - // } - // $res = $this->bsOpenDownload($id, 'metadata', $parent); - // #unset($res['filename']); - // return $res; - } - - - /** - * Release the resources obtained earlier by accessPlaylist(). - * - * @param string $sessid - * session ID - * @param string $playlistToken - * playlist access token - * @param boolean $recursive - * flag for recursive access content inside playlist - * @return string - * playlist ID - */ - public function releasePlaylist($sessid, $playlistToken, $recursive=FALSE) - { - if ($recursive) { - require_once"AccessRecur.php"; - $r = AccessRecur::releasePlaylist($this, $sessid, $playlistToken); - if (PEAR::isError($r)) { - return $r; - } - return $r; - } - return $this->bsCloseDownload($playlistToken, 'metadata'); - } - - - /** - * Create a tarfile with playlist export - playlist and all matching - * sub-playlists and media files (if desired) - * - * @param string $sessid - * session ID - * @param array $plids - * array of strings, playlist global unique IDs (one gunid is accepted too) - * @param string $type - * playlist format, values: lspl | smil | m3u - * @param boolean $standalone - * if only playlist should be exported or with all related files - * @return hasharray with fields: - * url string: readable url, - * token string: access token - * chsum string: md5 checksum, - */ - protected function exportPlaylistOpen($sessid, $plids, $type='lspl', $standalone=FALSE) - { - $res = $this->bsExportPlaylistOpen($plids, $type, !$standalone); - if (PEAR::isError($res)) { - return $res; - } - $url = BasicStor::GetUrlPart()."access/".basename($res['fname']); - $chsum = md5_file($res['fname']); - $size = filesize($res['fname']); - return array( - 'url' => $url, - 'token' => $res['token'], - 'chsum' => $chsum, - ); - } - - - /** - * Close playlist export previously opened by the exportPlaylistOpen method - * - * @param string $token - * access token obtained from exportPlaylistOpen method call - * @return boolean|PEAR_Error - */ - protected function exportPlaylistClose($token) - { - return $this->bsExportPlaylistClose($token); - } - - - /** - * Open writable handle for import playlist in LS Archive format - * - * @param string $sessid - * session id - * @param string $chsum - * md5 checksum of imported file - * @return hasharray with: - * url string: writable URL - * token string: PUT token - */ - protected function importPlaylistOpen($sessid, $chsum) - { - $userid = Alib::GetSessUserId($sessid); - if (PEAR::isError($userid)) { - return $userid; - } - $r = $this->bsOpenPut($chsum, NULL, $userid); - if (PEAR::isError($r)) { - return $r; - } - return $r; - } - - - /** - * Close import-handle and import playlist - * - * @param string $token - * import token obtained by importPlaylistOpen method - * @return string - * result file global id (or error object) - */ - protected function importPlaylistClose($token) - { - $arr = $this->bsClosePut($token); - if (PEAR::isError($arr)) { - return $arr; - } - $fname = $arr['fname']; - $owner = $arr['owner']; - $res = $this->bsImportPlaylist($fname); - if (file_exists($fname)) { - @unlink($fname); - } - if (PEAR::isError($res)) { - return $res; - } - $media = StoredFile::Recall($id); - return $media->getGunId(); - } - - - /** - * Check whether a Playlist metafile with the given playlist ID exists. - * - * @param string $sessid - * session ID - * @param string $playlistId - * playlist global unique ID - * @return boolean - */ - public function existsPlaylist($sessid, $playlistId) - { - return $this->existsFile($sessid, $playlistId, 'playlist'); - } - - - /** - * Check whether a Playlist metafile with the given playlist ID - * is available for editing, i.e., exists and is not marked as - * being edited. - * - * @param string $sessid - * session ID - * @param string $playlistId - * playlist global unique ID - * @param boolean $getUid - * flag for returning editedby uid - * @return boolean - */ - public function playlistIsAvailable($sessid, $playlistId, $getUid=FALSE) - { - $ex = $this->existsPlaylist($sessid, $playlistId); - if (PEAR::isError($ex)) { - return $ex; - } - if (!$ex) { - return PEAR::raiseError( - 'LocStor::playlistIsAvailable: playlist not exists' - ); - } - $ie = $this->isEdited($playlistId); - if ($ie === FALSE) { - return TRUE; - } - if ($getUid) { - return $ie; - } - return FALSE; - } - - - /* ------------------------------------------------------- render methods */ - /** - * Render playlist to ogg file (open handle) - * - * @param string $sessid - * session id - * @param string $plid - * playlist gunid - * @return hasharray - * token: string - render token - */ - protected function renderPlaylistToFileOpen($sessid, $plid) - { - require_once("Renderer.php"); - $r = Renderer::rnRender2FileOpen($this, $plid); - if (PEAR::isError($r)) { - return $r; - } - return $r; - } - - - /** - * Render playlist to ogg file (check results) - * - * @param string $token - * render token - * @return hasharray: - * status : string - success | working | fault - * url : string - readable url - */ - protected function renderPlaylistToFileCheck($token) - { - require_once("Renderer.php"); - $r = Renderer::rnRender2FileCheck($this, $token); - if (PEAR::isError($r)) { - return $r; - } - return array('status'=>$r['status'], 'url'=>$r['url']); - } - - - /** - * Render playlist to ogg file (close handle) - * - * @param string $token - * render token - * @return boolean status - */ - protected function renderPlaylistToFileClose($token) - { - require_once("Renderer.php"); - $r = Renderer::rnRender2FileClose($this, $token); - if (PEAR::isError($r)) { - return $r; - } - return array(TRUE); - } - - - /** - * Render playlist to storage media clip (open handle) - * - * @param string $sessid - * session id - * @param string $plid - * playlist gunid - * @return string - * render token - */ - protected function renderPlaylistToStorageOpen($sessid, $plid) - { - require_once("Renderer.php"); - $owner = Alib::GetSessUserId($sessid); - if (PEAR::isError($owner)) { - return $owner; - } - $r = Renderer::rnRender2FileOpen($this, $plid, $owner); - if (PEAR::isError($r)) { - return $r; - } - return $r; - } - - - /** - * Render playlist to storage media clip (check results) - * - * @param string $token - * render token - * @return hasharray: - * status : string - success | working | fault - * gunid : string - gunid of result file - */ - protected function renderPlaylistToStorageCheck($token) - { - require_once("Renderer.php"); - $r = Renderer::rnRender2StorageCheck($this, $token); - if (PEAR::isError($r)) { - return $r; - } - return $r; - } - - - /** - * Render playlist to RSS file (open handle) - * - * @param string $sessid - * session id - * @param string $plid - * playlist gunid - * @return string - * render token - */ - protected function renderPlaylistToRSSOpen($sessid, $plid) - { - global $CC_CONFIG; - $token = '123456789abcdeff'; - $fakeFile = $CC_CONFIG['accessDir']."/$token.rss"; - file_put_contents($fakeFile, "fake rendered file"); - return array('token'=>$token); - } - - - /** - * Render playlist to RSS file (check results) - * - * @param string $token - * render token - * @return hasharray : - * status : string - success | working | fault - * url : string - readable url - */ - protected function renderPlaylistToRSSCheck($token) - { - $fakeFile = $CC_CONFIG['accessDir']."/$token.rss"; - if ($token != '123456789abcdeff' || !file_exists($fakeFile)) { - return PEAR::raiseError( - "LocStor::renderPlaylistToRSSCheck: invalid token ($token)" - ); - } - $fakeFUrl = BasicStor::GetUrlPart()."access/$token.rss"; - return array( - 'status'=> 'success', - 'url' => $fakeFUrl, - ); - } - - - /** - * Render playlist to RSS file (close handle) - * - * @param string $token - * render token - * @return boolean - * status - */ - protected function renderPlaylistToRSSClose($token) - { - if ($token != '123456789abcdeff') { - return PEAR::raiseError( - "LocStor::renderPlaylistToRSSClose: invalid token" - ); - } - $fakeFile = $CC_CONFIG['accessDir']."/$token.rss"; - unlink($fakeFile); - return TRUE; - } - - - /*================================================= storage admin methods */ - - /* ------------------------------------------------------- backup methods */ - - /** - * Create backup of storage (open handle) - * - * @param string $sessid - * session id - * @param array $criteria - * see search criteria - * @return array - * token : string - backup token - */ - protected function createBackupOpen($sessid, $criteria='') - { - require_once("Backup.php"); - $bu = new Backup($this); - if (PEAR::isError($bu)) { - return $bu; - } - $r = $bu->openBackup($sessid,$criteria); - if ($r === FALSE) { - return PEAR::raiseError( - "LocStor::createBackupOpen: false returned from Backup" - ); - } - return $r; - } - - - /** - * Create backup of storage (check results) - * - * @param string $token - * backup token - * @return hasharray - * with field: - * status : string - susccess | working | fault - * faultString: string - description of fault - * token : stirng - backup token - * url : string - access url - */ - protected function createBackupCheck($token) - { - require_once("Backup.php"); - $bu = new Backup($this); - if (PEAR::isError($bu)) { - return $bu; - } - return $bu->checkBackup($token); - } - - - /** - * Create backup of storage (list results) - * - * @param string $sessid - * session id - * @param status $stat - * if this parameter is not set, then return with all unclosed backups - * @return array - * array of hasharray with field: - * status : string - susccess | working | fault - * token : stirng - backup token - * url : string - access url - */ - protected function createBackupList($sessid, $stat='') - { - require_once("Backup.php"); - $bu = new Backup($this); - if (PEAR::isError($bu)) { - return $bu; - } - return $bu->listBackups($stat); - } - - - /** - * Create backup of storage (close handle) - * - * @param string $token - * backup token - * @return boolean - * status - */ - protected function createBackupClose($token) - { - require_once("Backup.php"); - $bu = new Backup($this); - if (PEAR::isError($bu)) { - return $bu; - } - return $bu->closeBackup($token); - } - - - /* ------------------------------------------------------ restore methods */ - - /** - * Restore a backup file (open handle) - * - * @param string $sessid - * session id - * @param string $chsum - * md5 checksum of imported file - * @return array - * array with: - * url string: writable URL - * fname string: writable local filename - * token string: PUT token - */ - protected function restoreBackupOpen($sessid, $chsum) - { - $userid = Alib::getSessUserId($sessid); - if (PEAR::isError($userid)) { - return $userid; - } - $r = $this->bsOpenPut($chsum, NULL, $userid); - if (PEAR::isError($r)) { - return $r; - } - return $r; - } - - - /** - * Restore a backup file (close put handle) - * - * @param string $sessid - * session id - * @param string $token - * "put" token - * @return string $token - * restore token - */ - protected function restoreBackupClosePut($sessid, $token) { - $arr = $this->bsClosePut($token); - if (PEAR::isError($arr)) { - return $arr; - } - $fname = $arr['fname']; - require_once('Restore.php'); - $rs = new Restore($this); - if (PEAR::isError($rs)) { - return $rs; - } - return $rs->openRestore($sessid, $fname); - } - - - /** - * Restore a backup file (check state) - * - * @param string $token - * restore token - * @return array - * status - fields: - * token: string - restore token - * status: string - working | fault | success - * faultString: string - description of fault - */ - protected function restoreBackupCheck($token) - { - require_once('Restore.php'); - $rs = new Restore($this); - if (PEAR::isError($rs)) { - return $rs; - } - return $rs->checkRestore($token); - } - - - /** - * Restore a backup file (close handle) - * - * @param string $token - * restore token - * @return array - * status - fields: - * token: string - restore token - * status: string - working | fault | success - */ - protected function restoreBackupClose($token) { - require_once('Restore.php'); - $rs = new Restore($this); - if (PEAR::isError($rs)) { - return $rs; - } - return $rs->closeRestore($token); - } - - - /*===================================================== auxiliary methods */ - /** - * Dummy method - only returns Airtime version - * - * @return string - */ - public static function getVersion() - { - return AIRTIME_VERSION; - } - - /** - * Open upload transport (from station to hub) - * - * @param string $sessid - * session id - * @param string $chsum - * checksum - * @return array - * hasharray with: - * url string: writable URL - * token string: PUT token - */ - function uploadOpen($sessid, $chsum) - { - $owner = Alib::GetSessUserId($sessid); - if (PEAR::isError($owner)) { - return $owner; - } - $res = $this->bsOpenPut($chsum, NULL, $owner); - if (PEAR::isError($res)) { - return $res; - } - return array('url'=>$res['url'], 'token'=>$res['token']); - } - - - /** - * Close upload transport - * - * @param string $token - * transport token - * @param string $trtype - * transport type - * @param array $pars - * transport parameters - * @return mixed - */ - function uploadClose($token, $trtype, $pars=array()) - { - $res = $this->bsClosePut($token); - if (PEAR::isError($res)) { - return $res; - } - extract($res); // fname, owner - switch ($trtype) { - case "audioclip": - $mdtoken = $pars['mdpdtoken']; - $res = $this->bsClosePut($mdtoken); - if (PEAR::isError($res)) { - return $res; - } - $mdfname = $res['fname']; - if ($gunid == '') { - $gunid = NULL; - } - $values = array( - "filename" => $pars['name'], - "filepath" => $fname, - "metadata" => $mdfname, - "gunid" => $pars['gunid'], - "filetype" => "audioclip" - ); - $storedFile = StoredFile::Insert($values); - if (PEAR::isError($storedFile)) { - return $storedFile; - } - $res = $storedFile->getId(); - @unlink($fname); - @unlink($mdfname); - break; - case "playlist": - if ($gunid == '') { - $gunid = NULL; - } - $values = array( - "filename" => $pars['name'], - "metadata" => $fname, - "gunid" => $pars['gunid'], - "filetype" => "playlist" - ); - $storedFile = StoredFile::Insert($values); - if (PEAR::isError($storedFile)) { - return $storedFile; - } - $res = $storedFile->getId(); - @unlink($fname); - break; - case "playlistPkg": - $chsum = md5_file($fname); - // importPlaylistOpen: - $res = $this->bsOpenPut($chsum, NULL, $owner); - if (PEAR::isError($res)) { - return $res; - } - $dest = $res['fname']; - $token = $res['token']; - copy($fname, $dest); - $r = $this->importPlaylistClose($token); - if (PEAR::isError($r)) { - return $r; - } - @unlink($fname); - return $r; - break; - case "searchjob": - $crits = file_get_contents($fname); - $criteria = unserialize($crits); - @unlink($fname); - $results = $this->localSearch($criteria); - if (PEAR::isError($results)) { - return $results; - } - $realfile = tempnam($this->accessDir, 'searchjob_'); - @chmod($realfile, 0660); - $len = file_put_contents($realfile, serialize($results)); - $acc = BasicStor::bsAccess($realfile, '', NULL, 'download'); - if (PEAR::isError($acc)) { - return $acc; - } - $url = BasicStor::GetUrlPart()."access/".basename($acc['fname']); - $chsum = md5_file($realfile); - $size = filesize($realfile); - $res = array( - 'url'=>$url, 'token'=>$acc['token'], - 'chsum'=>$chsum, 'size'=>$size, - 'filename'=>$filename - ); - return $res; - break; - case "metadata": - break; - default: - } - return $res; - } - - - /** - * Open download transport - * - * @param string $sessid - * session id - * @param string $trtype - * transport type - * @param array $pars - * transport parameters - * @return hasharray with: - * url string: writable URL - * token string: PUT token - */ - function downloadOpen($sessid, $trtype, $pars=array()) - { - // global $CC_CONFIG; - // switch ($trtype) { - // case "unknown": - // case "audioclip": - // case "metadata": - // case "playlist": - // case "playlistPkg": - // if (!isset($pars['gunid'])) { - // return PEAR::raiseError("Archive::downloadOpen: gunid not set"); - // } - // break; - // } - // $gunid = $pars['gunid']; - // // resolve trtype by object type: - // if ( ($trtype == 'unknown') || ($trtype == 'playlistPkg') ) { - // $media = StoredFile::RecallByGunid($gunid); - // $trtype2 = $media->getType(); - // if (PEAR::isError($trtype2)) { - // return $trtype2; - // } - // // required with content: - // $trtype = ( ($trtype2 == 'playlist') && ($trtype == 'playlistPkg') ? - // 'playlistPkg' : $trtype2); - // //return PEAR::raiseError("Archive::downloadOpen: TT=$trtype TT2=$trtype2 G=$gunid"); - // } - // switch ($trtype) { - // case "audioclip": - // $res = $this->downloadRawAudioDataOpen($sessid, $gunid); - // break; - // case "metadata": - // $res = $this->downloadMetadataOpen($sessid, $gunid); - // break; - // case "playlist": - // $res = $this->accessPlaylist($sessid, $gunid); - // break; - // case "playlistPkg": - // $res = $this->bsExportPlaylistOpen($gunid); - // if (PEAR::isError($res)) { - // return $res; - // } - // $tmpn = tempnam($CC_CONFIG['transDir'], 'plExport_'); - // $plfpath = "$tmpn.lspl"; - // copy($res['fname'], $plfpath); - // $res = $this->bsExportPlaylistClose($res['token']); - // if (PEAR::isError($res)) { - // return $res; - // } - // $fname = "transported_playlist.lspl"; - // $id = BasicStor::IdFromGunid($gunid); - // $acc = BasicStor::bsAccess($plfpath, 'lspl', NULL, 'download'); - // if (PEAR::isError($acc)) { - // return $acc; - // } - // $url = BasicStor::GetUrlPart()."access/".basename($acc['fname']); - // $chsum = md5_file($plfpath); - // $size = filesize($plfpath); - // $res = array( - // 'url'=>$url, 'token'=>$acc['token'], - // 'chsum'=>$chsum, 'size'=>$size, - // 'filename'=>$fname - // ); - // break; - // case "searchjob": - // $res = $pars; - // break; - // case "file": - // $res = array(); - // break; - // default: - // return PEAR::raiseError("Archive::downloadOpen: NotImpl ($trtype)"); - // } - // if (PEAR::isError($res)) { - // return $res; - // } - // switch ($trtype) { - // case "audioclip": - // case "metadata": - // case "playlist": - // case "playlistPkg": - // $f = StoredFile::RecallByGunid($gunid); - // $title = $f->getTitle(); - // break; - // case "searchjob": - // $title = 'searchjob'; - // break; - // case "file": - // $title = 'regular file'; - // break; - // default: - // } - // $res['title'] = $title; - // $res['trtype'] = $trtype; - // return $res; - } - - - /** - * Close download transport - * - * @param string $token - * transport token - * @param string $trtype - * transport type - * @return array - * hasharray with: - * url string: writable URL - * token string: PUT token - */ - function downloadClose($token, $trtype) - { - switch ($trtype) { - case "audioclip": - $res = $this->downloadRawAudioDataClose($token); - if (PEAR::isError($res)) { - return $res; - } - return $res; - case "metadata": - $res = $this->downloadMetadataClose($token); - return $res; - case "playlist": - $res = $this->releasePlaylist(NULL/*$sessid*/, $token); - return $res; - case "playlistPkg": - $res = BasicStor::bsRelease($token, 'download'); - if (PEAR::isError($res)) { - return $res; - } - $realFname = $r['realFname']; - @unlink($realFname); - if (preg_match("|(plExport_[^\.]+)\.lspl$|", $realFname, $va)) { - list(,$tmpn) = $va; - $tmpn = $CC_CONFIG['transDir']."/$tmpn"; - if (file_exists($tmpn)) { - @unlink($tmpn); - } - } - return $res; - case "searchjob": - $res = BasicStor::bsRelease($token, 'download'); - return $res; - case "file": - return array(); - default: - return PEAR::raiseError("Archive::downloadClose: NotImpl ($trtype)"); - } - } - - - /** - * Prepare hub initiated transport - * - * @param string $target - * hostname of transport target - * @param string $trtype - * transport type - * @param string $direction - * 'up' | 'down' - * @param array $pars - * transport parameters - * @return mixed - */ - function prepareHubInitiatedTransfer( - $target, $trtype='file', $direction='up',$pars=array()) - { - $tr = new Transport($this); - $trec = TransportRecord::create($tr, $trtype, $direction, - array_merge($pars, array('target'=>$target))); - if (PEAR::isError($trec)) { - return $trec; - } - return TRUE; - } - - - /** - * List hub initiated transports - * - * @param string $target - * hostname of transport target - * @param string $direction - * 'up' | 'down' - * @param string $trtok - * transport token - * @return mixed - */ - function listHubInitiatedTransfers( - $target=NULL, $direction=NULL, $trtok=NULL) - { - $tr = new Transport($this); - $res = $tr->getTransports($direction, $target, $trtok); - return $res; - } - - - /** - * Set state of hub initiated transport - * - * @param string $target - * hostname of transport target - * @param string $trtok - * transport token - * @param string $state - * transport state - * @return TransportRecord|PEAR_Error - */ - function setHubInitiatedTransfer($target, $trtok, $state) - { - $tr = new Transport($this); - $trec = TransportRecord::recall($tr, $trtok); - if (PEAR::isError($trec)) { - return $trec; - } - $r = $trec->setState($state); - if (PEAR::isError($r)) { - return $r; - } - return $trec; - } - - /* ==================================================== auxiliary methods */ - -} // class LocStor diff --git a/application/models/M3uPlaylist.php b/application/models/M3uPlaylist.php deleted file mode 100644 index 8504ac9e3..000000000 --- a/application/models/M3uPlaylist.php +++ /dev/null @@ -1,351 +0,0 @@ -lock($gb, $subjid); -// if (PEAR::isError($r)) { -// return $r; -// } -// foreach ($arr as $i => $it) { -// list($md, $uri) = preg_split("|\n|", $it); -// list($length, $title) = preg_split("|, *|", $md); -// // $gunid = StoredFile::CreateGunid(); -// $gunid = ( isset($gunids[basename($uri)]) ? $gunids[basename($uri)] : NULL); -// $acId = BasicStor::IdFromGunid($gunid); -// if (PEAR::isError($acId)) { -// return $acId; -// } -// $length = Playlist::secondsToPlaylistTime($length); -// $offset = '???'; -// if (preg_match("|\.([a-zA-Z0-9]+)$|", $uri, $va)) { -// switch (strtolower($ext = $va[1])) { -// case "lspl": -// case "xml": -// case "smil": -// case "m3u": -// $acId = $gb->bsImportPlaylistRaw($gunid, -// $aPath, $uri, $ext, $gunids, $subjid); -// if (PEAR::isError($acId)) { -// break; -// } -// //no break! -// default: -// if (is_null($gunid)) { -// return PEAR::raiseError( -// "M3uPlaylist::import: no gunid"); -// } -// $r = $pl->addAudioClip($acId); -// if (PEAR::isError($r)) { -// return $r; -// } -// } -// } -// } -// $r = $pl->unlock($gb); -// if (PEAR::isError($r)) { -// return $r; -// } -// return $pl; - } - - /** - * Import M3U file to storage - * - * @param GreenBox $gb - * @param string $data - * local path to M3U file - * @return string - * XML playlist in Airtime playlist format - */ - function convert2lspl(&$gb, $data) - { - $arr = M3uPlaylist::parse($data); - if (PEAR::isError($arr)) { - return $arr; - } - $ind = ''; - $ind2 = $ind.INDCH; - $ind3 = $ind2.INDCH; - $res = ''; - foreach ($arr as $i => $it) { - list($md, $uri) = preg_split("|\n|", $it); - list($length, $title) = preg_split("|, *|", $md); - $gunid = StoredFile::CreateGunid(); - $gunid2 = StoredFile::CreateGunid(); - $length = Playlist::secondsToPlaylistTime($length); - $offset = '???'; - $clipStart = '???'; - $clipEnd = '???'; - $clipLength = '???'; - $uri_h = preg_replace("|--|", "d;d;", htmlspecialchars("$uri")); - if (preg_match("|\.([a-zA-Z0-9]+)$|", $uri, $va)) { - switch (strtolower($ext = $va[1])) { - case "lspl": - case "xml": - case "smil": - case "m3u": - $acOrPl = "$ind3 ". - "\n"; - break; - default: - $acOrPl = "$ind3 ". - "\n"; - break; - } - } - $res .= "$ind2\n". - $acOrPl. - "$ind2\n"; - } - $res = "$ind\n". - "$ind\n". - "$ind2\n". - "$res". - "$ind\n"; - return $res; - } -} // class M3uPlaylist - - -/** - * @package Airtime - * @subpackage StorageServer - * @copyright 2010 Sourcefabric O.P.S. - * @license http://www.gnu.org/licenses/gpl.txt - */ -class M3uPlaylistBodyElement { - function convert2lspl(&$tree, $ind='') - { - $ind2 = $ind.INDCH; - if ($tree->name != 'body') { - return PEAR::raiseError("M3uPlaylist::parse: body tag expected"); - } - if (isset($tree->children[1])) { - return PEAR::raiseError(sprintf( - "M3uPlaylist::parse: unexpected tag %s in tag body", - $tree->children[1]->name - )); - } - $res = M3uPlaylistParElement::convert2lspl($tree->children[0], $ind2); - if (PEAR::isError($res)) { - return $res; - } - $gunid = StoredFile::CreateGunid(); - $playlength = '???'; // *** - $res = "$ind\n". - "$ind\n". - "$ind2\n". - "$res". - "$ind\n"; - return $res; - } -} - - -/** - * @package Airtime - * @subpackage StorageServer - * @copyright 2010 Sourcefabric O.P.S. - * @license http://www.gnu.org/licenses/gpl.txt - */ -class M3uPlaylistParElement { - function convert2lspl(&$tree, $ind='') - { - if ($tree->name != 'par') { - return PEAR::raiseError("M3uPlaylist::parse: par tag expected"); - } - $res = ''; - foreach ($tree->children as $i => $ch) { - $ch =& $tree->children[$i]; - $r = M3uPlaylistAudioElement::convert2lspl($ch, $ind.INDCH); - if (PEAR::isError($r)) { - return $r; - } - $res .= $r; - } - return $res; - } -} - - -/** - * @package Airtime - * @subpackage StorageServer - * @copyright 2010 Sourcefabric O.P.S. - * @license http://www.gnu.org/licenses/gpl.txt - */ -class M3uPlaylistAudioElement { - function convert2lspl(&$tree, $ind='') - { - $ind2 = $ind.INDCH; - if ($tree->name != 'audio') { - return PEAR::raiseError("M3uPlaylist::parse: audio tag expected"); - } - if (isset($tree->children[2])) { - return PEAR::raiseError(sprintf( - "M3uPlaylist::parse: unexpected tag %s in tag audio", - $tree->children[2]->name - )); - } - $res = ''; $fadeIn = 0; $fadeOut = 0; - foreach ($tree->children as $i => $ch) { - $ch =& $tree->children[$i]; - $r = M3uPlaylistAnimateElement::convert2lspl($ch, $ind2); - if (PEAR::isError($r)) { - return $r; - } - switch ($r['type']) { - case "fadeIn": - $fadeIn = $r['val']; - break; - case "fadeOut": - $fadeOut = $r['val']; - break; - } - } - if ($fadeIn > 0 || $fadeOut > 0) { - $fadeIn = Playlist::secondsToPlaylistTime($fadeIn); - $fadeOut = Playlist::secondsToPlaylistTime($fadeOut); - $fInfo = "$ind2\n"; - } else { - $fInfo = ''; - } - $plElGunid = StoredFile::CreateGunid(); - $aGunid = StoredFile::CreateGunid(); - $title = basename($tree->attrs['src']->val); - $offset = Playlist::secondsToPlaylistTime($tree->attrs['begin']->val); - $playlength = '???'; # *** - $res = "$ind\n". - "$ind2\n". - $fInfo. - "$ind\n"; - return $res; - } -} // class M3uPlaylistAudioElement - - -/** - * @package Airtime - * @subpackage StorageServer - * @copyright 2010 Sourcefabric O.P.S. - * @license http://www.gnu.org/licenses/gpl.txt - */ -class M3uPlaylistAnimateElement { - function convert2lspl(&$tree, $ind='') { - if ($tree->name != 'animate') { - return PEAR::raiseError("M3uPlaylist::parse: animate tag expected"); - } - if ($tree->attrs['attributeName']->val == 'soundLevel' && - $tree->attrs['from']->val == '0%' && - $tree->attrs['to']->val == '100%' && - $tree->attrs['calcMode']->val == 'linear' && - $tree->attrs['fill']->val == 'freeze' && - $tree->attrs['begin']->val == '0s' && - preg_match("|^([0-9.]+)s$|", $tree->attrs['end']->val, $va) - ) { - return array('type'=>'fadeIn', 'val'=>$va[1]); - } - if ($tree->attrs['attributeName']->val == 'soundLevel' && - $tree->attrs['from']->val == '100%' && - $tree->attrs['to']->val == '0%' && - $tree->attrs['calcMode']->val == 'linear' && - $tree->attrs['fill']->val == 'freeze' && - preg_match("|^([0-9.]+)s$|", $tree->attrs['begin']->val, $vaBegin) && - preg_match("|^([0-9.]+)s$|", $tree->attrs['end']->val, $vaEnd) - ) { - return array('type'=>'fadeOut', 'val'=>($vaEnd[1] - $vaBegin[1])); - } - return PEAR::raiseError( - "M3uPlaylistAnimateElement::convert2lspl: animate parameters too general" - ); - } -} - diff --git a/application/models/Playlist.php b/application/models/Playlist.php index 9d9905e7b..732a142e9 100644 --- a/application/models/Playlist.php +++ b/application/models/Playlist.php @@ -1,10 +1,7 @@ gb =& $gb; } @@ -42,7 +33,7 @@ class Prefs { */ function loadPref($sessid, $key) { - $subjid = GreenBox::GetSessUserId($sessid); + $subjid = Alib::GetSessUserId($sessid); if (PEAR::isError($subjid)) { return $subjid; } @@ -75,7 +66,7 @@ class Prefs { */ function savePref($sessid, $key, $value) { - $subjid = GreenBox::GetSessUserId($sessid); + $subjid = Alib::GetSessUserId($sessid); if (PEAR::isError($subjid)) { return $subjid; } @@ -107,7 +98,7 @@ class Prefs { */ function delPref($sessid, $key) { - $subjid = GreenBox::GetSessUserId($sessid); + $subjid = Alib::GetSessUserId($sessid); if (PEAR::isError($subjid)) { return $subjid; } @@ -180,7 +171,7 @@ class Prefs { */ function saveGroupPref($sessid, $group, $key, $value) { - $uid = GreenBox::GetSessUserId($sessid); + $uid = Alib::GetSessUserId($sessid); if (PEAR::isError($uid)) { return $uid; } @@ -230,7 +221,7 @@ class Prefs { */ function delGroupPref($sessid, $group, $key) { - $uid = GreenBox::GetSessUserId($sessid); + $uid = Alib::GetSessUserId($sessid); if (PEAR::isError($uid)) { return $uid; } diff --git a/application/models/Renderer.php b/application/models/Renderer.php deleted file mode 100644 index 4ee908a50..000000000 --- a/application/models/Renderer.php +++ /dev/null @@ -1,289 +0,0 @@ -outputToSmil(); - if (PEAR::isError($smil)) { - return $smil; - } - // temporary file for smil: - $tmpn = tempnam($CC_CONFIG['bufferDir'], 'plRender_'); - $smilf = "$tmpn.smil"; - file_put_contents($smilf, $smil); - $url = "file://$smilf"; - // output file: - $outf = "$tmpn.".RENDER_EXT; - touch($outf); - // logging: - $logf = $CC_CONFIG['bufferDir']."/renderer.log"; - file_put_contents($logf, "--- ".date("Ymd-H:i:s")."\n", FILE_APPEND); - // open access to output file: /*gunid*/ /*parent*/ - $acc = BasicStor::bsAccess($outf, RENDER_EXT, $plid, 'render', 0, $owner); - if (PEAR::isError($acc)) { - return $acc; - } - extract($acc); - $statf = Renderer::getStatusFile($gb, $token); - file_put_contents($statf, "working"); - // command: - $stServDir = dirname(__FILE__)."/.."; - $renderExe = "$stServDir/bin/renderer.sh"; - $command = "$renderExe -p $url -o $outf -s $statf >> $logf &"; - file_put_contents($logf, "$command\n", FILE_APPEND); - $res = system($command); - if ($res === FALSE) { - return PEAR::raiseError( - 'Renderer::rnRender2File: Error running renderer' - ); - } - return array('token'=>$token); - } - - - /** - * Render playlist to ogg file (check results) - * - * @param GreenBox $gb - * GreenBox object reference - * @param string $token - * render token - * @return array - * status : string - success | working | fault - * url : string - readable url - */ - function rnRender2FileCheck(&$gb, $token) - { - $statf = Renderer::getStatusFile($gb, $token); - if (!file_exists($statf)) { - return PEAR::raiseError( - 'Renderer::rnRender2FileCheck: Invalid token' - ); - } - $status = trim(file_get_contents($statf)); - $url = Renderer::getUrl($gb, $token); - $tmpfile= Renderer::getLocalFile($gb, $token); - return array('status'=>$status, 'url'=>$url, 'tmpfile'=>$tmpfile); - } - - - /** - * Render playlist to ogg file (list results) - * - * @param GreenBox $gb - * greenbox object reference - * @param string $stat - * status (optional) if this parameter is not set, then return with all unclosed backups - * @return array - * array of hasharray: - * status : string - success | working | fault - * url : string - readable url - */ - function rnRender2FileList(&$gb,$stat='') { - // open temporary dir - $tokens = BasicStor::GetTokensByType('render'); - foreach ($tokens as $token) { - $st = Renderer::rnRender2FileCheck($gb, $token); - if ( ($stat=='') || ($st['status']==$stat) ) { - $r[] = $st; - } - } - return $r; - } - - - /** - * Render playlist to ogg file (close handle) - * - * @param GreenBox $gb - * greenbox object reference - * @param string $token - * render token - * @return mixed - * TRUE or PEAR_Error - */ - function rnRender2FileClose(&$gb, $token) - { - global $CC_CONFIG; - $r = BasicStor::bsRelease($token, 'render'); - if (PEAR::isError($r)) { - return $r; - } - $realOgg = $r['realFname']; - $tmpn = $CC_CONFIG['bufferDir']."/".basename($realOgg, '.'.RENDER_EXT); - $smilf = "$tmpn.smil"; - $statf = Renderer::getStatusFile($gb, $token); - @unlink($statf); - @unlink($realOgg); - @unlink($smilf); - @unlink($tmpn); - return TRUE; - } - - - /** - * Render playlist to storage as audioClip (check results) - * - * @param GreenBox $gb - * greenbox object reference - * @param string $token - * render token - * @return array - * status : string - success | working | fault - * gunid: string - global unique id of result file - */ - function rnRender2StorageCheck(&$gb, $token) - { - $r = Renderer::rnRender2FileCheck($gb, $token); - if (PEAR::isError($r)) { - return $r; - } - $status = $r['status']; - $res = array('status' => $status, 'gunid'=>'NULL'); - switch ($status) { - case "fault": - $res['faultString'] = "Error runing renderer"; - break; - case "success": - $r = Renderer::rnRender2StorageCore($gb, $token); - if (PEAR::isError($r)) { - return $r; - } - $res['gunid'] = $r['gunid']; - break; - default: - break; - } - return $res; - } - - - /** - * Render playlist to storage as audioClip (core method) - * - * @param GreenBox $gb - * greenbox object reference - * @param string $token - * render token - * @return array: - * gunid: string - global unique id of result file - */ - function rnRender2StorageCore(&$gb, $token) - { - // $r = BasicStor::bsRelease($token, 'render'); - // if (PEAR::isError($r)) { - // return $r; - // } - // $realOgg = $r['realFname']; - // $owner = $r['owner']; - // $gunid = $r['gunid']; - // $fileName = 'rendered_playlist'; - // $id = BasicStor::IdFromGunid($gunid); - // if (PEAR::isError($id)) { - // return $id; - // } - // $mdata = ''; - // foreach (array('dc:title', 'dcterms:extent', 'dc:creator', 'dc:description') as $item) { - // $val = $gb->bsGetMetadataValue($id, $item); - // $mdata .= " <$item>$val\n"; - // } - // $mdata = "\n \n$mdata \n\n"; - // //$mdata = "\n \n$mdata0\n\n\n"; - // $values = array( - // "filename" => $fileName, - // "filepath" => $realOgg, - // "metadata" => $mdata, - // "filetype" => "audioclip" - // ); - // $storedFile = $gb->bsPutFile($values); - // if (PEAR::isError($storedFile)) { - // return $storedFile; - // } - // return array('gunid' => $storedFile->getGunid()); - } - - - /** - * Return local filepath of rendered file - * - * @param Greenbox $gb - * greenbox object reference - * @param string $token - * render token - * @return array - */ - function getLocalFile(&$gb, $token) - { - global $CC_CONFIG; - $token = StoredFile::NormalizeGunid($token); - return $CC_CONFIG['accessDir']."/$token.".RENDER_EXT; - } - - - /** - * Return filepath of render status file - * - * @param GreenBox $gb - * greenbox object reference - * @param string $token - * render token - * @return array - */ - function getStatusFile(&$gb, $token) - { - return Renderer::getLocalFile($gb, $token).".status"; - } - - - /** - * Return remote accessible URL for rendered file - * - * @param GreenBox $gb - * greenbox object reference - * @param string $token - * render token - * @return array - */ - function getUrl(&$gb, $token) - { - $token = StoredFile::NormalizeGunid($token); - return BasicStor::GetUrlPart()."access/$token.".RENDER_EXT; - } - -} // class Renderer - diff --git a/application/models/Restore.php b/application/models/Restore.php deleted file mode 100644 index 7c03fe5de..000000000 --- a/application/models/Restore.php +++ /dev/null @@ -1,436 +0,0 @@ -gb =& $gb; - $this->token = null; - $this->logFile = $CC_CONFIG['bufferDir'].'/'.$this->ACCESS_TYPE.'.log'; - if ($this->loglevel == 'debug') { - $this->addLogItem("-I- ".date("Ymd-H:i:s")." construct\n"); - } - } - - - /** - * Call asyncronously the restore procedure. Restore from backup. - * - * @param string $sessid - * session id - * @param string $backup_file - * path of the backup file - * @return array - * hasharray with field: - * token string: backup token - */ - function openRestore($sessid, $backup_file) - { - if ($this->loglevel=='debug') { - $this->addLogItem("-I-".date("Ymd-H:i:s")." doRestore - sessid:$sessid\n"); - } - $this->sessid = $sessid; - - // generate token - $this->token = StoredFile::CreateGunid(); - - // status file -> working - $this->setEnviroment(); - file_put_contents($this->statusFile, 'working'); - - //call the restore script in background - $command = dirname(__FILE__).'/../bin/restore.php'; - $runLog = "/dev/null"; - $params = "{$backup_file} {$this->statusFile} {$this->token} {$sessid}>> $runLog &"; - $ret = system("$command $params", $st); - if ($this->loglevel=='debug') { - $this->addLogItem("-I-".date("Ymd-H:i:s")." restore.php call: $st/$ret\n"); - } - - return array('token'=>$this->token); - } - - - /** - * Check the status of restore - * - * @param string $token - * @return array - * hasharray with field: - * status : string - susccess | working | fault - * faultString : string - description of fault - * token : stirng - backup token - * url : string - access url - * tmpfile : string - access filename - */ - function checkRestore($token) - { - if ($this->loglevel == 'debug') { - $this->addLogItem("-I- ".date("Ymd-H:i:s")." checkBackup - token:$token\n"); - } - $this->token = $token; - $this->setEnviroment(); - if (is_file($this->statusFile)) { - $r = array(); - $stat = file_get_contents($this->statusFile); - if (strpos($stat,'fault|') !== false) { - list($stat,$message) = explode('|',$stat); - } - $r['status'] = $stat; - if ($stat=='fault') { - $r['faultString'] = $message; - } else { - $r['faultString'] = ''; - } - return $r; - } else { - return PEAR::raiseError('Restore::checkRestore: invalid token!'); - } - } - - - /** - * Check the status of restore. - * - * @param string $token - * @return array - * hasharray with field: - * status : boolean - is success - */ - function closeRestore($token) - { - if ($this->loglevel=='debug') { - $this->addLogItem("-I- ".date("Ymd-H:i:s")." checkBackup - token:$token\n"); - } - $this->token = $token; - $this->setEnviroment(); - $this->rRmDir($this->tmpDir); - unlink($this->statusFile); - return array("status" => !is_file($this->statusFile)); - } - - - /** - * Do restore in background - * - * this function is called from the asyncron commandline script - * ../bin/restore.php - * - * @param string $backupfile - * path of backupfile - * @param string $token - * restore token - * @param string $sessid - * session id - */ - function startRestore($backupfile, $token, $sessid) - { - if ($this->loglevel=='debug') { - $this->addLogItem("-I- ".date("Ymd-H:i:s")." startRestore - bufile:$backupfile | token:$token\n"); - } - $this->token = $token; - $this->sessid = $sessid; - $this->setEnviroment(); - - // extract tarball - $command = 'tar -xf '.$backupfile .' --directory '.$this->tmpDir; - $res = system($command); - //$this->addLogItem('command: '.$command."\n"); - //$this->addLogItem('res: '.$res."\n"); - - //simple check of archive format - if (is_dir($this->tmpDir.'audioClip/') && - is_dir($this->tmpDir.'meta-inf/') && - is_dir($this->tmpDir.'playlist/')) { - //search metafiles - $this->metafiles = $this->getMetaFiles(); - #$this->addLogItem('metafiles:'.print_r($this->metafiles,true)); - //add to storage server - foreach ($this->metafiles as $info) { - $r = $this->addFileToStorage($info['file'],$info['type'],$info['id']); - if (PEAR::isError($r)) { - $this->addLogItem("-E- ".date("Ymd-H:i:s"). - " startRestore - addFileToStorage \n". - "(".$put->getMessage()."/".$put->getUserInfo().")\n" - ); - file_put_contents($this->statusFile, 'fault|'.$put->getMessage()."/".$put->getUserInfo()); - return; - } - } - } else { - $this->addLogItem("-E- ".date("Ymd-H:i:s")." startRestore - invalid archive format\n"); - file_put_contents($this->statusFile, 'fault|invalid archive format'); - return; - } - file_put_contents($this->statusFile, 'success'); - // unlink($backupfile); - } - - - /** - * Get the metafiles. - * - * @return array - * array of hasharray with field: - * file : string - metafile path - * type : stirng - audioClip | playlist - * id : string - the backuped gunid - */ - function getMetaFiles() - { - if ($this->loglevel=='debug') { - $this->addLogItem("-I- ".date("Ymd-H:i:s")." getMetaFiles - tmpDir:{$this->tmpDir}\n"); - } - $audioclips = scandir($this->tmpDir.'audioClip/'); - $playlists = scandir($this->tmpDir.'playlist/'); - for ($i = 0; $i < count($audioclips); $i++) { - if (strpos($audioclips[$i],'xml')!==false) - $r[] = array('file' => $this->tmpDir.'audioClip/'.$audioclips[$i], - 'type' => 'audioClip', - 'id' => str_replace('.xml','',$audioclips[$i])); - } - for ($i = 0; $i < count($playlists); $i++) { - if (strpos($playlists[$i],'xml') !== false) - $r[] = array('file' => $this->tmpDir.'playlist/'.$playlists[$i], - 'type' => 'playlist', - 'id' => str_replace('.xml','',$playlists[$i])); - } - return $r; - } - - - /** - * Add the file to the storage server. - * - * @param string $file - * path of metafile - * @param string $type - * restore token - * @param string $sessid - * session id - * - * @return mixed - * true if success or PEAR_error - */ - // function addFileToStorage($file,$type,$gunid) - // { - // if ($this->loglevel=='debug') { - // $this->addLogItem("-I- ".date("Ymd-H:i:s")." addFileToStorage - file:$file | type:$type | id:$gunid\n"); - // } - // require_once("XmlParser.php"); - // $tree = XmlParser::parse($file); - // $mediaFileLP = str_replace('.xml','',$file); - // $mediaFileLP = ($type=='audioClip' && is_file($mediaFileLP))?$mediaFileLP:''; - // $ex = $this->gb->existsFile($this->sessid,$gunid); - // if (PEAR::isError($ex)) { - // $this->addLogItem("-E- ".date("Ymd-H:i:s"). - // " addFileToStorage - existsFile($gunid) ". - // "(".$ex->getMessage()."/".$ex->getUserInfo().")\n" - // ); - // } - // if (!PEAR::isError($ex) && $ex) { // file is exists in storage server - // //replace it - // $id = BasicStor::IdFromGunid($gunid); - // $replace = $this->gb->replaceFile( - // $id, # id int, virt.file's local id - // $mediaFileLP, # mediaFileLP string, local path of media file - // $file, # mdataFileLP string, local path of metadata file - // $this->sessid); # sessid string, session id - // if (PEAR::isError($replace)) { - // $this->addLogItem("-E- ".date("Ymd-H:i:s"). - // " addFileToStorage - replaceFile Error ". - // "(".$replace->getMessage()."/".$replace->getUserInfo().")\n" - // ); - // file_put_contents($this->statusFile, 'fault|'.$replace->getMessage()."/".$replace->getUserInfo()); - // return $replace; - // } - // #$this->addLogItem("replace it \n"); - // } else { - // // add as new - // $name = $tree->children[0]->children[0]->content; - // if (empty($name)) { - // $name = $tree->attrs['title']->val; - // } - // if (empty($name)) { - // $name = '???'; - // } - // if ($this->loglevel=='debug') { - // $this->addLogItem("-I- ".date("Ymd-H:i:s")." putFile\n". - // "$name, $mediaFileLP, $file, {$this->sessid}, $gunid, $type \n" - // ); - // } - // $values = array( - // "filename" => $name, - // "filepath" => $mediaFileLP, - // "metadata" => $file, - // "gunid" => $gunid, - // "filetype" => $type - // ); - // $put = $this->gb->putFile($values, $this->sessid); - // //$this->addLogItem("add as new \n"); - // if (PEAR::isError($put)) { - // $this->addLogItem("-E- ".date("Ymd-H:i:s"). - // " addFileToStorage - putFile Error ". - // "(".$put->getMessage()."/".$put->getUserInfo().")\n" - // ."\n---\n".file_get_contents($file)."\n---\n" - // ); - // file_put_contents($this->statusFile, 'fault|'.$put->getMessage()."/".$put->getUserInfo()); - // //$this->addLogItem("Error Object: ".print_r($put,true)."\n"); - // return $put; - // } - // } - // $ac = StoredFile::RecallByGunid($gunid); - // if (is_null($ac) || PEAR::isError($ac)) { - // return $ac; - // } - // $res = $ac->setState('ready'); - // if (PEAR::isError($res)) { - // return $res; - // } - // return true; - // } - - - /** - * Figure out the environment to the backup. - * - */ - function setEnviroment() - { - global $CC_CONFIG; - if ($this->loglevel=='debug') { - $this->addLogItem("-I- ".date("Ymd-H:i:s")." setEnviroment\n"); - } - $this->statusFile = $CC_CONFIG['accessDir'].'/'.$this->token.'.status'; - $this->tmpDir = '/tmp/ls_restore/'.$this->token.'/'; - $this->rMkDir($this->tmpDir); - } - - - /** - * Add a line to the logfile. - * - * @param string $item - * the new row of log file - */ - function addLogItem($item) - { - $f = fopen ($this->logFile,'a'); - flock($f,LOCK_SH); - fwrite($f,$item); - flock($f,LOCK_UN); - fclose($f); - //echo file_get_contents($this->logFile)."

\n\n"; - } - - - /** - * Delete a directory recursive - * - * @param string $dirname - * path of dir - * - * @return boolean - * is success - */ - function rRmDir($dirname) - { - if (is_dir($dirname)) { - $dir_handle = opendir($dirname); - } - while ($file = readdir($dir_handle)) { - if ($file!="." && $file!="..") { - if (!is_dir($dirname."/".$file)) { - unlink ($dirname."/".$file); - } else { - Restore::rRmDir($dirname."/".$file); - } - } - } - closedir($dir_handle); - rmdir($dirname); - return true; - } - - - /** - * Create a directory recursive - * - * @param string $dirname - * path of dir. - * @param int $mode - * octal - rights of dir. - * @param boolean $recursive - * do it recursive. - * - * @return boolean - */ - function rMkDir($dirname, $mode=0777, $recursive=true) - { - if (is_null($dirname) || $dirname === "" ) { - return false; - } - if (is_dir($dirname) || $dirname === "/" ) { - return true; - } - if ($this->rMkDir(dirname($dirname), $mode, $recursive)) { - return mkdir($dirname, $mode); - } - return false; - } - -} // class Restore - diff --git a/application/models/Shows.php b/application/models/Shows.php index 1465203b6..12fa1411b 100644 --- a/application/models/Shows.php +++ b/application/models/Shows.php @@ -175,15 +175,26 @@ class Show { Show::populateShowUntilLastGeneratedDate($showId); } - public static function getShows($start_timestamp, $end_timestamp, $excludeInstance=NULL) { + public static function getShows($start_timestamp, $end_timestamp, $excludeInstance=NULL, $onlyRecord=FALSE) { global $CC_DBC; $sql = "SELECT starts, ends, show_id, name, description, color, background_color, cc_show_instances.id AS instance_id FROM cc_show_instances - LEFT JOIN cc_show ON cc_show.id = cc_show_instances.show_id - WHERE ((starts >= '{$start_timestamp}' AND starts < '{$end_timestamp}') + LEFT JOIN cc_show ON cc_show.id = cc_show_instances.show_id"; + + //only want shows that are starting at the time or later. + if($onlyRecord) { + + $sql = $sql." WHERE (starts >= '{$start_timestamp}' AND starts < timestamp '{$start_timestamp}' + interval '2 hours')"; + $sql = $sql." AND (record = TRUE)"; + } + else { + + $sql = $sql." WHERE ((starts >= '{$start_timestamp}' AND starts < '{$end_timestamp}') OR (ends > '{$start_timestamp}' AND ends <= '{$end_timestamp}') OR (starts <= '{$start_timestamp}' AND ends >= '{$end_timestamp}'))"; + } + if(isset($excludeInstance)) { foreach($excludeInstance as $instance) { @@ -196,7 +207,6 @@ class Show { } //echo $sql; - return $CC_DBC->GetAll($sql); } diff --git a/application/models/StoredFile.php b/application/models/StoredFile.php index 8b8407f79..72e96dbea 100644 --- a/application/models/StoredFile.php +++ b/application/models/StoredFile.php @@ -1271,14 +1271,14 @@ class StoredFile { if (!file_exists($this->filepath) || @unlink($this->filepath)) { $this->exists = FALSE; return TRUE; - } + } else { return PEAR::raiseError( "StoredFile::deleteFile: unlink failed ({$this->filepath})", GBERR_FILEIO ); } - } + } else { $this->exists = FALSE; return TRUE; @@ -1304,7 +1304,7 @@ class StoredFile { Playlist::DeleteFileFromAllPlaylists($this->id); } - + $sql = "DELETE FROM ".$CC_CONFIG['filesTable'] ." WHERE gunid='{$this->gunid}'"; $res = $CC_DBC->query($sql); @@ -1717,7 +1717,7 @@ class StoredFile { foreach($searchCols as $col) { $escapedTerm = pg_escape_string($term); - $innerCond[] = "{$col}::text ILIKE '%{$escapedTerm}%'"; + $innerCond[] = "{$col}::text ILIKE '%{$escapedTerm}%'"; } $outerCond[] = "(".join(" OR ", $innerCond).")"; } diff --git a/application/models/Subjects.php b/application/models/Subjects.php index 8f29c6706..3484484f9 100644 --- a/application/models/Subjects.php +++ b/application/models/Subjects.php @@ -13,48 +13,11 @@ define('ALIBERR_BADSMEMB', 21); * @subpackage Alib * @copyright 2010 Sourcefabric O.P.S. * @license http://www.gnu.org/licenses/gpl.txt - * @see ObjClasses - * @see Alib */ class Subjects { /* ======================================================= public methods */ - /** - * Add new subject (a.k.a. "user") - * - * @param string $p_login - * @param string $p_pass - * @param string $p_realname - * @param boolean $p_passenc - * password already encrypted if true - * @return int|PEAR_Error - */ - public static function AddSubj($p_login, $p_pass=NULL, $p_realname='', $p_passenc=FALSE) - { - global $CC_CONFIG, $CC_DBC; - if (!$p_login) { - return $CC_DBC->raiseError("Subjects::AddSubj: empty login"); - } - $id = $CC_DBC->nextId($CC_CONFIG['subjSequence']); - if (PEAR::isError($id)) { - return $id; - } - if (!is_null($p_pass) && !$p_passenc) { - $p_pass = md5($p_pass); - } - $sql = "INSERT INTO ".$CC_CONFIG['subjTable']." (id, login, pass, type, realname)" - ." VALUES ($id, '$p_login', ". - (is_null($p_pass) ? "'!', 'G'" : "'$p_pass', 'U'").", - '$p_realname')"; - $r = $CC_DBC->query($sql); - if (PEAR::isError($r)) { - return $r; - } - return $id; - } - - /** * Check login and password * @@ -77,28 +40,6 @@ class Subjects { } // fn authenticate - /** - * Set lastlogin or lastfail timestamp - * - * @param string $login - * @param boolean $failed - * true=> set lastfail, false=> set lastlogin - * @return boolean|int|PEAR_Error - */ - public static function SetTimeStamp($login, $failed=FALSE) - { - global $CC_CONFIG, $CC_DBC; - $fld = ($failed ? 'lastfail' : 'lastlogin'); - $sql = "UPDATE ".$CC_CONFIG['subjTable']." SET $fld=now()" - ." WHERE login='$login'"; - $r = $CC_DBC->query($sql); - if (PEAR::isError($r)) { - return $r; - } - return TRUE; - } // fn setTimeStamp - - /** * Change user password * @@ -137,52 +78,6 @@ class Subjects { /* --------------------------------------------------------------- groups */ - /** - * Add {login} and direct/indirect members to {gname} and to groups, - * where {gname} is [in]direct member - * - * @param string $login - * @param string $gname - * @return int|PEAR_Error - */ - public static function AddSubjectToGroup($login, $gname) - { - $uid = Subjects::GetSubjId($login); - if (PEAR::isError($uid)) { - return $uid; - } - $gid = Subjects::GetSubjId($gname); - if (PEAR::isError($gid)) { - return $gid; - } - $isgr = Subjects::IsGroup($gid); - if (PEAR::isError($isgr)) { - return $isgr; - } - if (!$isgr) { - return PEAR::raiseError("Subjects::addSubj2Gr: Not a group ($gname)", ALIBERR_NOTGR); - } - // add subject and all [in]direct members to group $gname: - $mid = Subjects::_plainAddSubjectToGroup($uid, $gid); - if (PEAR::isError($mid)) { - return $mid; - } - // add it to all groups where $gname is [in]direct member: - $marr = Subjects::_listRMemb($gid); - if (PEAR::isError($marr)) { - return $marr; - } - foreach ($marr as $k => $v) { - $r = Subjects::_plainAddSubjectToGroup( - $uid, $v['gid'], intval($v['level'])+1, $v['id']); - if (PEAR::isError($r)) { - return $r; - } - } - return $mid; - } // fn addSubj2Gr - - /* --------------------------------------------------------- info methods */ /** @@ -201,114 +96,6 @@ class Subjects { } // fn getSubjId - /** - * Get subject name (login) from id - * - * @param int $id - * @param string $fld - * @return string|PEAR_Error - */ - public static function GetSubjName($id, $fld='login') - { - global $CC_CONFIG; - global $CC_DBC; - $sql = "SELECT $fld FROM ".$CC_CONFIG['subjTable'] - ." WHERE id='$id'"; - return $CC_DBC->getOne($sql); - } // fn getSubjName - - - /** - * Get one subject from the table. - * - * @param string $p_fieldValue - * @param string $p_fieldName - * @return array - */ - public static function GetSubject($p_fieldValue, $p_fieldName='login') - { - global $CC_CONFIG, $CC_DBC; - if (!in_array($p_fieldName, array("login", "id"))) { - return null; - } - $escapedValue = pg_escape_string($p_fieldValue); - $sql = "SELECT * FROM ".$CC_CONFIG['subjTable'] - ." WHERE $p_fieldName='$escapedValue'"; - $row = $CC_DBC->GetRow($sql); - return $row; - } - - - /** - * Get all subjects - * - * @param string $flds - * @return array|PEAR_Error - */ - public static function GetSubjects($flds='id, login') - { - global $CC_CONFIG, $CC_DBC; - $sql = "SELECT $flds FROM ".$CC_CONFIG['subjTable']; - return $CC_DBC->getAll($sql); - } // fn getSubjects - - - /** - * Get subjects with count of direct members - * - * @return array|PEAR_Error - */ - public static function GetSubjectsWCnt() - { - global $CC_CONFIG, $CC_DBC; - $sql = "SELECT count(m.uid)as cnt, s.id, s.login, s.type" - ." FROM ".$CC_CONFIG['subjTable']." s" - ." LEFT JOIN ".$CC_CONFIG['smembTable']." m ON m.gid=s.id" - ." WHERE m.mid is null" - ." GROUP BY s.id, s.login, s.type" - ." ORDER BY s.id"; - return $CC_DBC->getAll($sql); - } // fn getSubjectsWCnt - - - /** - * Return true if subject is a group - * - * @param int $gid - * @return boolean|PEAR_Error - */ - public static function IsGroup($gid) - { - global $CC_CONFIG, $CC_DBC; - if (empty($gid)) { - return FALSE; - } - $sql = "SELECT type FROM ".$CC_CONFIG['subjTable'] - ." WHERE id='$gid'"; - $r = $CC_DBC->getOne($sql); - if (PEAR::isError($r)) { - return $r; - } - return ($r === 'G'); - } // fn isGroup - - - /** - * List direct members of group - * - * @param int $gid - * @return array|PEAR_Error - */ - public static function ListGroup($gid) - { - global $CC_CONFIG, $CC_DBC; - $sql = "SELECT s.id, s.login, s.type" - ." FROM ".$CC_CONFIG['smembTable']." m, ".$CC_CONFIG['subjTable']." s" - ." WHERE m.uid=s.id AND m.mid is null AND m.gid='$gid'"; - return $CC_DBC->getAll($sql); - } // fn listGroup - - /** * Return true if uid is [id]direct member of gid * @@ -332,180 +119,5 @@ class Subjects { } // fn isMemberOf - /* ==================================================== "private" methods */ - - /** - * Create membership record - * - * @param int $uid - * @param int $gid - * @param int $level - * @param int $mid - * @return int|PEAR_Error - */ - private static function _addMemb($uid, $gid, $level=0, $mid='null') - { - global $CC_CONFIG, $CC_DBC; - if ($uid == $gid) { - return PEAR::raiseError("Subjects::_addMemb: uid==gid ($uid)", ALIBERR_BADSMEMB); - } - $sql = "SELECT id, level, mid FROM ".$CC_CONFIG['smembTable'] - ." WHERE uid='$uid' AND gid='$gid' ORDER BY level ASC"; - $a = $CC_DBC->getAll($sql); - if (PEAR::isError($a)) { - return $a; - } - if (count($a) > 0) { - $a0 = $a[0]; - $id = $a0['id']; - if ($level < intval($a0['level'])){ - $sql2 = "UPDATE ".$CC_CONFIG['smembTable'] - ." SET level='$level', mid=$mid WHERE id='{$a0['id']}'"; - $r = $CC_DBC->query($sql2); - if (PEAR::isError($r)) { - return $r; - } - } - } else { - $id = $CC_DBC->nextId($CC_CONFIG['smembSequence']); - if (PEAR::isError($id)) { - return $id; - } - $sql3 = "INSERT INTO ".$CC_CONFIG['smembTable']." (id, uid, gid, level, mid)" - ." VALUES ($id, $uid, $gid, $level, $mid)"; - $r = $CC_DBC->query($sql3); - if (PEAR::isError($r)) { - return $r; - } - } - return $id; - } // fn _addMemb - - - /** - * Remove membership record - * - * @param int $mid - * @return null|PEAR_Error - */ - private static function _removeMemb($mid) - { - global $CC_CONFIG, $CC_DBC; - $sql = "DELETE FROM ".$CC_CONFIG['smembTable'] - ." WHERE id='$mid'"; - return $CC_DBC->query($sql); - } // fn _removeMemb - - - /** - * List [in]direct members of group - * - * @param int $gid - * @param int $uid - * @return array|PEAR_Error - */ - private static function _listMemb($gid, $uid=NULL) - { - global $CC_CONFIG, $CC_DBC; - $sql = "SELECT id, uid, level FROM ".$CC_CONFIG['smembTable'] - ." WHERE gid='$gid'".(is_null($uid) ? '' : " AND uid='$uid'"); - return $CC_DBC->getAll($sql); - } // fn _listMemb - - - /** - * List groups where uid is [in]direct member - * - * @param int $gid - * @param int $uid - * @return array|PEAR_Error - */ - private static function _listRMemb($uid, $gid=NULL) - { - global $CC_CONFIG, $CC_DBC; - $sql = "SELECT id, gid, level FROM ".$CC_CONFIG['smembTable'] - ." WHERE uid='$uid'".(is_null($gid) ? '' : " AND gid='$gid'"); - return $CC_DBC->getAll($sql); - } // fn listRMemb - - - /** - * Add uid and its [in]direct members to gid - * - * @param int $uid - * @param int $gid - * @param int $level - * @param int $rmid - * @return int|PEAR_Error - */ - private static function _plainAddSubjectToGroup($uid, $gid, $level=0, $rmid='null') - { - $mid = Subjects::_addMemb($uid, $gid, $level, $rmid); - if (PEAR::isError($mid)) { - return $mid; - } - $marr = Subjects::_listMemb($uid); - if (PEAR::isError($marr)) { - return $marr; - } - foreach ($marr as $k => $v) { - $r = Subjects::_addMemb( - $v['uid'], $gid, intval($v['level'])+$level+1, $mid - ); - if (PEAR::isError($r)) { - return $r; - } - } - return $mid; - } - - - /** - * Rebuild indirect membership records
- * it's probably more complicated to do removing without rebuild ... - * - * @return true|PEAR_Error - */ - private static function _rebuildRels() - { - global $CC_CONFIG, $CC_DBC; - $CC_DBC->query("BEGIN"); - $r = $CC_DBC->query("LOCK TABLE ".$CC_CONFIG['smembTable']); - if (PEAR::isError($r)) { - return $r; - } - $sql = "DELETE FROM ".$CC_CONFIG['smembTable'] - ." WHERE mid is not null"; - $r = $CC_DBC->query($sql); - if (PEAR::isError($r)) { - return $r; - } - $arr = $CC_DBC->getAll("SELECT uid, gid FROM ".$CC_CONFIG['smembTable']); - // WHERE mid is null - if (PEAR::isError($arr)) { - return $arr; - } - foreach ($arr as $it) { - $marr = Subjects::_listRMemb($it['gid']); - if (PEAR::isError($marr)) { - return $marr; - } - foreach ($marr as $k => $v) { - $r = Subjects::_plainAddSubjectToGroup( - $it['uid'], $v['gid'], intval($v['level'])+1, $v['id'] - ); - if (PEAR::isError($r)) { - return $r; - } - } - } - $r = $CC_DBC->query("COMMIT"); - if (PEAR::isError($r)) { - return $r; - } - return TRUE; - } // fn _rebuildRels - - } // class Subjects diff --git a/application/models/Transport.php b/application/models/Transport.php deleted file mode 100644 index 5478618ce..000000000 --- a/application/models/Transport.php +++ /dev/null @@ -1,1833 +0,0 @@ - - * over unreliable network and from behind firewall

- * - * Transport states: - *
    - *
  • init: transport is prepared, but not started - * (e.g. no network connection is present)
  • - *
  • pending: transport is in progress, file is not fully transported to - * target system
  • - *
  • waiting: transport is in progress, but not running now
  • - *
  • finished: transport is finished, but file processing on target side - * is not completed
  • - *
  • closed: processing on target side is completed without errors
  • - *
  • failed: error - error message stored in errmsg field
  • - *
  • paused: transport have been paused
  • - *
- * - * Transport types: - *
    - *
  • audioclip
  • - *
  • playlist
  • - *
  • metadata
  • - *
  • file
  • - *
- * - * @package Airtime - * @subpackage StorageServer - * @copyright 2010 Sourcefabric O.P.S. - * @license http://www.gnu.org/licenses/gpl.txt - */ -class Transport -{ - /** - * @var GreenBox - */ - public $gb; - - /** - * File name - * @var string - */ - private $cronJobScript; - - /** - * wget --read-timeout parameter [s] - * @var int - */ - private $downTimeout = 900; - - /** - * wget --waitretry parameter [s] - * @var int - */ - private $downWaitretry = 10; - - /** - * wget --limit-rate parameter - */ - private $downLimitRate = NULL; -# private $downLimitRate = 500; - - /** - * wget -t parameter - * @var int - */ - private $downRetries = 6; - - /** - * curl --max-time parameter - * @var int - */ - private $upTrMaxTime = 1800; - - /** - * curl --speed-time parameter - * @var int - */ - private $upTrSpeedTime = 30; - - /** - * curl --speed-limit parameter - * @var int - */ - private $upTrSpeedLimit = 30; - - /** - * curl --connect-timeout parameter - * @var int - */ - private $upTrConnectTimeout = 20; - - /** - * curl --limit-rate parameter - * @var int - */ - private $upLimitRate = NULL; -# private $upLimitRate = 500; - - - /** - * Constructor - * - * @param LocStor $gb - * @return Transport - */ - public function __construct(&$gb) - { - $this->gb =& $gb; - $this->cronJobScript = realpath( - dirname(__FILE__). - '/../../storageServer/var/cron/transportCronJob.php' - ); - } - - - /* ==================================================== transport methods */ - /* ------------------------------------------------------- common methods */ - /** - * Common "check" method for transports - * - * @param string $trtok - * transport token - * @return array - * struct/hasharray with fields: - * trtype: string - - * audioclip | playlist | playlistPkg | metadata | file - * state: string - transport state - * init | pending | waiting | finished | closed | failed - * direction: string - up | down - * expectedsize: int - file size in bytes - * realsize: int - currently transported bytes - * expectedsum: string - orginal file checksum - * realsum: string - transported file checksum - * title: string - dc:title or filename etc. - * errmsg: string - error message for failed transports - * ... ? - */ - function getTransportInfo($trtok) - { - $trec = TransportRecord::recall($this, $trtok); - if (PEAR::isError($trec)) { - return $trec; - } - $res = array(); - foreach (array( - 'trtype', 'state', 'direction', 'expectedsize', 'realsize', - 'expectedsum', 'realsum', 'title', 'errmsg' - ) as $k) { - $res[$k] = ( isset($trec->row[$k]) ? $trec->row[$k] : NULL ); - } - if ( ($trec->row['direction'] == 'down') && file_exists($trec->row['localfile']) ){ - $res['realsize'] = filesize($trec->row['localfile']); - $res['realsum'] = $this->_chsum($trec->row['localfile']); - } - if ( ($trec->row['direction'] == 'up') ){ - $check = $this->uploadCheck($trec->row['pdtoken']); - if (!PEAR::isError($check)) { - $res['realsize'] = $check['size']; - $res['realsum'] = $check['realsum']; - } - } - return $res; - } - - - /** - * Turn transports on/off, optionaly return current state. - * (true=On / false=off) - * - * @param string $sessid - * session id - * @param boolean $onOff - * optional (if not used, current state is returned) - * @return boolea - * previous state - */ - function turnOnOffTransports($sessid, $onOff=NULL) - { - require_once('Prefs.php'); - $pr = new Prefs($this->gb); - $group = $CC_CONFIG['StationPrefsGr']; - $key = 'TransportsDenied'; - $res = $pr->loadGroupPref($group, $key); - if (PEAR::isError($res)) { - if ($res->getCode() !== GBERR_PREF) { - return $res; - } else { - $res = FALSE; // default - } - } - $state = !$res; - if (is_null($onOff)) { - return $state; - } - $res = $pr->saveGroupPref($sessid, $group, $key, !$onOff); - if (PEAR::isError($res)) { - return $res; - } - return $state; - } - - - /** - * Pause, resume or cancel transport - * - * @param string $trtok - * transport token - * @param string $action - * pause | resume | cancel - * @return string - * resulting transport state - */ - function doTransportAction($trtok, $action) - { - $trec = TransportRecord::recall($this, $trtok); - if (PEAR::isError($trec)) { - return $trec; - } - if ($trec->getState() == 'closed') { - return PEAR::raiseError( - "Transport::doTransportAction:". - " closed transport token ($trtok)", TRERR_TOK - ); - } - switch ($action) { - case 'pause'; - $newState = 'paused'; - break; - case 'resume'; - $newState = 'waiting'; - break; - case 'cancel'; - $newState = 'closed'; - break; - default: - return PEAR::raiseError( - "Transport::doTransportAction:". - " unknown action ($action)" - ); - } - $res = $trec->setState($newState); - switch ($action) { - case 'pause'; - case 'cancel'; - $trec->killJob(); - } - return $res; - } - - /* ------------- special methods for audioClip/webstream object transport */ - - /** - * Start upload of audioClip/webstream/playlist from local storageServer - * to hub. - * - * @param string $gunid - * global unique id of object being transported - * @param boolean $withContent - * if true, transport playlist content too (optional) - * @param array $pars - * default parameters (optional, internal use) - * @return string - * transport token - */ - function upload2Hub($gunid, $withContent=TRUE, $pars=array()) - { - global $CC_CONFIG, $CC_DBC; - $this->trLog("upload2Hub start: ".strftime("%H:%M:%S")); - switch ($ftype = BasicStor::GetType($gunid)) { - case "audioclip": - case "webstream": - $storedFile = StoredFile::RecallByGunid($gunid); - if (is_null($storedFile) || PEAR::isError($storedFile)) { - return $storedFile; - } - // handle metadata: - $mdfpath = $storedFile->getRealMetadataFileName(); - if (PEAR::isError($mdfpath)) { - return $mdfpath; - } - $mdtrec = $this->_uploadGeneralFileToHub($mdfpath, 'metadata', - array_merge(array('gunid'=>$gunid, 'fname'=>'metadata',), $pars) - ); - if (PEAR::isError($mdtrec)) { - return $mdtrec; - } - // handle raw media file: - $fpath = $storedFile->getRealFileName(); - if (PEAR::isError($fpath)) { - return $fpath; - } - $fname = $storedFile->getName(); - if (PEAR::isError($fname)) { - return $fname; - } - $trec = $this->_uploadGeneralFileToHub($fpath, 'audioclip', - array_merge(array( - 'gunid'=>$gunid, 'fname'=>$fname, 'mdtrtok'=>$mdtrec->trtok, - ), $pars) - ); - if (PEAR::isError($trec)) { - return $trec; - } - $this->startCronJobProcess($mdtrec->trtok); - break; - - case "playlist": - $plid = $gunid; - require_once("Playlist.php"); - $pl = StoredFile::RecallByGunid($plid); - if (is_null($pl) || PEAR::isError($pl)) { - return $pl; - } - $fname = $pl->getName(); - if (PEAR::isError($fname)) { - return $fname; - } - if ($withContent) { - $this->trLog("upload2Hub exportPlaylistOpen BEGIN: ".strftime("%H:%M:%S")); - $res = $this->gb->bsExportPlaylistOpen($plid); - $this->trLog("upload2Hub exportPlaylistOpen END: ".strftime("%H:%M:%S")); - if (PEAR::isError($res)) { - return $res; - } - $tmpn = tempnam($CC_CONFIG['transDir'], 'plExport_'); - $plfpath = "$tmpn.lspl"; - $this->trLog("upload2Hub begin copy: ".strftime("%H:%M:%S")); - copy($res['fname'], $plfpath); - $this->trLog("upload2Hub end copy: ".strftime("%H:%M:%S")); - $res = $this->gb->bsExportPlaylistClose($res['token']); - if (PEAR::isError($res)) { - return $res; - } - $fname = $fname.".lspl"; - $trtype = 'playlistPkg'; - } else { - $plfpath = $pl->getRealMetadataFileName(); - if (PEAR::isError($plfpath)) { - return $plfpath; - } - $trtype = 'playlist'; - } - $trec = $this->_uploadGeneralFileToHub($plfpath, $trtype, - array_merge(array('gunid'=>$plid,'fname'=>$fname,), $pars)); - if (PEAR::isError($trec)) { - return $trec; - } - break; - default: - return PEAR::raiseError("Transport::upload2Hub: ftype not supported ($ftype)"); - } - $this->startCronJobProcess($trec->trtok); - $this->trLog("upload2Hub end: ".strftime("%H:%M:%S")); - return $trec->trtok; - } - - - /** - * Start download of audioClip/webstream/playlist from hub to local - * storageServer - * - * @param int $uid - * local user id of transport owner - * (for downloading file to homedir in storage) - * @param string $gunid - * global unique id of object being transported - * @param boolean $withContent - * if true, transport playlist content too (optional) - * @param array $pars - * default parameters (optional, internal use) - * @return string - * transport token - */ - function downloadFromHub($uid, $gunid, $withContent=TRUE, $pars=array()) - { - $trtype = ($withContent ? 'playlistPkg' : 'unknown' ); - $trec = TransportRecord::create($this, $trtype, 'down', - array_merge(array('gunid'=>$gunid, 'uid'=>$uid), $pars)); - if (PEAR::isError($trec)) { - return $trec; - } - $this->startCronJobProcess($trec->trtok); - return $trec->trtok; - } - - - /* ------------------------------------------------ remote-search methods */ - /** - * Start search job on remote Airtime instance. - * - * @param array $criteria - * LS criteria format (see localSearch) - * @param string $resultMode - * 'php' | 'xmlrpc' - * @param array $pars - * default parameters (optional, internal use) - * @return string - * transport token - */ - function remoteSearch($criteria, $resultMode='php') - { - global $CC_CONFIG, $CC_DBC; - $criteria['resultMode'] = $resultMode; - - // testing of hub availability and hub account configuration. - $sessid = $this->loginToArchive(); - if (PEAR::isError($sessid)) { - switch(intval($sessid->getCode())) { - case 802: - return PEAR::raiseError("Can't login to Hub ({$sessid->getMessage()})", TRERR_XR_FAIL); - case TRERR_XR_FAIL: - return PEAR::raiseError("Can't connect to Hub ({$sessid->getMessage()})", TRERR_XR_FAIL); - } - return $sessid; - } - $params = array("sessid" => $sessid, "criteria" => $criteria); - $result = $this->xmlrpcCall("locstor.searchMetadata", $params); - //$result = $this->xmlrpcCall("locstor.ping", array("par" => "foo")); - $this->logoutFromArchive($sessid); - return $result; - } - - /** - * Start search job on network hub - * - * @param array $criteria - * LS criteria format (see localSearch) - * @param string $resultMode - * 'php' | 'xmlrpc' - * @param array $pars - * default parameters (optional, internal use) - * @return string - * transport token - */ -// function globalSearch($criteria, $resultMode='php', $pars=array()) -// { -// global $CC_CONFIG, $CC_DBC; -// // testing of hub availability and hub account configuration. -// // it makes searchjob not async - should be removed for real async -// $r = $this->loginToArchive(); -// if (PEAR::isError($r)) { -// switch(intval($r->getCode())) { -// case 802: -// return PEAR::raiseError("Can't login to Hub ({$r->getMessage()})", TRERR_XR_FAIL); -// case TRERR_XR_FAIL: -// return PEAR::raiseError("Can't connect to Hub ({$r->getMessage()})", TRERR_XR_FAIL); -// } -// return $r; -// } -// $this->logoutFromArchive($r); -// $criteria['resultMode'] = $resultMode; -// $localfile = tempnam($CC_CONFIG['transDir'], 'searchjob_'); -// @chmod($localfile, 0660); -// $len = file_put_contents($localfile, serialize($criteria)); -// $trec = $this->_uploadGeneralFileToHub($localfile, 'searchjob', $pars); -// if (PEAR::isError($trec)) { -// return $trec; -// } -// $this->startCronJobProcess($trec->trtok); -// return $trec->trtok; -// } - - - /** - * Get results from search job on network hub - * - * @param string $trtok - * transport token - * @param boolean $andClose - * if TRUE, close transport token - * @return array - * LS search result format (see localSearch) - */ -// function getSearchResults($trtok, $andClose=TRUE) -// { -// $trec = TransportRecord::recall($this, $trtok); -// if (PEAR::isError($trec)) { -// return $trec; -// } -// $row = $trec->row; -// switch ($st = $trec->getState()) { -// case "failed": -// return PEAR::raiseError( -// "Transport::getSearchResults:". -// " global search or results transport failed". -// " ({$trec->row['errmsg']})" -// ); -// case "closed": -///* -// $res = file_get_contents($row['localfile']); -// $results = unserialize($res); -// return $results; -//*/ -// return PEAR::raiseError( -// "Transport::getSearchResults:". -// " closed transport token ($trtok)", TRERR_TOK -// ); -// case "finished": -// if ($row['direction'] == 'down') { -// // really finished -// $res = file_get_contents($row['localfile']); -// $results = unserialize($res); -// if ($andClose) { -// $ret = $this->xmlrpcCall('archive.downloadClose', -// array( -// 'token' => $row['pdtoken'] , -// 'trtype' => $row['trtype'] , -// )); -// if (PEAR::isError($ret)) { -// return $ret; -// } -// @unlink($row['localfile']); -// $r = $trec->close(); -// if (PEAR::isError($r)) { -// return $r; -// } -// } -// return $results; -// } -// // otherwise not really finished - only request upload finished -// default: -// return PEAR::raiseError( -// "Transport::getSearchResults: not finished ($st)", -// TRERR_NOTFIN -// ); -// } -// } - - - /* ------------------------ methods for ls-archive-format file transports */ - /** - * Open async file transfer from local storageServer to network hub, - * file should be ls-archive-format file. - * - * @param string $filePath - * local path to uploaded file - * @param array $pars - * default parameters (optional, internal use) - * @return string - * transport token - */ - function uploadFile2Hub($filePath, $pars=array()) - { - if (!file_exists($filePath)) { - return PEAR::raiseError( - "Transport::uploadFile2Hub: file not found ($filePath)" - ); - } - $trec = $this->_uploadGeneralFileToHub($filePath, 'file', $pars); - if (PEAR::isError($trec)) { - return $trec; - } - $this->startCronJobProcess($trec->trtok); - return $trec->trtok; - } - - - /** - * Open async file transfer from network hub to local storageServer, - * file should be ls-archive-format file. - * - * @param string $url - * readable url - * @param string $chsum - * checksum from remote side - * @param int $size - * filesize from remote side - * @param array $pars - * default parameters (internal use) - * @return array - * trtok: string - transport token - * localfile: string - filepath of downloaded file - */ - function downloadFileFromHub($url, $chsum=NULL, $size=NULL, $pars=array()) - { - global $CC_CONFIG, $CC_DBC; - $tmpn = tempnam($CC_CONFIG['transDir'], 'HITrans_'); - $trec = TransportRecord::create($this, 'file', 'down', - array_merge(array( - 'url' => $url, - 'localfile' => $tmpn, - 'expectedsum' => $chsum, - 'expectedsize' => $size, - ), $pars) - ); - if (PEAR::isError($trec)) { - return $trec; - } - $this->startCronJobProcess($trec->trtok); - return array('trtok'=>$trec->trtok, 'localfile'=>$tmpn); - } - - - /** - * Get list of prepared transfers initiated by hub - * - * @return array - * array of structs/hasharrays with fields: - * trtok: string transport token - */ - function getHubInitiatedTransfers() - { - $ret = $this->xmlrpcCall('archive.listHubInitiatedTransfers', - array('target' => HOSTNAME)); - if (PEAR::isError($ret)) { - return $ret; - } - $res = array(); - foreach ($ret as $it) { - $res[] = array('trtok'=>$it['trtok']); - } - return $res; - } - - - /** - * Start of download initiated by hub - * - * @param int $uid - * local user id of transport owner - * (for downloading file to homedir in storage) - * @param string $rtrtok - * transport token obtained from the getHubInitiatedTransfers method - * @return string - * transport token - */ - function startHubInitiatedTransfer($uid, $rtrtok) - { - $ret = $this->xmlrpcCall('archive.listHubInitiatedTransfers', - array( - 'target' => HOSTNAME, - 'trtok' => $rtrtok, - )); - if (PEAR::isError($ret)) { - return $ret; - } - if (count($ret) != 1) { - return PEAR::raiseError( - "Transport::startHubInitiatedTransfer:". - " wrong number of transports (".count($ret).")" - ); - } - $ta = $ret[0]; - // direction invertation to locstor point of view: - $direction = ( $ta['direction']=='up' ? 'down' : 'up' ); - $gunid = $ta['gunid']; - switch ($direction) { - case "up": - switch ($ta['trtype']) { - case "audioclip": - case "playlist": - case "playlistPkg": - $trtok = $this->upload2Hub($gunid, TRUE, - array('rtrtok'=>$rtrtok)); - if (PEAR::isError($trtok)) { - return $trtok; - } - break; - //case "searchjob": break; // not supported yet - //case "file": break; // probably unusable - default: - return PEAR::raiseError( - "Transport::startHubInitiatedTransfer:". - " wrong direction / transport type combination". - " ({$ta['direction']}/{$ta['trtype']})" - ); - } - break; - case "down": - switch ($ta['trtype']) { - case "audioclip": - case "playlist": - case "playlistPkg": - $trtok = $this->downloadFromHub($uid, $gunid, TRUE, - array('rtrtok'=>$rtrtok)); - if (PEAR::isError($trtok)) { - return $trtok; - } - break; - //case "searchjob": break; // probably unusable - case "file": - $r = $this->downloadFileFromHub( - $ta['url'], $ta['expectedsum'], $ta['expectedsize'], - array('rtrtok'=>$rtrtok)); - if (PEAR::isError($r)) { - return $r; - } - extract($r); // trtok, localfile - break; - default: - return PEAR::raiseError( - "Transport::startHubInitiatedTransfer:". - " wrong direction / transport type combination". - " ({$ta['direction']}/{$ta['trtype']})" - ); - } - break; - default: - return PEAR::raiseError( - "Transport::startHubInitiatedTransfer: ???" - ); - } - $ret = $this->xmlrpcCall('archive.setHubInitiatedTransfer', - array( - 'target' => HOSTNAME, - 'trtok' => $rtrtok, - 'state' => 'waiting', - )); - if (PEAR::isError($ret)) { - return $ret; - } - $this->startCronJobProcess($trtok); - return $trtok; - } - - - /* =============================================== authentication methods */ - - /** - * Login to archive server - * (account info is taken from storageServer's config) - * - * @return string - * sessid or error - */ - function loginToArchive() - { - global $CC_CONFIG; - $res = $this->xmlrpcCall('locstor.login', - array( - 'login' => $CC_CONFIG['archiveAccountLogin'], - 'pass' => $CC_CONFIG['archiveAccountPass'] - )); - if (PEAR::isError($res)) { - return $res; - } - return $res['sessid']; - } - - - /** - * Logout from archive server - * - * @param unknown $sessid - * session id - * @return string - * Bye or error - */ - function logoutFromArchive($sessid) - { - $res = $this->xmlrpcCall('locstor.logout', - array('sessid'=>$sessid)); - return $res; - } - - - /* ========================================================= cron methods */ - /* -------------------------------------------------- common cron methods */ - /** - * Main method for periodical transport tasks - called by cron - * - * @param string $direction - * optional - * @return boolean - * TRUE - */ - function cronMain($direction=NULL) - { - global $CC_CONFIG; - if (is_null($direction)) { - $r = $this->cronMain('up'); - if (PEAR::isError($r)) { - return $r; - } - $r = $this->cronMain('down'); - if (PEAR::isError($r)) { - return $r; - } - return TRUE; - } - // fetch all opened transports - $transports = $this->getTransports($direction); - if (PEAR::isError($transports)) { - $this->trLog("cronMain: DB error"); - return FALSE; - } - if (count($transports) == 0) { - if (TR_LOG_LEVEL > 1) { - $this->trLog("cronMain: $direction - nothing to do."); - } - return TRUE; - } - // ping to archive server: - $r = $this->ping(); - chdir($CC_CONFIG['transDir']); - // for all opened transports: - foreach ($transports as $i => $row) { - $r = $this->startCronJobProcess($row['trtok']); - } // foreach transports - return TRUE; - } - - - /** - * Cron job process starter - * - * @param string $trtok - * transport token - * @return boolean - * status - */ - function startCronJobProcess($trtok) - { - global $CC_CONFIG, $CC_DBC; - if (TR_LOG_LEVEL > 2) { - $redirect = $CC_CONFIG['transDir']."/debug.log"; - } else { - $redirect = "/dev/null"; - } - $redirect_escaped = escapeshellcmd($redirect); - $command = "{$this->cronJobScript} {$trtok}"; - $command_escaped = escapeshellcmd($command); - $command_final = "$command_escaped >> $redirect_escaped 2>&1 &"; - $res = system($command_final, $status); - if ($res === FALSE) { - $this->trLog( - "cronMain: Error on execute cronJobScript with trtok {$trtok}" - ); - return FALSE; - } - return TRUE; - } - - - /** - * Dynamic method caller - wrapper - * - * @param string $trtok - * transport token - * @return mixed - * inherited from called method - */ - function cronCallMethod($trtok) - { - global $CC_CONFIG; - $trec = TransportRecord::recall($this, $trtok); - if (PEAR::isError($trec)) { - return $trec; - } - $row = $trec->row; - $state = $row['state']; - - $states = array('init'=>'init', - 'pending'=>'pending', - 'waiting'=>'waiting', - 'finished'=>'finished', - 'failed'=>'failed', - 'closed'=>'closed'); - $directions = array('up'=>'upload', 'down'=>'download'); - // method name construction: - $mname = "cron"; - if (isset($directions[$row['direction']])) { - $mname .= ucfirst($directions[$row['direction']]); - } else { - return PEAR::raiseError( - "Transport::cronCallMethod: invalid direction ({$row['direction']})" - ); - } - if (isset($states[$state])) { - $mname .= ucfirst($states[$state]); - } else { - return PEAR::raiseError( - "Transport::cronCallMethod: invalid state ({$state})" - ); - } - switch ($state) { - // do nothing if closed, penfing or failed: - case 'closed': // excluded in SQL query too, but let check it here - case 'failed': // -"- - case 'pending': - case 'paused': - return TRUE; - case 'waiting': - require_once('Prefs.php'); - $pr = new Prefs($this->gb); - $group = $CC_CONFIG['StationPrefsGr']; - $key = 'TransportsDenied'; - $res = $pr->loadGroupPref($group, $key); - if (PEAR::isError($res)) { - if ($res->getCode() !== GBERR_PREF) { - return $res; - } else { - $res = FALSE; // default - } - } - // transfers turned off - // if ($res) { return TRUE; break; } - if ($res) { - return PEAR::raiseError( - "Transport::cronCallMethod: transfers turned off" - ); - } - // NO break here! - default: - if (method_exists($this, $mname)) { - // lock the job: - $pid = getmypid(); - $r = $trec->setLock(TRUE, $pid); - if (PEAR::isError($r)) { - return $r; - } - $trec = TransportRecord::recall($this, $trtok); - if (PEAR::isError($trec)) { - $trec->setLock(FALSE); - return $trec; - } - $row = $trec->row; - $state = $row['state']; - - // login to archive server: - $r = $this->loginToArchive(); - if (PEAR::isError($r)) { - $r2 = $trec->setLock(FALSE); - return $r; - } - $asessid = $r; - // method call: - if (TR_LOG_LEVEL > 2) { - $this->trLog("cronCallMethod($pid): $mname($trtok) >"); - } - $ret = call_user_func(array($this, $mname), $row, $asessid); - if (PEAR::isError($ret)) { - $trec->setLock(FALSE); - return $this->_failFatal($ret, $trec); - } - if (TR_LOG_LEVEL > 2) { - $this->trLog("cronCallMethod($pid): $mname($trtok) <"); - } - // unlock the job: - $r = $trec->setLock(FALSE); - if (PEAR::isError($r)) { - return $r; - } - // logout: - $r = $this->logoutFromArchive($asessid); - if (PEAR::isError($r)) { - return $r; - } - return $ret; - } else { - return PEAR::raiseError( - "Transport::cronCallMethod: unknown method ($mname)" - ); - } - } - } - - - /** - * Upload initialization - * - * @param array $row - * row from getTransport results - * @param string $asessid - * session id (from network hub) - * @return mixed - * boolean TRUE or error object - */ - function cronUploadInit($row, $asessid) - { - $trtok = $row['trtok']; - $trec = TransportRecord::recall($this, $trtok); - if (PEAR::isError($trec)) { - return $trec; - } - $ret = $this->xmlrpcCall('archive.uploadOpen', - array( - 'sessid' => $asessid , - 'chsum' => $row['expectedsum'], - )); - if (PEAR::isError($ret)) { - return $ret; - } - $r = $trec->setState('waiting', - array('url'=>$ret['url'], 'pdtoken'=>$ret['token'])); - if (PEAR::isError($r)) { - return $r; - } - return TRUE; - } - - - /** - * Download initialization - * - * @param array $row - * row from getTransport results - * @param string $asessid - * session id (from network hub) - * @return mixed - * boolean TRUE or error object - */ - function cronDownloadInit($row, $asessid) - { - global $CC_CONFIG; - $trtok = $row['trtok']; - $trec = TransportRecord::recall($this, $trtok); - if (PEAR::isError($trec)) { - return $trec; - } - $ret = $this->xmlrpcCall('archive.downloadOpen', - array( - 'sessid'=> $asessid, - 'trtype'=> $row['trtype'], - 'pars'=>array( - 'gunid' => $row['gunid'], - 'token' => $row['pdtoken'], - ), - )); - if (PEAR::isError($ret)) { - return $ret; - } - $trtype = $ret['trtype']; - $title = $ret['title']; - $pars = array(); - switch ($trtype) { -// case "searchjob": -// $r = $trec->setState('waiting', $pars); -// break; - case "file": - $r = $trec->setState('waiting',array_merge($pars, array( - 'trtype'=>$trtype, - 'url'=>$ret['url'], 'pdtoken'=>$ret['token'], - 'expectedsum'=>$ret['chsum'], 'expectedsize'=>$ret['size'], - 'fname'=>$ret['filename'], - 'localfile'=>$CC_CONFIG['transDir']."/$trtok", - ))); - break; - case "audioclip": - $mdtrec = TransportRecord::create($this, 'metadata', 'down', - array('gunid'=>$row['gunid'], 'uid'=>$row['uid'], ) - ); - if (PEAR::isError($mdtrec)) { - return $mdtrec; - } - $this->startCronJobProcess($mdtrec->trtok); - $pars = array('mdtrtok'=>$mdtrec->trtok); - // NO break here ! - default: - $r = $trec->setState('waiting',array_merge($pars, array( - 'trtype'=>$trtype, - 'url'=>$ret['url'], 'pdtoken'=>$ret['token'], - 'expectedsum'=>$ret['chsum'], 'expectedsize'=>$ret['size'], - 'fname'=>$ret['filename'], 'title'=>$title, - 'localfile'=>$CC_CONFIG['transDir']."/$trtok", - ))); - } - if (PEAR::isError($r)) { - return $r; - } - return TRUE; - } - - - /** - * Upload next part of transported file - * - * @param array $row - * row from getTransport results - * @param string $asessid - * session id (from network hub) - * @return mixed - * boolean TRUE or error object - */ - function cronUploadWaiting($row, $asessid) - { - $trtok = $row['trtok']; - $check = $this->uploadCheck($row['pdtoken']); - if (PEAR::isError($check)) { - return $check; - } - // test filesize - if (!file_exists($row['localfile'])) { - return PEAR::raiseError("Transport::cronUploadWaiting:". - " file being uploaded does not exist! ({$row['localfile']})" - ); - } - $trec = TransportRecord::recall($this, $trtok); - if (PEAR::isError($trec)) { - return $trec; - } - $size = escapeshellarg($check['size']); - $localfile = escapeshellarg($row['localfile']); - $url = escapeshellarg($row['url']); - $command = - "curl -f -s -C $size --max-time {$this->upTrMaxTime}". - " --speed-time {$this->upTrSpeedTime}". - " --speed-limit {$this->upTrSpeedLimit}". - " --connect-timeout {$this->upTrConnectTimeout}". - (!is_null($this->upLimitRate)? - " --limit-rate {$this->upLimitRate}" : ""). - " -T $localfile $url"; - $r = $trec->setState('pending', array(), 'waiting'); - if (PEAR::isError($r)) { - return $r; - } - if ($r === FALSE) { - return TRUE; - } - $res = system($command, $status); - - // leave paused and closed transports - $trec2 = TransportRecord::recall($this, $trtok); - if (PEAR::isError($trec)) { - return $trec; - } - $state2 = $trec2->row['state']; - if ($state2 == 'paused' || $state2 == 'closed' ) { - return TRUE; - } - - - // status 18 - Partial file. Only a part of the file was transported. - // status 28 - Timeout. Too long/slow upload, try to resume next time rather. - // status 6 - Couldn't resolve host. - // status 7 - Failed to connect to host. - // status 56 - Failure in receiving network data. Important - this status is - // returned if file is locked on server side - if ($status == 0 || $status == 18 || $status == 28 || $status == 6 || $status == 7 || $status == 56) { - $check = $this->uploadCheck($row['pdtoken']); - if (PEAR::isError($check)) { - return $check; - } - // test checksum - if ($check['status'] == TRUE) { - // finished - $r = $trec->setState('finished', - array('realsum'=>$check['realsum'], 'realsize'=>$check['size'])); - if (PEAR::isError($r)) { - return $r; - } - } else { - if (intval($check['size']) < $row['expectedsize']) { - $r = $trec->setState('waiting', - array('realsum'=>$check['realsum'], 'realsize'=>$check['size'])); - if (PEAR::isError($r)) { - return $r; - } - } else { - // wrong md5 at finish - TODO: start again - // $this->xmlrpcCall('archive.uploadReset', array()); - $trec->fail('file uploaded with bad md5'); - return PEAR::raiseError("Transport::cronUploadWaiting:". - " file uploaded with bad md5 ". - "($trtok: {$check['realsum']}/{$check['expectedsum']})" - ); - } - } - } else { - return PEAR::raiseError("Transport::cronUploadWaiting:". - " wrong return status from curl: $status on $url". - "($trtok)" - ); - } - return TRUE; - } - - - /** - * Download next part of transported file - * - * @param array $row - * row from getTransport results - * @param string $asessid - * session id (from network hub) - * @return mixed - * boolean TRUE or error object - */ - function cronDownloadWaiting($row, $asessid) - { - $trtok = $row['trtok']; - // wget the file - $trec = TransportRecord::recall($this, $trtok); - if (PEAR::isError($trec)) { - return $trec; - } - $localfile = escapeshellarg($row['localfile']); - $url = escapeshellarg($row['url']); - $command = - "wget -q -c". - " --read-timeout={$this->downTimeout}". - " --waitretry={$this->downWaitretry}". - " -t {$this->downRetries}". - (!is_null($this->downLimitRate)? - " --limit-rate={$this->downLimitRate}" : ""). - " -O $localfile $url" - ; - $r = $trec->setState('pending', array(), 'waiting'); - if (PEAR::isError($r)) { - return $r; - } - if ($r === FALSE) { - return TRUE; - } - $res = system($command, $status); - - // leave paused and closed transports - $trec2 = TransportRecord::recall($this, $trtok); - if (PEAR::isError($trec)) { - return $trec; - } - $state2 = $trec2->row['state']; - if ($state2 == 'paused' || $state2 == 'closed' ) { - return TRUE; - } - - // check consistency - $size = filesize($row['localfile']); - if ($size < $row['expectedsize']) { - // not finished - return to the 'waiting' state - $r = $trec->setState('waiting', array('realsize'=>$size)); - if (PEAR::isError($r)) { - return $r; - } - } elseif ($size >= $row['expectedsize']) { - $chsum = $this->_chsum($row['localfile']); - if ($chsum == $row['expectedsum']) { - // mark download as finished - $r = $trec->setState('finished', - array('realsum'=>$chsum, 'realsize'=>$size)); - if (PEAR::isError($r)) { - return $r; - } - } else { - // bad checksum, retry from the scratch - @unlink($row['localfile']); - $r = $trec->setState('waiting', - array('realsum'=>$chsum, 'realsize'=>$size)); - if (PEAR::isError($r)) { - return $r; - } - } - } - return TRUE; - } - - - /** - * Finish the upload - * - * @param array $row - * row from getTransport results - * @param string $asessid - * session id (from network hub) - * @return mixed - * boolean TRUE or error object - */ - function cronUploadFinished($row, $asessid) - { - global $CC_CONFIG; - $trtok = $row['trtok']; - $trec = TransportRecord::recall($this, $trtok); - if (PEAR::isError($trec)) { - return $trec; - } - // don't close metadata transport - audioclip will close it - if ($row['trtype'] == 'metadata') { - return TRUE; - } - // handle metadata transport on audioclip trtype: - if ($row['trtype'] == 'audioclip') { - $mdtrec = TransportRecord::recall($this, $trec->row['mdtrtok']); - if (PEAR::isError($mdtrec)) { - return $mdtrec; - } - switch ($mdtrec->row['state']) { - case 'failed': - case 'closed': - return PEAR::raiseError("Transport::cronUploadFinished:". - " metadata transport in wrong state: {$mdtrec->row['state']}". - " ({$this->trtok})" - ); - break; - // don't close transport with nonfinished metadata transport: - case 'init': - case 'waiting': - case 'pending': - case 'paused': - return TRUE; - default: // finished - ok close parent transport - $mdpdtoken = $mdtrec->row['pdtoken']; - } - } else { - $mdpdtoken = NULL; - } - $ret = $this->xmlrpcCall('archive.uploadClose', - array( - 'token' => $row['pdtoken'] , - 'trtype' => $row['trtype'], - 'pars' => array( - 'gunid' => $row['gunid'], - 'name' => $row['fname'], - 'mdpdtoken' => $mdpdtoken, - ), - )); - if (PEAR::isError($ret)) { - if ($row['trtype'] == 'audioclip') { - $r2 = $mdtrec->close(); - } - return $ret; - } - -// if ($row['trtype'] == 'searchjob') { -// @unlink($row['localfile']); -// $r = $trec->setState('init', array( -// 'direction' => 'down', -// 'pdtoken' => $ret['token'], -// 'expectedsum' => $ret['chsum'], -// 'expectedsize' => $ret['size'], -// 'url' => $ret['url'], -// 'realsize' => 0, -// )); -// $this->startCronJobProcess($trec->trtok); -// } else { - $r = $trec->close(); -// } - if (PEAR::isError($r)) { - return $r; - } - switch ($row['trtype']) { - case 'audioclip': - // close metadata transport: - $r = $mdtrec->close(); - if (PEAR::isError($r)) { - return $r; - } - break; - case 'playlistPkg': - // remove exported playlist (playlist with content) - $ep = $row['localfile']; - @unlink($ep); - if (preg_match("|/(plExport_[^\.]+)\.lspl$|", $ep, $va)) { - list(,$tmpn) = $va; $tmpn = $CC_CONFIG['transDir']."/$tmpn"; - if (file_exists($tmpn)) { - @unlink($tmpn); - } - } - - break; - default: - } - - return TRUE; - } - - - /** - * Finish the download - * - * @param array $row - * row from getTransport results - * @param string $asessid - * session id (from network hub) - * @return mixed - * boolean TRUE or error object - */ - function cronDownloadFinished($row, $asessid) - { - $trtok = $row['trtok']; - $trec = TransportRecord::recall($this, $trtok); - if (PEAR::isError($trec)) { - return $trec; - } - switch ($row['trtype']) { - case "audioclip": - $mdtrtok = $trec->row['mdtrtok']; - $mdtrec = TransportRecord::recall($this, $mdtrtok); - if (PEAR::isError($mdtrec)) { - return $mdtrec; - } - $pid = getmypid(); - $r = $mdtrec->setLock(TRUE, $pid); - if (PEAR::isError($r)) { - return $r; - } - switch ($mdtrec->row['state']) { - // don't close transport with nonfinished metadata transport: - case 'init': - case 'waiting': - case 'pending': - case 'paused': - $r = $mdtrec->setLock(FALSE); - if (PEAR::isError($r)) { - return $r; - } - return TRUE; - case 'finished': // metadata finished, close main transport - $values = array( - "filename" => $row['fname'], - "filepath" => $trec->row['localfile'], - "metadata" => $mdtrec->row['localfile'], - "gunid" => $row['gunid'], - "filetype" => "audioclip" - ); - $storedFile = StoredFile::Insert($values); - if (PEAR::isError($storedFile)) { - $mdtrec->setLock(FALSE); - return $storedFile; - } - $res = $storedFile->getId(); - $ret = $this->xmlrpcCall('archive.downloadClose', - array( - 'token' => $mdtrec->row['pdtoken'] , - 'trtype' => 'metadata' , - )); - if (PEAR::isError($ret)) { - $mdtrec->setLock(FALSE); - return $ret; - } - $r = $mdtrec->close(); - if (PEAR::isError($r)) { - $r2 = $mdtrec->setLock(FALSE); - return $r; - } - @unlink($trec->row['localfile']); - @unlink($mdtrec->row['localfile']); - break; - default: - $r = $mdtrec->setLock(FALSE); - return PEAR::raiseError("Transport::cronDownloadFinished:". - " metadata transport in wrong state: {$mdtrec->row['state']}". - " ({$this->trtok})" - ); - } - $r = $mdtrec->setLock(FALSE); - if (PEAR::isError($r)) { - return $r; - } - break; - case "metadata": -// case "searchjob": - return TRUE; // don't close - getSearchResults should close it - break; - } - $ret = $this->xmlrpcCall('archive.downloadClose', - array( - 'token' => $row['pdtoken'] , - 'trtype' => $row['trtype'] , - )); - if (PEAR::isError($ret)) { - return $ret; - } - switch ($row['trtype']) { - case "playlist": - $values = array( - "filename" => $row['fname'], - "metadata" => $trec->row['localfile'], - "gunid" => $row['gunid'], - "filetype" => "playlist" - ); - $storedFile = StoredFile::Insert($values); - if (PEAR::isError($storedFile)) { - return $storedFile; - } - $res = $storedFile->getId(); - @unlink($row['localfile']); - break; - case "playlistPkg": - $subjid = $trec->row['uid']; - $fname = $trec->row['localfile']; - $res = $this->gb->bsImportPlaylist($fname, $subjid); - if (PEAR::isError($res)) { - return $res; - } - @unlink($fname); - break; - case "audioclip": - case "metadata": -// case "searchjob": - case "file": - break; - default: - return PEAR::raiseError("DEBUG: NotImpl ".var_export($row,TRUE)); - } - if (!is_null($rtrtok = $trec->row['rtrtok'])) { - $ret = $this->xmlrpcCall('archive.setHubInitiatedTransfer', - array( - 'target' => HOSTNAME, - 'trtok' => $rtrtok, - 'state' => 'closed', - )); - if (PEAR::isError($ret)) { - return $ret; - } - } - $r = $trec->close(); - if (PEAR::isError($r)) { - return $r; - } - return TRUE; - } - - - /* ==================================================== auxiliary methods */ - /** - * Prepare upload for general file - * - * @param string $fpath - * local filepath of uploaded file - * @param string $trtype - * transport type - * @param array $pars - * default parameters (optional, internal use) - * @return object - transportRecord instance - */ - function _uploadGeneralFileToHub($fpath, $trtype, $pars=array()) - { - $chsum = $this->_chsum($fpath); - $size = filesize($fpath); - $trec = TransportRecord::create($this, $trtype, 'up', - array_merge(array( - 'localfile'=>$fpath, 'fname'=>basename($fpath), - 'expectedsum'=>$chsum, 'expectedsize'=>$size - ), $pars) - ); - if (PEAR::isError($trec)) { - return $trec; - } - return $trec; - } - - - /** - * Create new transport token - * - * @return string - * transport token - */ - function _createTransportToken() - { - $ip = (isset($_SERVER['SERVER_ADDR']) ? $_SERVER['SERVER_ADDR'] : ''); - $initString = microtime().$ip.rand()."org.mdlf.campcaster"; - $hash = md5($initString); - $res = substr($hash, 0, 16); - return $res; - } - - - /** - * Get all relevant transport records - * - * @param string $direction - * 'up' | 'down' - * @param string $target - * target hostname - * @param string $trtok - * transport token for specific query - * @return array - * array of transportRecords (as hasharrays) - */ - function getTransports($direction=NULL, $target=NULL, $trtok=NULL) - { - global $CC_CONFIG, $CC_DBC; - switch ($direction) { - case 'up': - $dirCond = "direction='up' AND"; - break; - case 'down': - $dirCond = "direction='down' AND"; - break; - default: - $dirCond = ''; - break; - } - if (is_null($target)) { - $targetCond = ""; - } else { - $targetCond = "target='$target' AND"; - } - if (is_null($trtok)) { - $trtokCond = ""; - } else { - $trtokCond = "trtok='$trtok' AND"; - } - $rows = $CC_DBC->getAll(" - SELECT - id, trtok, state, trtype, direction, - to_hex(gunid)as gunid, to_hex(pdtoken)as pdtoken, - fname, localfile, expectedsum, expectedsize, url, - uid, target - FROM ".$CC_CONFIG['transTable']." - WHERE $dirCond $targetCond $trtokCond - state not in ('closed', 'failed', 'paused') - ORDER BY start DESC - "); - if (PEAR::isError($rows)) { - return $rows; - } - foreach ($rows as $i => $row) { - $rows[$i]['pdtoken'] = StoredFile::NormalizeGunid($row['pdtoken']); - $rows[$i]['gunid'] = StoredFile::NormalizeGunid($row['gunid']); - } - return $rows; - } - - - /** - * Check remote state of uploaded file - * - * @param string $pdtoken - * put/download token (from network hub) - * @return array - * hash: chsum, size, url - */ - function uploadCheck($pdtoken) - { - $ret = $this->xmlrpcCall('archive.uploadCheck', - array('token'=>$pdtoken)); - return $ret; - } - - - /** - * Ping to remote Airtime server - * - * @return string - * network hub response or error object - */ - function ping() - { - $res = $this->xmlrpcCall('ping', - array('par'=>'ping_'.date('H:i:s'))); - return $res; - } - - - /** - * XMLRPC call to network hub. - * - * @param string $method - * method name - * @param array $pars - * call parameters - * @return mixed - * response - */ - function xmlrpcCall($method, $pars=array()) - { - global $CC_CONFIG; - $xrp = XML_RPC_encode($pars); - - $pr = new Prefs($this->gb); - $group = $CC_CONFIG["StationPrefsGr"]; - $key = 'archiveServerLocation'; - $archiveUrl = $pr->loadGroupPref($group, $key, false); - - if ($archiveUrl) { - $archiveUrlInfo = parse_url($archiveUrl); - if ($archiveUrlInfo['port']) { - $port = $archiveUrlInfo['port']; - } - else { - $port = 80; - } - - $c = new XML_RPC_Client($archiveUrlInfo['path'], $archiveUrlInfo['host'], $port); - } - else { - $c = new XML_RPC_Client( - $CC_CONFIG['archiveUrlPath']."/".$CC_CONFIG['archiveXMLRPC'], - $CC_CONFIG['archiveUrlHost'], $CC_CONFIG['archiveUrlPort'] - ); - } - - $f = new XML_RPC_Message($method, array($xrp)); - $r = $c->send($f); - if (!$r) { - return PEAR::raiseError("XML-RPC request failed", TRERR_XR_FAIL); - } elseif ($r->faultCode() > 0) { - return PEAR::raiseError($r->faultString(), $r->faultCode()); - // return PEAR::raiseError($r->faultString(). - // " (code ".$r->faultCode().")", TRERR_XR_FAIL); - } else { - $v = $r->value(); - return XML_RPC_decode($v); - } - } - - - /** - * Checksum of local file - * - * @param string $fpath - * local filepath - * @return string - * checksum - */ - function _chsum($fpath) - { - return md5_file($fpath); - } - - - /** - * Check exception and eventually mark transport as failed - * - * @param mixed $res - * result object to be checked - * @param unknown $trec - * transport record object - * @return unknown - */ - function _failFatal($res, $trec) - { - if (PEAR::isError($res)) { - switch ($res->getCode()) { - // non fatal: - case TRERR_XR_FAIL: - break; - // fatal: - default: - $trec->fail('', $res); - } - } - return $res; - } - - - /** - * Clean up transport jobs - * - * @param string $interval - * psql time interval - older closed jobs will be deleted - * @param boolean $forced - * if true, delete non-closed jobs too - * @return boolean true or error - */ - function _cleanUp($interval='1 minute'/*'1 hour'*/, $forced=FALSE) - { - global $CC_CONFIG, $CC_DBC; - $cond = ($forced ? '' : " AND state='closed' AND lock = 'N'"); - $r = $CC_DBC->query(" - DELETE FROM ".$CC_CONFIG['transTable']." - WHERE ts < now() - interval '$interval'".$cond - ); - if (PEAR::isError($r)) { - return $r; - } - return TRUE; - } - - - /** - * Logging wrapper for PEAR error object - * - * @param string $txt - * log message - * @param PEAR_Error $eo - * @param array $row - * array returned from getRow - * @return mixed - * void or error object - */ - function trLogPear($txt, $eo, $row=NULL) - { - $msg = $txt.$eo->getMessage()." ".$eo->getUserInfo(). - " [".$eo->getCode()."]"; - if (!is_null($row)) { - $trec = TransportRecord::recall($this, $row['trtok']); - if (!PEAR::isError($trec)) { - $trec->setState('failed', array('errmsg'=>$msg)); - } - $msg .= "\n ".serialize($row); - } - $this->trLog($msg); - } - - - /** - * Logging for debug transports - * - * @param string $msg - * log message - * @return mixed - * void or error object - */ - function trLog($msg) - { - global $CC_CONFIG; - $logfile = $CC_CONFIG['transDir']."/activity.log"; - if (FALSE === ($fp = fopen($logfile, "a"))) { - return PEAR::raiseError( - "Transport::trLog: Can't write to log ($logfile)" - ); - } - flock($fp,LOCK_SH); - fputs($fp, "---".date("H:i:s")."---\n $msg\n"); - flock($fp,LOCK_UN); - fclose($fp); - } - - - /* ====================================================== install methods */ - /** - * Delete all transports - * - * @return mixed - * void or error object - */ - function resetData() - { - global $CC_CONFIG, $CC_DBC; - return $CC_DBC->query("DELETE FROM ".$CC_CONFIG['transTable']); - } - -} - - diff --git a/application/models/TransportRecord.php b/application/models/TransportRecord.php deleted file mode 100644 index 43b3a529a..000000000 --- a/application/models/TransportRecord.php +++ /dev/null @@ -1,419 +0,0 @@ -tr =& $tr; - $this->gb =& $tr->gb; - } - - - /** - * Factory method - * - * @param Transport $tr - * @param string $trtype - * transport type (see Transport::install) - * @param string $direction - * 'up' | 'down' - * @param array $defaults - * default parameters (optional, internal use) - * @return TransportRecord - */ - function create(&$tr, $trtype, $direction='up', $defaults=array()) - { - global $CC_DBC, $CC_CONFIG; - $trec = new TransportRecord($tr); - $trec->trtok = $trtok = $tr->_createTransportToken(); - $trec->row = array_merge($defaults, - array('trtype'=>$trtype, 'direction'=>$direction)); - $trec->recalled = TRUE; - if (!isset($defaults['title'])) { - $defaults['title'] = $trec->getTitle(); - if (PEAR::isError($defaults['title'])) { - return $defaults['title']; - } - } - $id = $CC_DBC->nextId($CC_CONFIG['transSequence']); - $names = "id, trtok, direction, state, trtype, start, ts"; - $values = "$id, '$trtok', '$direction', 'init', '$trtype', now(), now()"; - foreach ($defaults as $k => $v) { - $sqlVal = $trec->_getSqlVal($k, $v); - $names .= ", $k"; - $values .= ", $sqlVal"; - } - $query = " - INSERT INTO ".$CC_CONFIG['transTable']." - ($names) - VALUES - ($values) - "; - $res = $CC_DBC->query($query); - if (PEAR::isError($res)) { - return $res; - } - return $trec; - } - - - /** - * Recall transport record from DB - * - * @param Transport $tr - * @param string $trtok - * transport token - * @return TransportRecord - */ - function recall(&$tr, $trtok) - { - global $CC_DBC, $CC_CONFIG; - $trec = new TransportRecord($tr); - $trec->trtok = $trtok; - $row = $CC_DBC->getRow(" - SELECT - id, trtok, state, trtype, direction, - to_hex(gunid)as gunid, to_hex(pdtoken)as pdtoken, - fname, localfile, url, rtrtok, mdtrtok, uid, - expectedsize, realsize, expectedsum, realsum, - errmsg, title, jobpid - FROM ".$CC_CONFIG['transTable']." - WHERE trtok='$trtok' - "); - if (PEAR::isError($row)) { - return $row; - } - if (is_null($row)) { - return PEAR::raiseError("TransportRecord::recall:". - " invalid transport token ($trtok)", TRERR_TOK - ); - } - $row['pdtoken'] = StoredFile::NormalizeGunid($row['pdtoken']); - $row['gunid'] = StoredFile::NormalizeGunid($row['gunid']); - $trec->row = $row; - $trec->recalled = TRUE; - return $trec; - } - - - /** - * Set state of transport record - * - * @param string $newState - * @param array $data - * other data fields to set - * @param string $oldState - * check old state and do nothing if differ - * @param boolean $lock - * check lock and do nothing if differ - * @return boolean success - */ - function setState($newState, $data=array(), $oldState=NULL, $lock=NULL) - { - global $CC_CONFIG, $CC_DBC; - $set = " state='$newState', ts=now()"; - if (!is_null($lock)) { - $slock = ($lock ? 'Y' : 'N'); - $nlock = (!$lock); - $snlock = ($nlock ? 'Y' : 'N'); - $set .= ", lock='$snlock'"; - } - foreach ($data as $k => $v) { - $set .= ", $k=".$this->_getSqlVal($k, $v); - } - $r = $CC_DBC->query(" - UPDATE ".$CC_CONFIG['transTable']." - SET $set - WHERE trtok='{$this->trtok}'". - (is_null($oldState) ? '' : " AND state='$oldState'"). - (is_null($lock) ? '' : " AND lock = '$slock'") - ); - if (PEAR::isError($r)) { - return $r; - } - // return TRUE; - $affRows = $CC_DBC->affectedRows(); - if (PEAR::isError($affRows)) { - return $affRows; - } - return ($affRows == 1); - } - - - /** - * Return state of transport record - * - * @return string - * state - */ - function getState() - { - if (!$this->recalled) { - return PEAR::raiseError("TransportRecord::getState:". - " not recalled ({$this->trtok})", TRERR_TOK - ); - } - return $this->row['state']; - } - - - /** - * Set lock on transport record and save/clear process id - * - * @param boolean $lock - * lock if true, release lock if false - * @param int $pid - * process id - * @return mixed - * true or error - */ - function setLock($lock, $pid=NULL) - { - global $CC_CONFIG, $CC_DBC; - $pidsql = (is_null($pid) ? "NULL" : "$pid" ); - if ($this->dropped) { - return TRUE; - } - $slock = ($lock ? 'Y' : 'N'); - $nlock = (!$lock); - $snlock = ($nlock ? 'Y' : 'N'); - $r = $CC_DBC->query(" - UPDATE ".$CC_CONFIG['transTable']." - SET lock='$slock', jobpid=$pidsql, ts=now() - WHERE trtok='{$this->trtok}' AND lock = '$snlock'" - ); - if (PEAR::isError($r)) { - return $r; - } - $affRows = $CC_DBC->affectedRows(); - if (PEAR::isError($affRows)) { - return $affRows; - } - if ($affRows === 0) { - $ltxt = ($lock ? 'lock' : 'unlock' ); - return PEAR::raiseError( - "TransportRecord::setLock: can't $ltxt ({$this->trtok})" - ); - } - return TRUE; - } - - - /** - * Return type of transport - * - * @return string - * Transport type - */ - function getTransportType() - { - if (!$this->recalled) { - return PEAR::raiseError("TransportRecord::getTransportType:". - " not recalled ({$this->trtok})", TRERR_TOK - ); - } - return $this->row['trtype']; - } - - - /** - * Kill transport job (on pause or cancel) - * - * @return string - * Transport type - */ - function killJob() - { - if (!$this->recalled) { - return PEAR::raiseError("TransportRecord::getTransportType:". - " not recalled ({$this->trtok})", TRERR_TOK - ); - } - $jobpid = $this->row['jobpid']; - $res = system("pkill -P $jobpid", $status); - } - - - /** - * Set state to failed and set error message in transport record - * - * @param string $txt - * base part of error message - * @param PEAR_Error $eo - * (opt.) error msg can be construct from it - * @return mixed - * boolean true or error - */ - function fail($txt='', $eo=NULL) - { - if (!$this->recalled) { - return PEAR::raiseError("TransportRecord::fail:". - " not recalled ({$this->trtok})", TRERR_TOK - ); - } - $msg = $txt; - if (!is_null($eo)) { - $msg .= $eo->getMessage()." ".$eo->getUserInfo(). - " [".$eo->getCode()."]"; - } - $r = $this->setState('failed', array('errmsg'=>$msg)); - if (PEAR::isError($r)) { - return $r; - } - return TRUE; - } - - - /** - * Close transport record - * - * @return mixed - * boolean true or error - */ - function close() - { - global $CC_CONFIG, $CC_DBC; - if (!$this->recalled) { - return PEAR::raiseError("TransportRecord::close:". - " not recalled ({$this->trtok})", TRERR_TOK - ); - } - if (TR_LEAVE_CLOSED) { - $r = $this->setState('closed'); - if (PEAR::isError($r)) { - return $r; - } - } else { - $r = $CC_DBC->query(" - DELETE FROM ".$CC_CONFIG['transTable']." - WHERE trtok='{$this->trtok}' - "); - if (PEAR::isError($r)) { - return $r; - } - $this->recalled = FALSE; - $this->dropped = TRUE; - } - return TRUE; - } - - - /** - * Add field specific envelopes to values (e.g. ' around strings) - * - * @param string $fldName - * field name - * @param mixed $fldVal - * field value - * @return string - */ - function _getSqlVal($fldName, $fldVal) - { - switch ($fldName) { - case 'realsize': - case 'expectedsize': - case 'uid': - return ("$fldVal"!='' ? "$fldVal" : "NULL"); - break; - case 'gunid': - case 'pdtoken': - return "x'$fldVal'::bigint"; - break; - default: - $fldVal = pg_escape_string($fldVal); - return "'$fldVal'"; - break; - } - } - - - /** - * Get title from transported object's metadata (if exists) - * - * @return string - * the title or descriptive string - */ - function getTitle() - { - $defStr = 'unknown'; - $trtype = $this->getTransportType(); //contains recall check - if (PEAR::isError($trtype)) { - return $trtype; - } - switch ($trtype) { - case "audioclip": - case "playlist": - case "playlistPkg": - case "metadata": - $title = $this->gb->bsGetTitle(NULL, $this->row['gunid']); - if (is_null($title)) { - $title = $defStr; - } - if (PEAR::isError($title)) { - if ($title->getCode() == GBERR_FOBJNEX) { - $title = $defStr; - } else { - return $title; - } - } - break; - case "searchjob": - $title = 'searchjob'; - break; - case "file": - $title = ( isset($this->row['localfile']) ? - basename($this->row['localfile']) : 'regular file'); - break; - default: - $title = $defStr; - } - return $title; - } - -} // class TransportRecord - diff --git a/application/models/Validator.php b/application/models/Validator.php deleted file mode 100644 index 077f0aba6..000000000 --- a/application/models/Validator.php +++ /dev/null @@ -1,385 +0,0 @@ - - *
  • audioClipFormat.php
  • - *
  • webstreamFormat.php
  • - *
  • playlistFormat.php
  • - * - * It probably should be replaced by XML schema validation in the future. - * - * @package Airtime - * @subpackage StorageServer - * @copyright 2010 Sourcefabric O.P.S. - * @license http://www.gnu.org/licenses/gpl.txt - */ -class Validator { - /** - * Format type of validated document - * @var string - */ - private $format = NULL; - - /** - * Preloaded format tree structure - * @var array - */ - private $formTree = NULL; - - /** - * Gunid of validated file for identification in mass input - * @var string - */ - private $gunid = NULL; - - - /** - * Constructor - * - * @param string $format - * format type of validated document - * @param string $gunid - * gunid of validated file for identification in mass input - */ - public function __construct($format, $gunid) - { - $format = strtolower($format); - $this->format = $format; - $this->gunid = $gunid; - $formats = array( - 'audioclip' => "audioClipFormat", - 'playlist' => "playlistFormat", - 'webstream' => "webstreamFormat", - ); - if (!isset($formats[$format])) { - return $this->_err(VAL_FORMAT); - } - $formatName = $formats[$format]; - $formatFile = dirname(__FILE__)."/$formatName.php"; - if (!file_exists($formatFile)) { - return $this->_err(VAL_FORMAT); - } - require($formatFile); - $this->formTree = $$formatName; - } - - - /** - * Validate document - only wrapper for validateNode method - * - * @param object $data - * validated object tree - * @return mixed - * TRUE or PEAR::error - */ - function validate(&$data) - { - $r = $this->validateNode($data, $this->formTree['_root']); - return $r; - } - - - /** - * Validate one metadata value (on insert/update) - * - * @param string $fname - * parent element name - * @param string $category - * qualif.category name - * @param string $predxml - * 'A' | 'T' (attr or tag) - * @param string $value - * validated element value - * @return TRUE|PEAR_Error - */ - function validateOneValue($fname, $category, $predxml, $value) - { - $formTree =& $this->formTree; - switch ($predxml) { - case 'T': - if (!$this->isChildInFormat($fname, $category)) { - return $this->_err(VAL_UNKNOWNE, "$category in $fname"); - } - break; - case 'A': - if (!$this->isAttrInFormat($fname, $category)) { - return $this->_err(VAL_UNKNOWNA, "$category in $fname"); - } - break; - case 'N': - return TRUE; - break; - default: - return $this->_err(VAL_PREDXML, $predxml); - } - if (isset($formTree[$category]['regexp'])) { - // echo "XXX {$formTree[$fname]['regexp']} / ".$node->content."\n"; - if (!preg_match("|{$formTree[$category]['regexp']}|", $value)) { - return $this->_err(VAL_CONTENT, "$category/$value"); - } - } - } - - - /** - * Validation of one element node from object tree - * - * @param object $node - * validated node - * @param string $fname - * actual name in format structure - * @return mixed - * TRUE or PEAR::error - */ - function validateNode(&$node, $fname) - { - $dname = (($node->ns? $node->ns.":" : '').$node->name); - $formTree =& $this->formTree; - if (DEBUG) { - echo"\nVAL::validateNode: 1 $dname/$fname\n"; - } - // check root node name: - if ($dname != $fname) { - return $this->_err(VAL_ROOT, $fname); - } - // check if this element is defined in format: - if (!isset($formTree[$fname])) { - return $this->_err(VAL_NOTDEF, $fname); - } - // check element content - if (isset($formTree[$fname]['regexp'])) { - // echo "XXX {$formTree[$fname]['regexp']} / ".$node->content."\n"; - if (!preg_match("|{$formTree[$fname]['regexp']}|", $node->content)) { - return $this->_err(VAL_CONTENT, "$fname/{$node->content}"); - } - } - // validate attributes: - $ra = $this->validateAttributes($node, $fname); - if (PEAR::isError($ra)) { - return $ra; - } - // validate children: - $r = $this->validateChildren($node, $fname); - if (PEAR::isError($r)) { - return $r; - } - return TRUE; - } - - - /** - * Validation of attributes - * - * @param object $node - * validated node - * @param string $fname - * actual name in format structure - * @return mixed - * TRUE or PEAR::error - */ - function validateAttributes(&$node, $fname) - { - $formTree =& $this->formTree; - $attrs = array(); - // check if all attrs are permitted here: - foreach ($node->attrs as $i => $attr) { - $aname = (($attr->ns? $attr->ns.":" : '').$attr->name); - $attrs[$aname] =& $node->attrs[$i]; - if (!$this->isAttrInFormat($fname, $aname)) { - return $this->_err(VAL_UNKNOWNA, $aname); - } - // check attribute format - // echo "XXA $aname\n"; - if (isset($formTree[$aname]['regexp'])) { - // echo "XAR {$formTree[$fname]['regexp']} / ".$node->content."\n"; - if (!preg_match("|{$formTree[$aname]['regexp']}|", $attr->val)) { - return $this->_err(VAL_ATTRIB, "$aname [".var_export($attr->val,TRUE)."]"); - } - } - } - // check if all required attrs are here: - if (isset($formTree[$fname]['attrs'])) { - $fattrs =& $formTree[$fname]['attrs']; - if (isset($fattrs['required'])) { - foreach ($fattrs['required'] as $i => $attr) { - if (!isset($attrs[$attr])) { - return $this->_err(VAL_NOREQA, $attr); - } - } - } - } - return TRUE; - } - - - /** - * Validation children nodes - * - * @param object $node - * validated node - * @param string $fname - * actual name in format structure - * @return mixed - * TRUE or PEAR::error - */ - function validateChildren(&$node, $fname) - { - $formTree =& $this->formTree; - $childs = array(); - // check if all children are permitted here: - foreach ($node->children as $i => $ch) { - $chname = (($ch->ns? $ch->ns.":" : '').$ch->name); - // echo "XXE $chname\n"; - if (!$this->isChildInFormat($fname, $chname)) { - return $this->_err(VAL_UNKNOWNE, $chname); - } - // call children recursive: - $r = $this->validateNode($node->children[$i], $chname); - if (PEAR::isError($r)) { - return $r; - } - $childs[$chname] = TRUE; - } - // check if all required children are here: - if (isset($formTree[$fname]['childs'])) { - $fchilds =& $formTree[$fname]['childs']; - if (isset($fchilds['required'])) { - foreach ($fchilds['required'] as $i => $ch) { - if (!isset($childs[$ch])) return $this->_err(VAL_NOREQE, $ch); - } - } - // required one from set - if (isset($fchilds['oneof'])) { - $one = FALSE; - foreach ($fchilds['oneof'] as $i => $ch) { - if (isset($childs[$ch])) { - if ($one) { - return $this->_err(VAL_UNEXPONEOF, "$ch in $fname"); - } - $one = TRUE; - } - } - if (!$one) { - return $this->_err(VAL_NOONEOF); - } - } - } - return TRUE; - } - - - /** - * Test if child is presented in format structure - * - * @param string $fname - * node name in format structure - * @param string $chname - * child node name - * @return boolean - */ - function isChildInFormat($fname, $chname) - { - $listo = $this->isInFormatAs($fname, $chname, 'childs', 'optional'); - $listr = $this->isInFormatAs($fname, $chname, 'childs', 'required'); - $list1 = $this->isInFormatAs($fname, $chname, 'childs', 'oneof'); - return ($listo!==FALSE || $listr!==FALSE || $list1!==FALSE); - } - - - /** - * Test if attribute is presented in format structure - * - * @param string $fname - * node name in format structure - * @param string $aname - * attribute name - * @return boolean - */ - function isAttrInFormat($fname, $aname) - { - $listr = $this->isInFormatAs($fname, $aname, 'attrs', 'required'); - $listi = $this->isInFormatAs($fname, $aname, 'attrs', 'implied'); - $listn = $this->isInFormatAs($fname, $aname, 'attrs', 'normal'); - return ($listr!==FALSE || $listi!==FALSE || $listn!==FALSE); - } - - - /** - * Check if node/attribute is presented in format structure - * - * @param string $fname - * node name in format structure - * @param string $chname - * node/attribute name - * @param string $nType - * 'childs' | 'attrs' - * @param string $reqType - *
      - *
    • for elements: 'required' | 'optional' | 'oneof'
    • - *
    • for attributes: 'required' | 'implied' | 'normal'
    • - *
    - * @return mixed - * boolean/int (index int format array returned if found) - */ - function isInFormatAs($fname, $chname, $nType='childs', $reqType='required') - { - $formTree =& $this->formTree; - $listed = ( - isset($formTree[$fname][$nType][$reqType]) ? - array_search($chname, $formTree[$fname][$nType][$reqType]) : - FALSE - ); - return $listed; - } - - - /** - * Error exception generator - * - * @param int $errno - * erron code - * @param string $par - * optional string for more descriptive error messages - * @return PEAR_Error - */ - function _err($errno, $par='') - { - $msg = array( - VAL_ROOT => 'Wrong root element', - VAL_NOREQE => 'Required element missing', - VAL_NOONEOF => 'One-of element missing', - VAL_UNKNOWNE => 'Unknown element', - VAL_UNKNOWNA => 'Unknown attribute', - VAL_NOTDEF => 'Not defined', - VAL_UNEXPONEOF => 'Unexpected second object from one-of set', - VAL_FORMAT => 'Unknown format', - VAL_CONTENT => 'Invalid content', - VAL_NOREQA => 'Required attribute missing', - VAL_ATTRIB => 'Invalid attribute format', - VAL_PREDXML => 'Invalid predicate type', - ); - return PEAR::raiseError( - "Validator: {$msg[$errno]} #$errno ($par, gunid={$this->gunid})", - $errno - ); - } - - -} // class Validator - - diff --git a/application/models/audioClipFormat.php b/application/models/audioClipFormat.php deleted file mode 100644 index d8460ccfa..000000000 --- a/application/models/audioClipFormat.php +++ /dev/null @@ -1,326 +0,0 @@ -'audioClip', - 'audioClip'=>array( - 'childs'=>array( - 'required'=>array('metadata'), - ), - ), - 'metadata'=>array( - 'childs'=>array( - 'required'=>array( - 'dc:title', 'dcterms:extent' - ), - 'optional'=>array( - 'dc:identifier', - 'dc:creator', 'dc:source', 'ls:genre', - 'ls:year', 'dc:type', 'dc:description', 'dc:format', - 'ls:bpm', 'ls:rating', 'ls:encoded_by', 'ls:track_num', - 'ls:disc_num', 'ls:disc_num', 'dc:publisher', 'ls:composer', - 'ls:bitrate', 'ls:channels', 'ls:samplerate', 'ls:encoder', - 'ls:crc', 'ls:lyrics', 'ls:orchestra', 'ls:conductor', - 'ls:lyricist', 'ls:originallyricist', 'ls:radiostationname', - 'ls:audiofileinfourl', 'ls:artisturl', 'ls:audiosourceurl', - 'ls:radiostationurl', 'ls:buycdurl', 'ls:isrcnumber', - 'ls:catalognumber', 'ls:originalartist', 'dc:rights', - 'ls:license', 'dc:title', 'dcterms:temporal', - 'dcterms:spatial', 'dcterms:entity', 'dc:description', - 'dc:creator', 'dc:subject', 'dc:type', 'dc:format', - 'dc:contributor', 'dc:language', 'dc:rights', - 'dcterms:isPartOf', 'dc:date', - 'dc:publisher', - // extra - 'dcterms:alternative', 'ls:filename', 'ls:mtime', - // added lately by sebastian - 'ls:mood', - ), - ), - 'namespaces'=>array( - 'dc'=>"http://purl.org/dc/elements/1.1/", - 'dcterms'=>"http://purl.org/dc/terms/", - 'xbmf'=>"http://www.streamonthefly.org/xbmf", - 'xsi'=>"http://www.w3.org/2001/XMLSchema-instance", - 'xml'=>"http://www.w3.org/XML/1998/namespace", - ), - ), - 'dc:identifier'=>array( - 'type'=>'Text', - 'auto'=>TRUE, - ), - 'dc:title'=>array( - 'type'=>'Text', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'dcterms:alternative'=>array( - 'type'=>'Text', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'dcterms:extent'=>array( - 'type'=>'Time', -// 'regexp'=>'^\d{2}:\d{2}:\d{2}.\d{6}$', - 'regexp'=>'^((\d{1,2}:)?\d{1,2}:)?\d{1,20}(.\d{1,6})?$', - ), - 'dc:creator'=>array( - 'type'=>'Text', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'dc:source'=>array( - 'type'=>'Text', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:genre'=>array( - 'type'=>'Menu', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:year'=>array( - 'type'=>'Menu', - 'area'=>'Music', - ), - 'dc:type'=>array( - 'type'=>'Menu', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'dc:description'=>array( - 'type'=>'Longtext', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'dc:format'=>array( - 'type'=>'Menu', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:bpm'=>array( - 'type'=>'Number', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:rating'=>array( - 'type'=>'Number', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:encoded_by'=>array( - 'type'=>'Text', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:track_num'=>array( - 'type'=>'Menu', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:disc_num'=>array( - 'type'=>'Menu', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:disc_num'=>array( - 'type'=>'Menu', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'dc:publisher'=>array( - 'type'=>'Text', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:composer'=>array( - 'type'=>'Text', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:bitrate'=>array( - 'type'=>'Number', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:channels'=>array( - 'type'=>'Menu', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:samplerate'=>array( - 'type'=>'Menu', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:encoder'=>array( - 'type'=>'Text', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:crc'=>array( - 'type'=>'Number', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:lyrics'=>array( - 'type'=>'Longtext', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:orchestra'=>array( - 'type'=>'Text', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:conductor'=>array( - 'type'=>'Text', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:lyricist'=>array( - 'type'=>'Text', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:originallyricist'=>array( - 'type'=>'Text', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:radiostationname'=>array( - 'type'=>'Text', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:audiofileinfourl'=>array( - 'type'=>'URL', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:artisturl'=>array( - 'type'=>'URL', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:audiosourceurl'=>array( - 'type'=>'URL', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:radiostationurl'=>array( - 'type'=>'URL', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:buycdurl'=>array( - 'type'=>'URL', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:isrcnumber'=>array( - 'type'=>'Number', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:catalognumber'=>array( - 'type'=>'Number', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:originalartist'=>array( - 'type'=>'Text', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'dc:rights'=>array( - 'type'=>'Text', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:license'=>array( - 'type'=>'Text', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'dc:title'=>array( - 'type'=>'Text', - 'area'=>'Talk', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'dcterms:temporal'=>array( - 'type'=>'Time/Date', - 'area'=>'Talk', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'dcterms:spatial'=>array( - 'type'=>'Menu', - 'area'=>'Talk', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'dcterms:entity'=>array( - 'type'=>'Text', - 'area'=>'Talk', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'dc:description'=>array( - 'type'=>'Longtext', - 'area'=>'Talk', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'dc:creator'=>array( - 'type'=>'Menu', - 'area'=>'Talk', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'dc:subject'=>array( - 'type'=>'Text', - 'area'=>'Talk', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'dc:type'=>array( - 'type'=>'Menu', - 'area'=>'Talk', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'dc:format'=>array( - 'type'=>'Menu', - 'area'=>'Talk', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'dc:contributor'=>array( - 'type'=>'Text', - 'area'=>'Talk', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'dc:language'=>array( - 'type'=>'Menu', - 'area'=>'Talk', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'dc:rights'=>array( - 'type'=>'Menu', - 'area'=>'Talk', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'dcterms:isPartOf'=>array( - 'type'=>'Text', - 'area'=>'Talk', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'dc:date'=>array( - 'type'=>'Date', - 'area'=>'Talk', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'dc:publisher'=>array( - 'type'=>'Text', - 'area'=>'Talk', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - - 'ls:filename'=>array( - 'type'=>'Text', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:mtime'=>array( - 'type'=>'Int', -// 'regexp'=>'^\d{4}(-\d{2}(-\d{2}(T\d{2}:\d{2}(:\d{2}\.\d+)?(Z)|([\+\-]?\d{2}:\d{2}))?)?)?$', - ), -); diff --git a/application/models/cron/transportCron.php b/application/models/cron/transportCron.php deleted file mode 100755 index caf6a24ac..000000000 --- a/application/models/cron/transportCron.php +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/php -setErrorHandling(PEAR_ERROR_RETURN); -$CC_DBC->setFetchMode(DB_FETCHMODE_ASSOC); - -$gb = new LocStor(); -$tr = new Transport($gb); - -$r = $tr->cronMain(); -if (!$r) { - exit(1); -} - -exit(0); diff --git a/application/models/cron/transportCronJob.php b/application/models/cron/transportCronJob.php deleted file mode 100755 index 0a0f66f3d..000000000 --- a/application/models/cron/transportCronJob.php +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/php -setErrorHandling(PEAR_ERROR_RETURN); -$CC_DBC->setFetchMode(DB_FETCHMODE_ASSOC); - -$gb = new LocStor(); -$tr = new Transport($gb); - -$pid = getmypid(); -list(, $trtok) = $_SERVER['argv']; -if (TR_LOG_LEVEL > 1) { - $tr->trLog("transportCronJob($pid) start ($trtok)"); -} - -// 4-pass on job: -$cnt = 4; -for ($i = 0; $i < $cnt; $i++, sleep(1)) { - // run the action: - $r = $tr->cronCallMethod($trtok); - if (PEAR::isError($r)) { - $tr->trLogPear("transportCronJob($pid): ($trtok): ", $r); - } else { -# $tr->trLog("X transportCronJob: ".var_export($r, TRUE)); - if ($r !== TRUE) { - $tr->trLog("transportCronJob($pid): ($trtok): nonTRUE returned"); - } - } - #if(!$r) exit(1); - #sleep(2); -} - -if (TR_LOG_LEVEL>1) { - $tr->trLog("transportCronJob($pid) end ($trtok)"); -} -exit(0); diff --git a/application/models/playlistFormat.php b/application/models/playlistFormat.php deleted file mode 100644 index 36342163e..000000000 --- a/application/models/playlistFormat.php +++ /dev/null @@ -1,112 +0,0 @@ -'playlist', - 'playlist'=>array( - 'childs'=>array( - // 'repeatable'=>array('playlistElement'), - 'optional'=>array('metadata', 'playlistElement'), - ), - 'attrs'=>array( - 'required'=>array('id'), - 'implied'=>array('title', 'playlength'), - ), - ), - 'playlistElement'=>array( - 'childs'=>array( - 'oneof'=>array('audioClip', 'playlist'), - 'optional'=>array('fadeInfo'), - ), - 'attrs'=>array( - 'required'=>array('id', 'relativeOffset', 'clipStart', 'clipEnd', 'clipLength'), - ), - ), - 'audioClip'=>array( - 'childs'=>array( - 'optional'=>array('metadata'), - ), - 'attrs'=>array( - 'implied'=>array('id', 'title', 'playlength', 'uri'), - ), - ), - 'fadeInfo'=>array( - 'attrs'=>array( - 'required'=>array('id', 'fadeIn', 'fadeOut'), - ), - ), - 'metadata'=>array( - 'childs'=>array( - 'optional'=>array( - 'dc:title', 'dcterms:extent', 'dc:creator', 'dc:description', - 'dcterms:alternative', 'ls:filename', 'ls:mtime', - ), - ), - 'namespaces'=>array( - 'dc'=>"http://purl.org/dc/elements/1.1/", - 'dcterms'=>"http://purl.org/dc/terms/", - 'xbmf'=>"http://www.streamonthefly.org/xbmf", - 'xsi'=>"http://www.w3.org/2001/XMLSchema-instance", - 'xml'=>"http://www.w3.org/XML/1998/namespace", - ), - ), - 'dc:title'=>array( - 'type'=>'Text', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'dcterms:alternative'=>array( - 'type'=>'Text', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'dcterms:extent'=>array( - 'type'=>'Time', - 'regexp'=>'^\d{2}:\d{2}:\d{2}.\d{6}$', - ), - 'dc:creator'=>array( - 'type'=>'Text', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'dc:description'=>array( - 'type'=>'Longtext', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'playlength'=>array( - 'type'=>'Time', - 'regexp'=>'^((\d{2}:)?\d{2}:)?\d{1,2}(.\d{6})?$', - ), - 'id'=>array( - 'type'=>'Attribute', - 'regexp'=>'^[0-9a-f]{16}$', - ), - 'fadeIn'=>array( - 'type'=>'Attribute', - 'regexp'=>'^((\d{2}:)?\d{2}:)?\d{1,2}(.\d{6})?$', - ), - 'fadeOut'=>array( - 'type'=>'Attribute', - 'regexp'=>'^((\d{2}:)?\d{2}:)?\d{1,2}(.\d{6})?$', - ), - 'ls:filename'=>array( - 'type'=>'Text', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:mtime'=>array( - 'type'=>'Int', -// 'regexp'=>'^\d{4}(-\d{2}(-\d{2}(T\d{2}:\d{2}(:\d{2}\.\d+)?(Z)|([\+\-]?\d{2}:\d{2}))?)?)?$', - ), -/* - ''=>array( - 'childs'=>array(''), - 'attrs'=>array('implied'=>array()), - ), -*/ -); - diff --git a/application/models/tests/PlaylistTests.php b/application/models/tests/PlaylistTests.php deleted file mode 100644 index 834efb506..000000000 --- a/application/models/tests/PlaylistTests.php +++ /dev/null @@ -1,181 +0,0 @@ -getMessage()." ".$CC_DBC->getUserInfo()."\n"; - exit(1); -} -$CC_DBC->setFetchMode(DB_FETCHMODE_ASSOC); - -class PlaylistTests extends PHPUnit_TestCase { - - private $greenbox; - private $storedFile; - private $storedFile2; - - function __construct($name) { - parent::__construct($name); - } - - function setup() { - global $CC_CONFIG, $CC_DBC; - $this->greenbox = new GreenBox(); - - // Add a file - $values = array("filepath" => dirname(__FILE__)."/test10001.mp3"); - $this->storedFile = StoredFile::Insert($values, false); - - // Add a file - $values = array("filepath" => dirname(__FILE__)."/test10002.mp3"); - $this->storedFile2 = StoredFile::Insert($values, false); - - } - - function testGBCreatePlaylist() { - - $pl = new Playlist(); - $pl_id = $pl->create("Playlist UnitTest: create ".date("g:i a")); - - if (!is_int($pl_id)) { - $this->fail("problems creating playlist."); - return; - } - } - - function testGBLock() { - $pl = new Playlist(); - $pl_id = $pl->create("Playlist Metadata: lock ".date("g:i a")); - - $sessid = Alib::Login('root', 'q'); - - $res = $this->greenbox->lockPlaylistForEdit($pl_id, $sessid); - - if($res !== TRUE) { - $this->fail("problems locking playlist for editing."); - return; - } - } - - function testGBUnLock() { - $pl = new Playlist(); - $pl_id = $pl->create("Playlist UnitTest: unlock ".date("g:i a")); - - $sessid = Alib::Login('root', 'q'); - - $this->greenbox->lockPlaylistForEdit($pl_id, $sessid); - $res = $this->greenbox->releaseLockedPlaylist($pl_id, $sessid); - - if($res !== TRUE) { - $this->fail("problems unlocking playlist."); - return; - } - } - - function testGBSetPLMetaData() { - $pl = new Playlist(); - $pl_id = $pl->create("Playlist UnitTest: Set Metadata ".date("g:i a")); - - $res = $this->greenbox->setPLMetadataValue($pl_id, "dc:title", "Playlist Unit Test: Updated Title ".date("g:i a")); - - if($res !== TRUE) { - $this->fail("problems setting playlist metadata."); - return; - } - } - - function testGBGetPLMetaData() { - $pl = new Playlist(); - $name = "Playlist UnitTest: GetMetadata ".date("g:i a"); - $pl_id = $pl->create($name); - - $res = $this->greenbox->getPLMetadataValue($pl_id, "dc:title"); - - if($res !== $name) { - $this->fail("problems getting playlist metadata."); - return; - } - } - - function testAddAudioClip() { - - $pl = new Playlist(); - $pl_id = $pl->create("Playlist Unit Test ". date("g:i a")); - $res = $this->greenbox->addAudioClipToPlaylist($pl_id, $this->storedFile->getId()); - if($res !== TRUE) { - $this->fail("problems adding audioclip to playlist."); - return; - } - - $res = $this->greenbox->addAudioClipToPlaylist($pl_id, $this->storedFile2->getId()); - if($res !== TRUE) { - $this->fail("problems adding audioclip 2 to playlist."); - return; - } - } - - function testMoveAudioClip() { - $pl = new Playlist(); - $pl_id = $pl->create("Playlist Unit Test: Move ". date("g:i a")); - - $this->greenbox->addAudioClipToPlaylist($pl_id, $this->storedFile->getId()); - $this->greenbox->addAudioClipToPlaylist($pl_id, $this->storedFile2->getId()); - - $res = $this->greenbox->moveAudioClipInPlaylist($pl_id, 0, 1); - - if($res !== TRUE) { - $this->fail("problems moving audioclip in playlist."); - return; - } - } - - function testDeleteAudioClip() { - $pl = new Playlist(); - $pl_id = $pl->create("Playlist UnitTest: Delete ".date("g:i a")); - - $this->greenbox->addAudioClipToPlaylist($pl_id, $this->storedFile->getId()); - $res = $this->greenbox->delAudioClipFromPlaylist($pl_id, 0); - - if($res !== TRUE) { - $this->fail("problems deleting audioclip from playlist."); - return; - } - } - - function testFadeInfo() { - $pl = new Playlist(); - $pl_id = $pl->create("Playlist Unit Test: Fade Info " . date("g:i a")); - - $this->greenbox->addAudioClipToPlaylist($pl_id, $this->storedFile2->getId()); - - $res = $this->greenbox->changeFadeInfo($pl_id, 0, '00:00:01.14', null); - - if(!is_array($res) && count($res) !== 2) { - $this->fail("problems setting fade in playlist."); - return; - } - } -} - - diff --git a/application/models/tests/analyze.php b/application/models/tests/analyze.php deleted file mode 100755 index 7e2a9fbac..000000000 --- a/application/models/tests/analyze.php +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/php -q -analyzeFile(); - -echo "r=$r (".gettype($r).")\n"; -if (PEAR::isError($r)) { - echo "ERR: ".$r->getMessage()."\n".$r->getUserInfo()."\n"; -} -if (is_array($r)) { - print_r($r); -} -echo"\n"; diff --git a/application/models/webstreamFormat.php b/application/models/webstreamFormat.php deleted file mode 100644 index 48d6354fa..000000000 --- a/application/models/webstreamFormat.php +++ /dev/null @@ -1,340 +0,0 @@ -'audioClip', - 'audioClip'=>array( - 'childs'=>array( - 'required'=>array('metadata'), - ), - ), - 'metadata'=>array( - 'childs'=>array( - 'required'=>array( - 'dc:title', 'dcterms:extent', 'ls:url' - ), - 'optional'=>array( - 'dc:identifier', - 'dc:creator', 'dc:source', 'ls:genre', - 'ls:year', 'dc:type', 'dc:description', 'dc:format', - 'ls:bpm', 'ls:rating', 'ls:encoded_by', 'ls:track_num', - 'ls:disc_num', 'ls:disc_num', 'dc:publisher', 'ls:composer', - 'ls:bitrate', 'ls:channels', 'ls:samplerate', 'ls:encoder', - 'ls:crc', 'ls:lyrics', 'ls:orchestra', 'ls:conductor', - 'ls:lyricist', 'ls:originallyricist', 'ls:radiostationname', - 'ls:audiofileinfourl', 'ls:artisturl', 'ls:audiosourceurl', - 'ls:radiostationurl', 'ls:buycdurl', 'ls:isrcnumber', - 'ls:catalognumber', 'ls:originalartist', 'dc:rights', - 'ls:license', 'dc:title', 'dcterms:temporal', - 'dcterms:spatial', 'dcterms:entity', 'dc:description', - 'dc:creator', 'dc:subject', 'dc:type', 'dc:format', - 'dc:contributor', 'dc:language', 'dc:rights', - 'dcterms:isPartOf', 'dc:date', - 'dc:publisher', - // extra - 'dcterms:alternative', 'ls:filename', 'ls:mtime', - // added lately by sebastian - 'ls:mood', - ), - ), - 'namespaces'=>array( - 'dc'=>"http://purl.org/dc/elements/1.1/", - 'dcterms'=>"http://purl.org/dc/terms/", - 'xbmf'=>"http://www.streamonthefly.org/xbmf", - 'xsi'=>"http://www.w3.org/2001/XMLSchema-instance", - 'xml'=>"http://www.w3.org/XML/1998/namespace", - ), - ), - 'ls:url'=>array( - 'type'=>'URL', - ), - 'dc:identifier'=>array( - 'type'=>'Text', - 'auto'=>TRUE, - ), - 'dc:title'=>array( - 'type'=>'Text', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'dcterms:alternative'=>array( - 'type'=>'Text', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'dcterms:extent'=>array( - 'type'=>'Time', - 'regexp'=>'^\d{2}:\d{2}:\d{2}.\d{6}$', - ), - 'dc:creator'=>array( - 'type'=>'Text', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'dc:source'=>array( - 'type'=>'Text', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:genre'=>array( - 'type'=>'Menu', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:year'=>array( - 'type'=>'Menu', - 'area'=>'Music', - ), - 'dc:type'=>array( - 'type'=>'Menu', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'dc:description'=>array( - 'type'=>'Longtext', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'dc:format'=>array( - 'type'=>'Menu', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:bpm'=>array( - 'type'=>'Number', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:rating'=>array( - 'type'=>'Number', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:encoded_by'=>array( - 'type'=>'Text', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:track_num'=>array( - 'type'=>'Menu', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:disc_num'=>array( - 'type'=>'Menu', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:disc_num'=>array( - 'type'=>'Menu', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'dc:publisher'=>array( - 'type'=>'Text', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:composer'=>array( - 'type'=>'Text', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:bitrate'=>array( - 'type'=>'Number', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:channels'=>array( - 'type'=>'Menu', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:samplerate'=>array( - 'type'=>'Menu', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:encoder'=>array( - 'type'=>'Text', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:crc'=>array( - 'type'=>'Number', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:lyrics'=>array( - 'type'=>'Longtext', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:orchestra'=>array( - 'type'=>'Text', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:conductor'=>array( - 'type'=>'Text', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:lyricist'=>array( - 'type'=>'Text', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:originallyricist'=>array( - 'type'=>'Text', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:radiostationname'=>array( - 'type'=>'Text', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:audiofileinfourl'=>array( - 'type'=>'URL', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:artisturl'=>array( - 'type'=>'URL', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:audiosourceurl'=>array( - 'type'=>'URL', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:radiostationurl'=>array( - 'type'=>'URL', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:buycdurl'=>array( - 'type'=>'URL', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:isrcnumber'=>array( - 'type'=>'Number', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:catalognumber'=>array( - 'type'=>'Number', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:originalartist'=>array( - 'type'=>'Text', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'dc:rights'=>array( - 'type'=>'Text', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:license'=>array( - 'type'=>'Text', - 'area'=>'Music', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'dc:title'=>array( - 'type'=>'Text', - 'area'=>'Talk', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'dcterms:temporal'=>array( - 'type'=>'Time/Date', - 'area'=>'Talk', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'dcterms:spatial'=>array( - 'type'=>'Menu', - 'area'=>'Talk', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'dcterms:entity'=>array( - 'type'=>'Text', - 'area'=>'Talk', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'dc:description'=>array( - 'type'=>'Longtext', - 'area'=>'Talk', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'dc:creator'=>array( - 'type'=>'Menu', - 'area'=>'Talk', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'dc:subject'=>array( - 'type'=>'Text', - 'area'=>'Talk', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'dc:type'=>array( - 'type'=>'Menu', - 'area'=>'Talk', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'dc:format'=>array( - 'type'=>'Menu', - 'area'=>'Talk', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'dc:contributor'=>array( - 'type'=>'Text', - 'area'=>'Talk', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'dc:language'=>array( - 'type'=>'Menu', - 'area'=>'Talk', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'dc:rights'=>array( - 'type'=>'Menu', - 'area'=>'Talk', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'dcterms:isPartOf'=>array( - 'type'=>'Text', - 'area'=>'Talk', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'dc:date'=>array( - 'type'=>'Date', - 'area'=>'Talk', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'dc:publisher'=>array( - 'type'=>'Text', - 'area'=>'Talk', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:filename'=>array( - 'type'=>'Text', - 'attrs'=>array('implied'=>array('xml:lang')), - ), - 'ls:mtime'=>array( - 'type'=>'Int', -// 'regexp'=>'^\d{4}(-\d{2}(-\d{2}(T\d{2}:\d{2}(:\d{2}\.\d+)?(Z)|([\+\-]?\d{2}:\d{2}))?)?)?$', - ), -/* - ''=>array( - 'type'=>'', - 'area'=>'', - 'attrs'=>array(), - ), -*/ -); - diff --git a/application/models/xmlrpc/XR_LocStor.php b/application/models/xmlrpc/XR_LocStor.php deleted file mode 100644 index 2208f07d5..000000000 --- a/application/models/xmlrpc/XR_LocStor.php +++ /dev/null @@ -1,3833 +0,0 @@ - - *
  • version : string
  • - * - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_getVersion: - * <message from lower layer>
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see Subjects::getVersion - */ -// public function xr_getVersion($input) -// { -// list($ok, $r) = XR_LocStor::xr_getParams($input); -// if (!$ok) { -// return $r; -// } -// $res = $this->getVersion(); -// if (PEAR::isError($res)) { -// return new XML_RPC_Response(0, 805, -// "xr_getVersion: ".$res->getMessage(). -// " ".$res->getUserInfo() -// ); -// } -// return new XML_RPC_Response( -// XML_RPC_encode(array('version'=>$res)) -// ); -// } - public function xr_getVersion() - { -// list($ok, $r) = XR_LocStor::xr_getParams($input); -// if (!$ok) { -// return $r; -// } - $res = $this->getVersion(); -// if (PEAR::isError($res)) { -// return new XML_RPC_Response(0, 805, -// "xr_getVersion: ".$res->getMessage(). -// " ".$res->getUserInfo() -// ); -// } - return new XML_RPC_Response( - XML_RPC_encode(array('version'=>$res)) - ); - } - - - /* ------------------------------------------------------- authentication */ - /** - * Checks the login name and password of the user and return - * true if login data are correct, othervise return false. - * - * The XML-RPC name of this method is "locstor.authenticate". - * - * Input parameters: XML-RPC struct with the following fields: - *
      - *
    • login : string - login name
    • - *
    • pass : string - password
    • - *
    - * On success, returns a XML-RPC struct with single field: - *
      - *
    • authenticate : boolean
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 804 - xr_authenticate: database error
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see Subjects::authenticate - */ - public function xr_authenticate($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - $res = $this->authenticate($r['login'], $r['pass']); - if (PEAR::isError($res)) { - return new XML_RPC_Response(0, 804, - "xr_authenticate: ".$res->getMessage(). - " ".$res->getUserInfo() - ); - } - $retval = ($res !== FALSE); - return new XML_RPC_Response( - XML_RPC_encode(array('authenticate'=>$retval)) - ); - } - - - /** - * Checks the login name and password of the user. If the login is - * correct, a new session ID string is generated, to be used in all - * subsequent XML-RPC calls as the "sessid" field of the - * parameters. - * - * The XML-RPC name of this method is "locstor.login". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • login : string - login name
    • - *
    • pass : string - password
    • - *
    - * - * On success, returns a XML-RPC struct with single field: - *
      - *
    • sessid : string - the newly generated session ID
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 802 - xr_login: login failed - - * incorrect username or password.
    • - *
    • 804 - xr_login:: database error
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see Alib::login - */ - public function xr_login($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - $res = Alib::Login($r['login'], $r['pass']); - if (PEAR::isError($res)) { - return new XML_RPC_Response(0, 804, - "xr_login: ".$res->getMessage(). - " ".$res->getUserInfo() - ); - } - if ($res === FALSE) { - return new XML_RPC_Response(0, 802, - "xr_login: login failed - incorrect username or password." - ); - } else { - return new XML_RPC_Response(XML_RPC_encode(array('sessid'=>$res))); - } - } - - /** - * Logout, destroy session and return status. - * If session is not valid error message is returned. - * - * The XML-RPC name of this method is "locstor.logout". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • sessid : string - session id
    • - *
    - * - * On success, returns a XML-RPC struct with single field: - *
      - *
    • status : boolean - TRUE
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 803 - xr_logout: logout failed - not logged.
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - */ - public function xr_logout($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - $res = Alib::Logout($r['sessid']); - if (PEAR::isError($res)) { - return new XML_RPC_Response(0, 803, - "xr_logout: ".$res->getMessage(). - " ".$res->getUserInfo() - ); - } - return new XML_RPC_Response(XML_RPC_encode(array('status'=>$res))); - } - - /* ---------------------------------------------------------------- store */ - /** - * Open writable URL for store new AudioClip or replace existing one. - * Writing to returned URL is possible using HTTP PUT method - * (as e.g. curl -T <filename> command does) - * - * The XML-RPC name of this method is "locstor.storeAudioClipOpen". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • sessid : string - session id
    • - *
    • gunid : string - global unique id of AudioCLip, - * if gunid is empty string, new one is generated - * (returned by subsequent storeAudioClipClose call) - *
    • - *
    • metadata : string - metadata XML string - * (as defined in Airtime::Core::AudioClip Class Reference, - * examples are in storageServer/var/tests/*.xml) - *
    • - *
    • fname : string - human readable mnemonic file name - * with extension corresponding to filetype
    • - *
    • chsum : string - md5 checksum of media file
    • - *
    - * - * On success, returns a XML-RPC struct: - *
      - *
    • url : string - writable URL for HTTP PUT
    • - *
    • token : string - access token
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_storeAudioClipOpen: - * <message from lower layer>
    • - *
    • 888 - If the file being uploaded is a duplicate of - * a file already in the system.
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see LocStor::storeAudioClipOpen - */ - public function xr_storeAudioClipOpen($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - $res = $this->storeAudioClipOpen($r['sessid'], $r['gunid'], - $r['metadata'], $r['fname'], $r['chsum']); - if (PEAR::isError($res)) { - $code = 805; - if ($res->getCode() == 888) { - $code = 888; - } - return new XML_RPC_Response(0, $code, - "xr_storeAudioClipOpen: ".$res->getMessage(). - " ".$res->getUserInfo() - ); - } - return new XML_RPC_Response(XML_RPC_encode($res)); - } - - /** - * Close writable URL for store new AudioClip or replace existing one. - * - * The XML-RPC name of this method is "locstor.storeAudioClipClose". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • sessid : string - session id
    • - *
    • token : string - access token
    • - *
    - * - * On success, returns a XML-RPC struct with single field: - *
      - *
    • gunid : string - gunid of stored file
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_storeAudioClipClose: - * <message from lower layer>
    • - *
    • 850 - wrong 1st parameter, struct expected.
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see LocStor::storeAudioClipClose - */ - public function xr_storeAudioClipClose($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - $res = $this->storeAudioClipClose($r['sessid'], $r['token']); - if (PEAR::isError($res)) { - $ec0 = intval($res->getCode()); - $ec = ($ec0 == GBERR_TOKEN ? 800+$ec0 : 805 ); - return new XML_RPC_Response(0, $ec, - "xr_storeAudioClipClose: ".$res->getMessage(). - " ".$res->getUserInfo() - ); - } - return new XML_RPC_Response(XML_RPC_encode(array('gunid'=>$res))); - } - - /** - * Store audio stream identified by URL - no raw audio data - * - * The XML-RPC name of this method is "locstor.storeWebstream". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • sessid : string - session id
    • - *
    • gunid : string - global unique id of AudioCLip
    • - *
    • metadata : string - metadata XML string
    • - *
    • fname : string - human readable mnemonic file name - * with extension corresponding to filetype
    • - *
    • url : string - URL of the webstrea,
    • - *
    - * - * On success, returns a XML-RPC struct with single field: - *
      - *
    • gunid : string - gunid of stored file
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_storeWebstream: - * <message from lower layer>
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see LocStor::storeWebstream - */ - public function xr_storeWebstream($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - $res = $this->storeWebstream( - $r['sessid'], $r['gunid'], $r['metadata'], $r['fname'], $r['url'] - ); - if (PEAR::isError($res)) { - return new XML_RPC_Response(0, 805, - "xr_storeWebstream: ".$res->getMessage(). - " ".$res->getUserInfo() - ); - } - return new XML_RPC_Response(XML_RPC_encode(array('gunid'=>$res))); - } - - /* ------------------------------------------------ access raw audio data */ - /** - * Make access to audio clip. - * - * The XML-RPC name of this method is "locstor.accessRawAudioData". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • sessid : string - session id
    • - *
    • gunid : string - global unique id of AudioClip
    • - *
    - * - * On success, returns a XML-RPC struct: - *
      - *
    • url : string - local access url
    • - *
    • token : string - access token
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_accessRawAudioData: - * <message from lower layer>
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see LocStor::accessRawAudioData - */ - public function xr_accessRawAudioData($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - $res = $this->accessRawAudioData($r['sessid'], $r['gunid']); - if (PEAR::isError($res)) { - return new XML_RPC_Response(0, 805, - "xr_accessRawAudioData: ".$res->getMessage(). - " ".$res->getUserInfo() - ); - } - return new XML_RPC_Response(XML_RPC_encode($res)); - } - - /** - * Release access to audio clip - * - * The XML-RPC name of this method is "locstor.releaseRawAudioData". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • token : string - access token - * returned by locstor.accessRawAudioData
    • - *
    - * - * On success, returns a XML-RPC struct with single field: - *
      - *
    • status : boolean
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_releaseRawAudioData: - * <message from lower layer>
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see LocStor::releaseRawAudioData - */ - public function xr_releaseRawAudioData($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - $res = $this->releaseRawAudioData(NULL, $r['token']); - if (PEAR::isError($res)) { - return new XML_RPC_Response(0, 805, - "xr_releaseRawAudioData: ".$res->getMessage(). - " ".$res->getUserInfo() - ); - } - return new XML_RPC_Response(XML_RPC_encode(array('status'=>$res))); - } - - /* ---------------------------------------------- download raw audio data */ - /** - * Create downlodable URL for stored file - * - * The XML-RPC name of this method is "locstor.downloadRawAudioDataOpen". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • sessid : string - session id
    • - *
    • gunid : string - global unique id of AudioClip
    • - *
    - * - * On success, returns a XML-RPC struct: - *
      - *
    • url : string - downloadable url
    • - *
    • token : string - download token
    • - *
    • chsum : string - md5 checksum
    • - *
    • size : int - file size
    • - *
    • filename : string - human readable mnemonic file name
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_accessRawAudioDataOpen: - * <message from lower layer>
    • - *
    • 847 - invalid gunid.
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see LocStor::downloadRawAudioDataOpen - */ - public function xr_downloadRawAudioDataOpen($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - $res = $this->downloadRawAudioDataOpen($r['sessid'], $r['gunid']); - if (PEAR::isError($res)) { - $ec0 = intval($res->getCode()); - $ec = ($ec0 == GBERR_NOTF ? 800+$ec0 : 805 ); - return new XML_RPC_Response(0, $ec, - "xr_downloadRawAudioDataOpen: ".$res->getMessage(). - " ".$res->getUserInfo() - ); - } - return new XML_RPC_Response(XML_RPC_encode($res)); - } - - /** - * Delete downlodable URL with media file. - * - * The XML-RPC name of this method is "locstor.downloadRawAudioDataClose". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • sessid : string - session id
    • - *
    • token : string - download token - * returned by locstor.downloadRawAudioDataOpen
    • - *
    - * - * On success, returns a XML-RPC struct with single field: - *
      - *
    • gunid : string - global unique ID
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_releaseRawAudioDataClose: - * <message from lower layer>
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see LocStor::downloadRawAudioDataClose - */ - public function xr_downloadRawAudioDataClose($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - $res = $this->downloadRawAudioDataClose($r['token']); - if (PEAR::isError($res)) { - return new XML_RPC_Response(0, 805, - "xr_downloadRawAudioDataClose: ".$res->getMessage(). - " ".$res->getUserInfo() - ); - } - return new XML_RPC_Response(XML_RPC_encode(array('gunid'=>$res))); - } - - /* ---------------------------------------------------- download metadata */ - /** - * Create downlodable URL for metadata part of stored file - * - * The XML-RPC name of this method is "locstor.downloadMetadataOpen". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • sessid : string - session id
    • - *
    • gunid : string - global unique id of AudioClip
    • - *
    - * - * On success, returns a XML-RPC struct: - *
      - *
    • url : string - downloadable url
    • - *
    • token : string - download token
    • - *
    • chsum : string - md5 checksum
    • - *
    • filename : string - mnemonic filename
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_downloadMetadataOpen: - * <message from lower layer>
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see LocStor::downloadRawAudioDataOpen - */ - public function xr_downloadMetadataOpen($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - #$this->debugLog("{$r['sessid']}, {$r['gunid']}"); - $res = $this->downloadMetadataOpen($r['sessid'], $r['gunid']); - if (PEAR::isError($res)) { - return new XML_RPC_Response(0, 805, - "xr_downloadMetadataOpen: ".$res->getMessage(). - " ".$res->getUserInfo() - ); - } - return new XML_RPC_Response(XML_RPC_encode($res)); - } - - /** - * Delete downlodable URL with metadata. - * - * The XML-RPC name of this method is "locstor.downloadMetadataClose". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • sessid : string - session id
    • - *
    • token : string - download token - * returned by locstor.downloadRawAudioDataOpen
    • - *
    - * - * On success, returns a XML-RPC struct with single field: - *
      - *
    • gunid : string - global unique ID
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_downloadMetadataClose: - * <message from lower layer>
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see LocStor::downloadRawAudioDataClose - */ - public function xr_downloadMetadataClose($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - $res = $this->downloadMetadataClose($r['token']); - if (PEAR::isError($res)) { - return new XML_RPC_Response(0, 805, - "xr_downloadMetadataClose: ".$res->getMessage(). - " ".$res->getUserInfo() - ); - } - return new XML_RPC_Response(XML_RPC_encode(array('gunid'=>$res))); - } - - /* --------------------------------------------------------------- delete */ - /** - * Delete existing audio clip - DISABLED now! - * - * The XML-RPC name of this method is "locstor.deleteAudioClip". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • sessid : string - session id
    • - *
    • gunid : string - global unique id of AudioCLip
    • - *
    - * - * On success, returns a XML-RPC struct with single field: - *
      - *
    • status : boolean - TRUE
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_deleteAudioClip: - * <message from lower layer>
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see LocStor::deleteAudioClip - */ - public function xr_deleteAudioClip($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - if (!isset($r['forced'])) { - $r['forced']=FALSE; - } - $res = $this->deleteAudioClip($r['sessid'], $r['gunid'], $r['forced']); - if (!$r['forced']) { - return new XML_RPC_Response(0, 805, "xr_deleteAudioClip: method disabled"); - } - if (PEAR::isError($res)) { - return new XML_RPC_Response(0, 805, - "xr_deleteAudioClip: ".$res->getMessage(). - " ".$res->getUserInfo() - ); - } - return new XML_RPC_Response(XML_RPC_encode(array('status'=>$res))); - } - - /*====================================================== playlist methods */ - /** - * Create a new Playlist metafile. - * - * The XML-RPC name of this method is "locstor.createPlaylist". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • sessid : string - session id
    • - *
    • plid : string - global unique id of Playlist
    • - *
    • fname : string - human readable menmonic file name
    • - *
    - * - * On success, returns a XML-RPC struct with single field: - *
      - *
    • plid : string
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_createPlaylist: - * <message from lower layer>
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see LocStor::createPlaylist - */ - public function xr_createPlaylist($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - $res = $this->createPlaylist($r['sessid'], $r['plid'], $r['fname']); - if (PEAR::isError($res)) { - return new XML_RPC_Response(0, 805, - "xr_createPlaylist: ".$res->getMessage(). - " ".$res->getUserInfo() - ); - } - return new XML_RPC_Response(XML_RPC_encode(array('plid'=>$res))); - } - - /** - * Open a Playlist metafile for editing. - * Open readable URL and mark file as beeing edited. - * - * The XML-RPC name of this method is "locstor.editPlaylist". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • sessid : string - session id
    • - *
    • plid : string - global unique id of Playlist
    • - *
    - * - * On success, returns a XML-RPC struct with single field: - *
      - *
    • url : string - readable url
    • - *
    • token : string - playlist token
    • - *
    • chsum : string - md5 checksum
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_editPlaylist: - * <message from lower layer>
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see LocStor::editPlaylist - */ - public function xr_editPlaylist($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - $res = $this->editPlaylist($r['sessid'], $r['plid']); - if (PEAR::isError($res)) { - return new XML_RPC_Response(0, 805, - "xr_editPlaylist: ".$res->getMessage(). - " ".$res->getUserInfo() - ); - } - return new XML_RPC_Response(XML_RPC_encode($res)); - } - - /** - * Store a new Playlist metafile in place of the old one. - * - * The XML-RPC name of this method is "locstor.savePlaylist". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • sessid : string - session id
    • - *
    • token : string - playlist token - * returned by locstor.editPlaylist
    • - *
    • newPlaylist : string - new Playlist in XML string
    • - *
    - * - * On success, returns a XML-RPC struct with single field: - *
      - *
    • plid : string - playlistId
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_savePlaylist: - * <message from lower layer>
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see LocStor::savePlaylist - */ - public function xr_savePlaylist($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - $res = $this->savePlaylist($r['sessid'], $r['token'], $r['newPlaylist']); - if (PEAR::isError($res)) { - return new XML_RPC_Response(0, 805, - "xr_savePlaylist: ".$res->getMessage(). - " ".$res->getUserInfo() - ); - } - return new XML_RPC_Response(XML_RPC_encode(array('plid'=>$res))); - } - - /** - * RollBack playlist changes to the locked state - * - * The XML-RPC name of this method is "locstor.revertEditedPlaylist". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • sessid : string - session id
    • - *
    • token : string - playlist token - * returned by locstor.editPlaylist
    • - *
    - * - * On success, returns a XML-RPC struct with single field: - *
      - *
    • plid : string - playlistId
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_revertEditedPlaylist: - * <message from lower layer>
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see LocStor::revertEditedPlaylist - */ - public function xr_revertEditedPlaylist($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - $res = $this->revertEditedPlaylist($r['token'], $r['sessid']); - if (PEAR::isError($res)) { - return new XML_RPC_Response(0, 805, - "xr_revertEditedPlaylist: ".$res->getMessage(). - " ".$res->getUserInfo() - ); - } - return new XML_RPC_Response(XML_RPC_encode(array('plid'=>$res))); - } - - /* ------------------------------------------------------- delete playlist*/ - /** - * Delete a Playlist metafile - DISABLED now! - * - * The XML-RPC name of this method is "locstor.deletePlaylist". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • sessid : string - session id
    • - *
    • plid : string - global unique id of Playlist
    • - *
    - * - * On success, returns a XML-RPC struct with single field: - *
      - *
    • status : boolean - TRUE
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_deletePlaylist: - * <message from lower layer>
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see LocStor::deletePlaylist - */ - public function xr_deletePlaylist($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - if (!isset($r['forced'])) { - $r['forced']=FALSE; - } - $res = $this->deletePlaylist($r['sessid'], $r['plid'], $r['forced']); - if (! $r['forced']) { - return new XML_RPC_Response(0, 805,"xr_deletePlaylist: method disabled"); - } - if (PEAR::isError($res)) { - $ec0 = intval($res->getCode()); - $ec = ($ec0 == GBERR_FILENEX ? 800+$ec0 : 805 ); - return new XML_RPC_Response(0, $ec, - "xr_deletePlaylist: ".$res->getMessage(). - " ".$res->getUserInfo() - ); - } - return new XML_RPC_Response(XML_RPC_encode(array('status'=>$res))); - } - - /* ------------------------------------------------------- access playlist*/ - /** - * Access (read) a Playlist metafile. - * - * The XML-RPC name of this method is "locstor.accessPlaylist". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • sessid : string - session id
    • - *
    • plid : string - global unique id of Playlist
    • - *
    • recursive : boolean - flag for recursive access content - * inside playlist (default: false)
    • - *
    - * - * On success, returns an XML-RPC struct with the following fields: - *
      - *
    • url : string - readable url of accessed playlist in - * XML format
    • - *
    • token : string - playlist token
    • - *
    • chsum : string - md5 checksum
    • - *
    • content: array of structs - recursive access (optional)
    • - *
    - * - * The content field contains a struct for each playlist - * element contained in the playlist. For audio clips, this struct is - * of type {url, token}; for sub-playlists, it is of type - * {url, token, chsum, content}. - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_accessPlaylist: - * <message from lower layer>
    • - *
    • 847 - invalid plid.
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see LocStor::accessPlaylist - */ - public function xr_accessPlaylist($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - if (!isset($r['recursive']) || is_null($r['recursive'])) { - $r['recursive']=FALSE; - } - $res = $this->accessPlaylist($r['sessid'], $r['plid'], (boolean)$r['recursive']); - if (PEAR::isError($res)) { - $ec0 = intval($res->getCode()); - $ec = ($ec0 == GBERR_NOTF ? 800+$ec0 : 805 ); - return new XML_RPC_Response(0, $ec, - "xr_accessPlaylist: ".$res->getMessage(). - " ".$res->getUserInfo() - ); - } - return new XML_RPC_Response(XML_RPC_encode($res)); - } - - /** - * Release the resources obtained earlier by accessPlaylist(). - * - * The XML-RPC name of this method is "locstor.releasePlaylist". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • token : string - playlist token - * returned by locstor.accessPlaylist
    • - *
    • recursive : boolean - flag for recursive release content - * accessed by recursive accessPlaylist - * (ignored now - true forced)
    • - *
    - * - * On success, returns a XML-RPC struct with single field: - *
      - *
    • plid : string - playlist ID
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_releasePlaylist: - * <message from lower layer>
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see LocStor::releasePlaylist - */ - public function xr_releasePlaylist($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - //if (!isset($r['recursive']) || is_null($r['recursive'])) $r['recursive']=FALSE; - $res = $this->releasePlaylist(NULL, $r['token'], TRUE); - if (PEAR::isError($res)) { - return new XML_RPC_Response(0, 805, - "xr_releasePlaylist: ".$res->getMessage(). - " ".$res->getUserInfo() - ); - } - return new XML_RPC_Response(XML_RPC_encode(array('plid'=>$res))); - } - - /* -------------------------------------------------------- playlist info */ - /** - * Check whether a Playlist metafile with the given playlist ID exists. - * - * The XML-RPC name of this method is "locstor.existsPlaylist". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • sessid : string - session id
    • - *
    • plid : string - global unique id of Playlist
    • - *
    - * - * On success, returns a XML-RPC struct with single field: - *
      - *
    • exists : boolean
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_existsPlaylist: - * <message from lower layer>
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see LocStor::existsPlaylist - */ - public function xr_existsPlaylist($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - $res = $this->existsPlaylist($r['sessid'], $r['plid']); - if (PEAR::isError($res)) { - return new XML_RPC_Response(0, 805, - "xr_existsPlaylist: ".$res->getMessage(). - " ".$res->getUserInfo() - ); - } - return new XML_RPC_Response(XML_RPC_encode(array('exists'=>$res))); - } - - /** - * Check whether a Playlist metafile with the given playlist ID - * is available for editing, i.e., exists and is not marked as - * beeing edited. - * - * The XML-RPC name of this method is "locstor.playlistIsAvailable". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • sessid : string - session id
    • - *
    • plid : string - global unique id of Playlist
    • - *
    - * - * On success, returns a XML-RPC struct with single field: - *
      - *
    • available : boolean
    • - *
    • ownerid : int - local user id
    • - *
    • ownerlogin : string - local username
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_playlistIsAvailable: - * <message from lower layer>
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see LocStor::playlistIsAvailable - */ - public function xr_playlistIsAvailable($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - $res = $this->playlistIsAvailable($r['sessid'], $r['plid'], TRUE); - $ownerId = ($res === TRUE ? NULL : $res); - $ownerLogin = (is_null($ownerId) ? NULL : Subjects::GetSubjName($ownerId)); - if (PEAR::isError($res)) { - return new XML_RPC_Response(0, 805, - "xr_playlistIsAvailable: ".$res->getMessage(). - " ".$res->getUserInfo() - ); - } - return new XML_RPC_Response(XML_RPC_encode(array( - 'available' => ($res === TRUE), - 'ownerid' => $ownerId, - 'ownerlogin' => $ownerLogin, - ))); - } - - /* ------------------------------------------------------ export playlist */ - /** - * Create a tarfile with playlist export - playlist and all matching - * sub-playlists and media files (if desired) - * - * The XML-RPC name of this method is "locstor.exportPlaylistOpen". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • sessid : string - session id
    • - *
    • plids : array of strings - global unique IDs of Playlists
    • - *
    • type : string - playlist format, values: lspl | smil
    • - *
    • standalone : boolean - if only playlist should be exported or - * with all related files
    • - *
    - * - * On success, returns a XML-RPC struct with following fields: - *
      - *
    • url : string - readable url
    • - *
    • token : string - access token
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_exportPlaylistOpen: - * <message from lower layer>
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see LocStor::exportPlaylistOpen - */ - public function xr_exportPlaylistOpen($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - if (!isset($r['standalone']) || empty($r['standalone'])) { - $r['standalone']=FALSE; - } - $res = $this->exportPlaylistOpen( - $r['sessid'], $r['plids'], $r['type'], $r['standalone'] - ); - if (PEAR::isError($res)) { - return new XML_RPC_Response(0, 805, - "xr_exportPlaylistOpen: ".$res->getMessage(). - " ".$res->getUserInfo() - ); - } - return new XML_RPC_Response(XML_RPC_encode(array( - 'url' => $res['url'], - 'token' => $res['token'], - ))); - } - - /** - * Close playlist export previously opened by the exportPlaylistOpen method - * - * The XML-RPC name of this method is "locstor.exportPlaylistClose". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • token : string - access token
    • - *
    - * - * On success, returns a XML-RPC struct with single field: - *
      - *
    • status : boolean - status/li> - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_exportPlaylistClose: - * <message from lower layer>
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see LocStor::exportPlaylistClose - */ - public function xr_exportPlaylistClose($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - $res = $this->exportPlaylistClose($r['token']); - if (PEAR::isError($res)) { - return new XML_RPC_Response(0, 805, - "xr_exportPlaylistClose: ".$res->getMessage(). - " ".$res->getUserInfo() - ); - } - return new XML_RPC_Response(XML_RPC_encode(array('status'=>TRUE))); - } - - /* ------------------------------------------------------ import playlist */ - /** - * Open writable URL for import playlist in LS Archive format - * - * The XML-RPC name of this method is "locstor.importPlaylistOpen". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • sessid : string - session id
    • - *
    • chsum : string - md5 checksum of imported file
    • - *
    - * - * On success, returns a XML-RPC struct with following fields: - *
      - *
    • url : string - writable url
    • - *
    • token : string - PUT token
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_importPlaylistOpen: - * <message from lower layer>
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see LocStor::importPlaylistOpen - */ - public function xr_importPlaylistOpen($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - $res = $this->importPlaylistOpen($r['sessid'], $r['chsum']); - if (PEAR::isError($res)) { - return new XML_RPC_Response(0, 805, - "xr_importPlaylistOpen: ".$res->getMessage(). - " ".$res->getUserInfo() - ); - } - return new XML_RPC_Response(XML_RPC_encode(array( - 'url'=>$res['url'], - 'token'=>$res['token'], - ))); - } - - /** - * Open writable URL for import playlist in LS Archive format - * - * The XML-RPC name of this method is "locstor.importPlaylistClose". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • token : string - access token
    • - *
    - * - * On success, returns a XML-RPC struct with following fields: - *
      - *
    • gunid : string - global id
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_importPlaylistClose: - * <message from lower layer>
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see LocStor::importPlaylistClose - */ - public function xr_importPlaylistClose($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - $res = $this->importPlaylistClose($r['token']); - if (PEAR::isError($res)) { - return new XML_RPC_Response(0, 805, - "xr_importPlaylistClose: ".$res->getMessage(). - " ".$res->getUserInfo() - ); - } - return new XML_RPC_Response(XML_RPC_encode(array( - 'gunid'=>$res, - ))); - } - - /* ---------------------------------------------- render playlist to file */ - /** - * Render playlist to ogg file (open handle) - * - * The XML-RPC name of this method is "locstor.renderPlaylistToFileOpen". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • sessid : string - session id
    • - *
    • plid : string - playlist gunid
    • - *
    - * - * On success, returns a XML-RPC struct with following fields: - *
      - *
    • token : string - render token
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_renderPlaylistToFileOpen: - * <message from lower layer>
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see LocStor::renderPlaylistToFileOpen - */ - public function xr_renderPlaylistToFileOpen($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - $res = $this->renderPlaylistToFileOpen($r['sessid'], $r['plid']); - if (PEAR::isError($res)) { - return new XML_RPC_Response(0, 805, - "xr_renderPlaylistToFileOpen: ".$res->getMessage(). - " ".$res->getUserInfo() - ); - } - return new XML_RPC_Response(XML_RPC_encode(array( - 'token'=>$res['token'], - ))); - } - - /** - * Render playlist to ogg file (check results) - * - * The XML-RPC name of this method is "locstor.renderPlaylistToFileCheck". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • token : string - render token
    • - *
    - * - * On success, returns a XML-RPC struct with following fields: - *
      - *
    • status : string - success | working | fault
    • - *
    • url : string - readable url
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_renderPlaylistToFileCheck: - * <message from lower layer>
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see LocStor::renderPlaylistToFileCheck - */ - public function xr_renderPlaylistToFileCheck($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - $res = $this->renderPlaylistToFileCheck($r['token']); - if (PEAR::isError($res)) { - return new XML_RPC_Response(0, 805, - "xr_renderPlaylistToFileCheck: ".$res->getMessage(). - " ".$res->getUserInfo() - ); - } - return new XML_RPC_Response(XML_RPC_encode(array( - 'url'=>$res['url'], - 'status'=>$res['status'], - ))); - } - - /** - * Render playlist to ogg file (close handle) - * - * The XML-RPC name of this method is "locstor.renderPlaylistToFileClose". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • token : string - render token
    • - *
    - * - * On success, returns a XML-RPC struct with following fields: - *
      - *
    • status : boolean
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_renderPlaylistToFileClose: - * <message from lower layer>
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see LocStor::renderPlaylistToFileClose - */ - public function xr_renderPlaylistToFileClose($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - $res = $this->renderPlaylistToFileClose($r['token']); - if (PEAR::isError($res)) { - return new XML_RPC_Response(0, 805, - "xr_renderPlaylistToFileClose: ".$res->getMessage(). - " ".$res->getUserInfo() - ); - } - return new XML_RPC_Response(XML_RPC_encode(array( - 'status'=>$res['status'], - ))); - } - - /* ------------------------------------------- render playlist to storage */ - /** - * Render playlist to storage media clip (open handle) - * - * The XML-RPC name of this method is "locstor.renderPlaylistToStorageOpen". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • sessid : string - session id
    • - *
    • plid : string - playlist gunid
    • - *
    - * - * On success, returns a XML-RPC struct with following fields: - *
      - *
    • token : string - render token
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_renderPlaylistToStorageOpen: - * <message from lower layer>
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see LocStor::renderPlaylistToStorageOpen - */ - public function xr_renderPlaylistToStorageOpen($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - $res = $this->renderPlaylistToStorageOpen($r['sessid'], $r['plid']); - if (PEAR::isError($res)) { - return new XML_RPC_Response(0, 805, - "xr_renderPlaylistToStorageOpen: ".$res->getMessage(). - " ".$res->getUserInfo() - ); - } - return new XML_RPC_Response(XML_RPC_encode(array( - 'token'=>$res['token'], - ))); - } - - /** - * Render playlist to storage media clip (check results) - * - * The XML-RPC name of this method is "locstor.renderPlaylistToStorageCheck". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • token : string - render token
    • - *
    - * - * On success, returns a XML-RPC struct with following fields: - *
      - *
    • status : string - success | working | fault
    • - *
    • gunid : string - gunid of result file
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_renderPlaylistToStorageCheck: - * <message from lower layer>
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see LocStor::renderPlaylistToStorageCheck - */ - public function xr_renderPlaylistToStorageCheck($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - $res = $this->renderPlaylistToStorageCheck($r['token']); - if (PEAR::isError($res)) { - return new XML_RPC_Response(0, 805, - "xr_renderPlaylistToStorageCheck: ".$res->getMessage(). - " ".$res->getUserInfo() - ); - } - return new XML_RPC_Response(XML_RPC_encode(array( - 'status'=>$res['status'], - 'gunid'=>$res['gunid'], - ))); - } - - /* ----------------------------------------------- render playlist to RSS */ - /** - * Render playlist to RSS file (open handle) - * - * The XML-RPC name of this method is "locstor.renderPlaylistToRSSOpen". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • sessid : string - session id
    • - *
    • plid : string - playlist gunid
    • - *
    - * - * On success, returns a XML-RPC struct with following fields: - *
      - *
    • token : string - render token
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_renderPlaylistToRSSOpen: - * <message from lower layer>
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see LocStor::renderPlaylistToRSSOpen - */ - public function xr_renderPlaylistToRSSOpen($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - $res = $this->renderPlaylistToRSSOpen($r['sessid'], $r['plid']); - if (PEAR::isError($res)) { - return new XML_RPC_Response(0, 805, - "xr_renderPlaylistToRSSOpen: ".$res->getMessage(). - " ".$res->getUserInfo() - ); - } - return new XML_RPC_Response(XML_RPC_encode(array( - 'token'=>$res['token'], - ))); - } - - /** - * Render playlist to RSS file (check results) - * - * The XML-RPC name of this method is "locstor.renderPlaylistToRSSCheck". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • token : string - render token
    • - *
    - * - * On success, returns a XML-RPC struct with following fields: - *
      - *
    • status : string - success | working | fault
    • - *
    • url : string - readable url
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_renderPlaylistToRSSCheck: - * <message from lower layer>
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see LocStor::renderPlaylistToRSSCheck - */ - public function xr_renderPlaylistToRSSCheck($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - $res = $this->renderPlaylistToRSSCheck($r['token']); - if (PEAR::isError($res)) { - return new XML_RPC_Response(0, 805, - "xr_renderPlaylistToRSSCheck: ".$res->getMessage(). - " ".$res->getUserInfo() - ); - } - return new XML_RPC_Response(XML_RPC_encode(array( - 'url'=>$res['url'], - 'status'=>$res['status'], - ))); - } - - /** - * Render playlist to RSS file (close handle) - * - * The XML-RPC name of this method is "locstor.renderPlaylistToRSSClose". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • token : string - render token
    • - *
    - * - * On success, returns a XML-RPC struct with following fields: - *
      - *
    • status : boolean
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_renderPlaylistToRSSClose: - * <message from lower layer>
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see LocStor::renderPlaylistToRSSClose - */ - public function xr_renderPlaylistToRSSClose($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - $res = $this->renderPlaylistToRSSClose($r['token']); - if (PEAR::isError($res)) { - return new XML_RPC_Response(0, 805, - "xr_renderPlaylistToRSSClose: ".$res->getMessage(). - " ".$res->getUserInfo() - ); - } - return new XML_RPC_Response(XML_RPC_encode(array( - 'status'=>$res['status'], - ))); - } - - /*==================================================storage admin methods */ - /* ------------------------------------------------------- backup methods */ - /** - * Create backup of storage (open handle) - * - * The XML-RPC name of this method is "locstor.createBackupOpen". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • sessid : string - session id
    • - *
    • criteria : struct - see search criteria
    • - *
    - * - * On success, returns a XML-RPC struct with following fields: - *
      - *
    • token : string - backup token
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_createBackupOpen: - * <message from lower layer>
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see LocStor::createBackupOpen - */ - public function xr_createBackupOpen($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - -# return new XML_RPC_Response(XML_RPC_encode(var_export($this, TRUE))); - - $res = $this->createBackupOpen($r['sessid'], $r['criteria']); - if (PEAR::isError($res)) { - return new XML_RPC_Response(0, 805, - "xr_createBackupOpen: ".$res->getMessage(). - " ".$res->getUserInfo() - ); - } - return new XML_RPC_Response(XML_RPC_encode(array( - 'token'=>$res['token'], - ))); - } - - /** - * Create backup of storage (check results) - * - * The XML-RPC name of this method is "locstor.createBackupCheck". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • token : string - backup token
    • - *
    - * - * On success, returns a XML-RPC struct with following fields: - *
      - *
    • status : string - success | working | fault
    • - *
    • url : string - readable url
    • - *
    • metafile : string - archive metafile in XML format
    • - *
    • faultString : string - error message - * (use only if status==fault)
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_createBackupCheck: - * <message from lower layer>
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see LocStor::createBackupCheck - */ - //
  • 854 - backup process fault
  • - public function xr_createBackupCheck($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - $res = $this->createBackupCheck($r['token']); - - if (PEAR::isError($res)) { - $ec0 = intval($res->getCode()); - $ec = ($ec0 == GBERR_BGERR ? 800+$ec0 : 805 ); - return new XML_RPC_Response(0, $ec, - "xr_createBackupCheck: ".$res->getMessage(). - " ".$res->getUserInfo() - ); - } - return new XML_RPC_Response(XML_RPC_encode($res)); - } - - /** - * Create backup of storage (list results) - * - * The XML-RPC name of this method is "locstor.createBackupList". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • stat : string - backup status
    • - *
    - * - * On success, returns a XML-RPC array of struct with following fields: - *
      - *
    • status : string - success | working | fault
    • - *
    • url : string - readable url
    • - *
    • metafile : string - archive metafile in XML format
    • - *
    • faultString : string - error message - * (use only if status==fault)
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_createBackupCheck: - * <message from lower layer>
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see LocStor::createBackupCheck - */ - //
  • 854 - backup process fault
  • - public function xr_createBackupList($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - if (!isset($r['stat']) || is_null($r['stat'])) { - $r['stat']=''; - } - $res = $this->createBackupList($r['stat']); - if (PEAR::isError($res)) { - $ec0 = intval($res->getCode()); - $ec = ($ec0 == GBERR_BGERR ? 800+$ec0 : 805 ); - return new XML_RPC_Response(0, $ec, - "xr_createBackupCheck: ".$res->getMessage(). - " ".$res->getUserInfo() - ); - } - return new XML_RPC_Response(XML_RPC_encode($res)); - } - - /** - * Create backup of storage (close handle) - * - * The XML-RPC name of this method is "locstor.createBackupClose". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • token : string - backup token
    • - *
    - * - * On success, returns a XML-RPC struct with following fields: - *
      - *
    • status : boolean
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_createBackupClose: - * <message from lower layer>
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see LocStor::createBackupClose - */ - public function xr_createBackupClose($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - $res = $this->createBackupClose($r['token']); - if (PEAR::isError($res)) { - return new XML_RPC_Response(0, 805, - "xr_createBackupClose: ".$res->getMessage(). - " ".$res->getUserInfo() - ); - } - return new XML_RPC_Response(XML_RPC_encode(array( - 'status'=>$res['status'], - ))); - } - /* ------------------------------------------------------ restore methods */ - /** - * Open restore a backup file - * - * The XML-RPC name of this method is "locstor.restoreBackupOpen". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • sessid : string - session id
    • - *
    • chsum : string - md5 checksum of restore file
    • - *
    - * - * On success, returns a XML-RPC struct with following fields: - *
      - *
    • url : string - writable URL for HTTP PUT
    • - *
    • token : string - PUT token
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_restoreBackupOpen: - * <message from lower layer>
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see LocStor::restoreBackupOpen - */ - public function xr_restoreBackupOpen($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - $res = $this->restoreBackupOpen($r['sessid'], $r['chsum']); - if (PEAR::isError($res)) { - return new XML_RPC_Response(0, 805, - "xr_restoreBackupOpen: ".$res->getMessage(). - " ".$res->getUserInfo() - ); - } - unset($res['fname']); - return new XML_RPC_Response(XML_RPC_encode($res)); - } - - /** - * Close writable URL for restore a backup file and start the restore - * process - * - * The XML-RPC name of this method is "locstor.restoreBackupClosePut". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • sessid : string - session id
    • - *
    • token : string - PUT token
    • - *
    - * - * On success, returns a XML-RPC struct with following fields: - *
      - *
    • token : string - restore token
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_restoreBackupClosePut: - * <message from lower layer>
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see LocStor::restoreBackupClosePut - */ - public function xr_restoreBackupClosePut($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - $res = $this->restoreBackupClosePut($r['sessid'], $r['token']); - if (PEAR::isError($res)) { - return new XML_RPC_Response(0, 805, - "xr_restoreBackupClosePut: ".$res->getMessage(). - " ".$res->getUserInfo() - ); - } - return new XML_RPC_Response(XML_RPC_encode($res)); - } - - /** - * Check the state of restore procedure - * - * The XML-RPC name of this method is "locstor.restoreBackupCheck". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • token : string - restore token
    • - *
    - * - * On success, returns a XML-RPC struct with following fields: - *
      - *
    • status : string - success | working | fault
    • - *
    • faultString: string - description of fault
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_restoreBackupCheck: - * <message from lower layer>
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see LocStor::restoreBackupCheck - */ - public function xr_restoreBackupCheck($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - $res = $this->restoreBackupCheck($r['token']); - if (PEAR::isError($res)) { - return new XML_RPC_Response(0, 805, - "xr_restoreBackupCheck: ".$res->getMessage(). - " ".$res->getUserInfo() - ); - } -# return new XML_RPC_Response(XML_RPC_encode(array( -# 'status'=>$res, -# ))); - return new XML_RPC_Response(XML_RPC_encode($res)); - } - - /** - * Close the restore process - * - * The XML-RPC name of this method is "locstor.restoreBackupClose". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • token : string - restore token
    • - *
    - * - * On success, returns a XML-RPC struct with following fields: - *
      - *
    • status : string - status
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_restoreBackupClose: - * <message from lower layer>
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see LocStor::restoreBackupClose - */ - public function xr_restoreBackupClose($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - $res = $this->restoreBackupClose($r['token']); - if (PEAR::isError($res)) { - return new XML_RPC_Response(0, 805, - "xr_restoreBackupClose: ".$res->getMessage(). - " ".$res->getUserInfo() - ); - } -# return new XML_RPC_Response(XML_RPC_encode(array( -# 'gunid'=>$res, -# ))); - return new XML_RPC_Response(XML_RPC_encode($res)); - } - - /*========================================================== info methods */ - /** - * Check if audio clip exists and return TRUE/FALSE - * - * The XML-RPC name of this method is "locstor.existsAudioClip". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • sessid : string - session id
    • - *
    • gunid : string - global unique id of AudioCLip
    • - *
    - * - * On success, returns a XML-RPC struct with single field: - *
      - *
    • exists : boolean
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_existsAudioClip: - * <message from lower layer>
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see LocStor::existsAudioClip - */ - public function xr_existsAudioClip($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - #$this->debugLog(join(', ', $r)); - $res = $this->existsAudioClip($r['sessid'], $r['gunid']); - #$this->debugLog($res); - if (PEAR::isError($res)) - return new XML_RPC_Response(0, 805, - "xr_existsAudioClip: ".$res->getMessage(). - " ".$res->getUserInfo() - ); - return new XML_RPC_Response(XML_RPC_encode(array('exists'=>$res))); - } - - /*====================================================== metadata methods */ - /** - * Return all file's metadata as XML string - * - * The XML-RPC name of this method is "locstor.getAudioClip". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • sessid : string - session id
    • - *
    • gunid : string - global unique id of AudioCLip
    • - *
    - * - * On success, returns a XML-RPC struct with single field: - *
      - *
    • metadata : string - metadata as XML
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_getAudioClip: - * <message from lower layer>
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see LocStor::getAudioClip - */ - public function xr_getAudioClip($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - $res = $this->getAudioClip($r['sessid'], $r['gunid']); - if (PEAR::isError($res)) { - return new XML_RPC_Response(0, 805, - "xr_getAudioClip: ".$res->getMessage()." ".$res->getUserInfo() - ); - } - return new XML_RPC_Response(XML_RPC_encode(array('metadata'=>$res))); - } - - /** - * Update existing audio clip metadata - * - * The XML-RPC name of this method is "locstor.updateAudioClipMetadata". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • sessid : string - session id
    • - *
    • gunid : string - global unique id of AudioCLip
    • - *
    • metadata : metadata XML string
    • - *
    - * - * On success, returns a XML-RPC struct with single field: - *
      - *
    • status : boolean - TRUE
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_updateAudioClipMetadata: - * <message from lower layer>
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see LocStor::updateAudioClipMetadata - */ - public function xr_updateAudioClipMetadata($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - $res = $this->updateAudioClipMetadata( - $r['sessid'], $r['gunid'], $r['metadata'] - ); - if (PEAR::isError($res)) { - return new XML_RPC_Response(0, 805, - "xr_updateAudioClip: ".$res->getMessage(). - " ".$res->getUserInfo() - ); - } - return new XML_RPC_Response(XML_RPC_encode(array('status'=>$res))); - } - - /** - * Search in local metadata database - * - * The XML-RPC name of this method is "locstor.searchMetadata". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • sessid : string - session id
    • - *
    • criteria : struct, with following fields:
      - *
        - *
      • filetype : string - type of searched files, - * meaningful values: 'audioclip', 'webstream', 'playlist', 'all'
      • - *
      • 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 - * "conditions" field) - *
      • - *
      • limit : int - limit for result arrays (0 means unlimited)
      • - *
      • offset : int - starting point (0 means without offset)
      • - *
      • orderby : string - metadata category for sorting (optional) - * or array of strings for multicolumn orderby - * [default: dc:creator, dc:source, dc:title] - *
      • - *
      • desc : boolean - flag for descending order (optional) - * or array of boolean for multicolumn orderby - * (it corresponds to elements of orderby field) - * [default: all ascending] - *
      • - *
      • conditions : array of struct with fields: - *
          - *
        • cat : string - metadata category name
        • - *
        • op : string - operator, meaningful values: - * 'full', 'partial', 'prefix', '=', '<', '<=', - * '>', '>='
        • - *
        • val : string - search value
        • - *
        - *
      • - *
      - *
    • - *
    - * - * On success, returns a XML-RPC array of structs with 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
      • - *
      - *
    • - *
    - * (cnt value may be greater than size of result array - see limit param) - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_searchMetadata: - * <message from lower layer>
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see LocStor::searchMetadata - * @see BasicStor::localSearch - */ - public function xr_searchMetadata($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - $res = $this->searchMetadata($r['sessid'], $r['criteria']); - if (PEAR::isError($res)) { - return new XML_RPC_Response(0, 805, - "xr_searchAudioClip: ".$res->getMessage(). - " ".$res->getUserInfo() - ); - } -# return new XML_RPC_Response(XML_RPC_encode($res)); - $xv = new XML_RPC_Value; - $xv->addStruct(array( - 'cnt' => XML_RPC_encode($res['cnt']), - 'results' => - (count($res['results'])==0 - ? new XML_RPC_Value(array(), 'array') - : XML_RPC_encode($res['results']) - ), - )); - return new XML_RPC_Response($xv); - } - - /** - * Return values of specified metadata category - * - * The XML-RPC name of this method is "locstor.browseCategory". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • sessid : string - session id
    • - *
    • category : string - metadata category name - * with or without namespace prefix (dc:title, author)
    • - *
    • criteria : hash - see searchMetadata method
    • - *
    - * - * On success, returns a XML-RPC struct with the following fields: - *
      - *
    • results : array with found values
    • - *
    • cnt : integer - number of matching values
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_browseCategory: - * <message from lower layer>
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see LocStor::browseCategory - */ - public function xr_browseCategory($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - $res = $this->browseCategory( - $r['category'], $r['criteria'], $r['sessid'] - ); - if (PEAR::isError($res)) { - return new XML_RPC_Response(0, 805, - "xr_getAudioClip: ".$res->getMessage()." ".$res->getUserInfo() - - ); - } - $xv = new XML_RPC_Value; - $xv->addStruct(array( - 'cnt' => XML_RPC_encode($res['cnt']), - 'results' => - (count($res['results'])==0 - ? new XML_RPC_Value(array(), 'array') - : XML_RPC_encode($res['results']) - ), - )); - return new XML_RPC_Response($xv); - } - - /* ============================================== methods for preferences */ - /** - * Load user preference value - * - * The XML-RPC name of this method is "locstor.loadPref". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • sessid : string - session id
    • - *
    • key : string - preference key
    • - *
    - * - * On success, returns a XML-RPC struct with single field: - *
      - *
    • value : string - preference value
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_loadPref: - * <message from lower layer>
    • - *
    • 848 - invalid session id.
    • - *
    • 849 - invalid preference key.
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see Pref::loadPref - */ - public function xr_loadPref($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - require_once(dirname(__FILE__).'/../Prefs.php'); - $pr = new Prefs($this); - $res = $pr->loadPref($r['sessid'], $r['key']); - if (PEAR::isError($res)) { - $ec0 = intval($res->getCode()); - $ec = ($ec0 == GBERR_SESS || $ec0 == GBERR_PREF ? 800+$ec0 : 805 ); - return new XML_RPC_Response(0, $ec, - "xr_getAudioClip: ".$res->getMessage()." ".$res->getUserInfo() - - ); - } - return new XML_RPC_Response(XML_RPC_encode(array('value'=>$res))); - } - - /** - * Save user preference value - * - * The XML-RPC name of this method is "locstor.savePref". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • sessid : string - session id
    • - *
    • key : string - preference key
    • - *
    • value : string - preference value
    • - *
    - * - * On success, returns a XML-RPC struct with single field: - *
      - *
    • status : boolean
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_savePref: - * <message from lower layer>
    • - *
    • 848 - invalid session id.
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see Pref::savePref - */ - public function xr_savePref($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - require_once(dirname(__FILE__).'/../Prefs.php'); - $pr = new Prefs($this); - $res = $pr->savePref($r['sessid'], $r['key'], $r['value']); - if (PEAR::isError($res)) { - $ec0 = intval($res->getCode()); - $ec = ($ec0 == GBERR_SESS ? 800+$ec0 : 805 ); - return new XML_RPC_Response(0, $ec, - "xr_getAudioClip: ".$res->getMessage()." ".$res->getUserInfo() - ); - } - return new XML_RPC_Response(XML_RPC_encode(array('status'=>$res))); - } - - /** - * Delete user preference record - * - * The XML-RPC name of this method is "locstor.delPref". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • sessid : string - session id
    • - *
    • key : string - preference key
    • - *
    - * - * On success, returns a XML-RPC struct with single field: - *
      - *
    • status : boolean
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_delPref: - * <message from lower layer>
    • - *
    • 848 - invalid session id.
    • - *
    • 849 - invalid preference key.
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see Pref::delPref - */ - public function xr_delPref($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - require_once(dirname(__FILE__).'/../Prefs.php'); - $pr = new Prefs($this); - $res = $pr->delPref($r['sessid'], $r['key']); - if (PEAR::isError($res)) { - $ec0 = intval($res->getCode()); - $ec = ($ec0 == GBERR_SESS || $ec0 == GBERR_PREF ? 800+$ec0 : 805 ); - return new XML_RPC_Response(0, $ec, - "xr_getAudioClip: ".$res->getMessage()." ".$res->getUserInfo() - ); - } - return new XML_RPC_Response(XML_RPC_encode(array('status'=>$res))); - } - - /** - * Read group preference record - * - * The XML-RPC name of this method is "locstor.loadGroupPref". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • sessid : string - session id
    • - *
    • group : string - group name
    • - *
    • key : string - preference key
    • - *
    - * - * On success, returns a XML-RPC struct with single field: - *
      - *
    • value : string - preference value
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_loadGroupPref: - * <message from lower layer>
    • - *
    • 820 - invalid group name.
    • - *
    • 848 - invalid session id.
    • - *
    • 849 - invalid preference key.
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see Pref::loadGroupPref - */ - public function xr_loadGroupPref($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - require_once(dirname(__FILE__).'/../Prefs.php'); - $pr = new Prefs($this); - $res = $pr->loadGroupPref($r['group'], $r['key']); - if (PEAR::isError($res)) { - $ec0 = intval($res->getCode()); - $ec = ( - $ec0 == GBERR_SESS || $ec0 == GBERR_PREF || $ec0==ALIBERR_NOTGR - ? 800+$ec0 : 805 - ); - return new XML_RPC_Response(0, $ec, - "xr_getAudioClip: ".$res->getMessage()." ".$res->getUserInfo() - - ); - } - return new XML_RPC_Response(XML_RPC_encode(array('value'=>$res))); - } - - /** - * Save group preference record - * - * The XML-RPC name of this method is "locstor.saveGroupPref". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • sessid : string - session id
    • - *
    • group : string - group name
    • - *
    • key : string - preference key
    • - *
    • value : string - preference value
    • - *
    - * - * On success, returns a XML-RPC struct with single field: - *
      - *
    • status : boolean
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_saveGroupPref: - * <message from lower layer>
    • - *
    • 820 - invalid group name.
    • - *
    • 848 - invalid session id.
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see Pref::saveGroupPref - */ - public function xr_saveGroupPref($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - require_once(dirname(__FILE__).'/../Prefs.php'); - $pr = new Prefs($this); - $res = $pr->saveGroupPref($r['sessid'], $r['group'], $r['key'], $r['value']); - if (PEAR::isError($res)) { - $ec0 = intval($res->getCode()); - $ec = ($ec0==GBERR_SESS || $ec0==ALIBERR_NOTGR ? 800+$ec0 : 805 ); - return new XML_RPC_Response(0, $ec, - "xr_getAudioClip: ".$res->getMessage()." ".$res->getUserInfo() - ); - } - return new XML_RPC_Response(XML_RPC_encode(array('status'=>$res))); - } - - /* =============================== remote repository (networking) methods */ - /* ------------------------------------------------------- common methods */ - /** - * Common "check" method for transports - * - * The XML-RPC name of this method is "locstor.getTransportInfo". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • trtok : string - transport token
    • - *
    - * - * On success, returns a XML-RPC struct with the following fields: - *
      - *
    • trtype: string - audioclip | playlist | search | file
    • - *
    • direction: string - up | down
    • - *
    • state: string - transport state
    • - *
    • expectedsize: int - expected size
    • - *
    • realsize: int - size of transported file
    • - *
    • expectedchsum: string - expected checksum
    • - *
    • realchsum: string - checksum of transported file
    • - *
    • title: string - file title
    • - *
    • errmsg: string - error message from failed transports
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_getTransportInfo: - * <message from lower layer>
    • - *
    • 848 - invalid session id.
    • - *
    • 872 - invalid tranport token.
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see Transport::getTransportInfo - */ - public function xr_getTransportInfo($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - require_once('../Transport.php'); - $tr = new Transport($this); - $res = $tr->getTransportInfo($r['trtok']); - if (PEAR::isError($res)) { - $ec0 = intval($res->getCode()); - $ec = ($ec0 == GBERR_SESS || $ec0 == TRERR_TOK ? 800+$ec0 : 805 ); - return new XML_RPC_Response(0, $ec, - "xr_getTransportInfo: ".$res->getMessage()." ".$res->getUserInfo() - ); - } - return new XML_RPC_Response(XML_RPC_encode($res)); - } - - /** - * Turn transports on/off, optionaly return current state. - * - * The XML-RPC name of this method is "locstor.turnOnOffTransports". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • sessid : string - session id
    • - *
    • onOff: boolean optional - * (if not used, current state is returned)
    • - *
    - * - * On success, returns a XML-RPC struct with the following fields: - *
      - *
    • state : boolean - previous state
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_turnOnOffTransports: - * <message from lower layer>
    • - *
    • 848 - invalid session id.
    • - *
    • 872 - invalid tranport token.
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see Transport::turnOnOffTransports - */ - public function xr_turnOnOffTransports($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - require_once('../Transport.php'); - $tr = new Transport($this); - $res = $tr->turnOnOffTransports($r['onOff']); - if (PEAR::isError($res)) { - $ec0 = intval($res->getCode()); - $ec = ($ec0 == GBERR_SESS || $ec0 == TRERR_TOK ? 800+$ec0 : 805 ); - return new XML_RPC_Response(0, $ec, - "xr_turnOnOffTransports: ".$res->getMessage()." ".$res->getUserInfo() - ); - } - return new XML_RPC_Response(XML_RPC_encode(array('state'=>$res))); - } - - /** - * Pause, resume or cancel transport - * - * The XML-RPC name of this method is "locstor.doTransportAction". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • sessid : string - session id
    • - *
    • trtok : string - transport token
    • - *
    • action: string - pause | resume | cancel - *
    - * - * On success, returns a XML-RPC struct with the following fields: - *
      - *
    • state : string - resulting transport state
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_doTransportAction: - * <message from lower layer>
    • - *
    • 848 - invalid session id.
    • - *
    • 872 - invalid tranport token.
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see Transport::doTransportAction - */ - public function xr_doTransportAction($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - require_once('../Transport.php'); - $tr = new Transport($this); - $res = $tr->doTransportAction($r['trtok'], $r['action']); - if (PEAR::isError($res)) { - $ec0 = intval($res->getCode()); - $ec = ($ec0 == GBERR_SESS || $ec0 == TRERR_TOK ? 800+$ec0 : 805 ); - return new XML_RPC_Response(0, $ec, - "xr_doTransportAction: ".$res->getMessage()." ".$res->getUserInfo() - ); - } - return new XML_RPC_Response(XML_RPC_encode(array('state'=>$res))); - } - - /* ------------------------ methods for ls-archive-format file transports */ - /** - * Open async file transfer from local storageServer to network hub, - * file should be ls-archive-format file. - * - * The XML-RPC name of this method is "locstor.uploadFile2Hub". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • sessid : string - session id
    • - *
    • filePath string - local path to uploaded file
    • - *
    - * - * On success, returns a XML-RPC struct with the following fields: - *
      - *
    • trtok : string - transport token
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_uploadFile2Hub: - * <message from lower layer>
    • - *
    • 848 - invalid session id.
    • - *
    • 872 - invalid tranport token.
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see Transport::uploadFile2Hub - */ - public function xr_uploadFile2Hub($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - require_once('../Transport.php'); - $tr = new Transport($this); - $res = $tr->uploadFile2Hub($r['filePath']); // local files on XML-RPC :( - // there should be something as uploadFile2storageServer - if (PEAR::isError($res)) { - $ec0 = intval($res->getCode()); - $ec = ($ec0 == GBERR_SESS || $ec0 == TRERR_TOK ? 800+$ec0 : 805 ); - return new XML_RPC_Response(0, $ec, - "xr_uploadFile2Hub: ".$res->getMessage()." ".$res->getUserInfo() - ); - } - return new XML_RPC_Response(XML_RPC_encode(array('trtok'=>$res))); - } - - /** - * Get list of prepared transfers initiated by hub - * - * The XML-RPC name of this method is "locstor.getHubInitiatedTransfers". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • sessid : string - session id
    • - *
    - * - * On success, returns a XML-RPC struct with the following fields: - *
      - *
    • results : array of structs with fields: - *
        - *
      • trtok : string - transport token
      • - *
      • ... ?
      • - *
      - *
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_getHubInitiatedTransfers: - * <message from lower layer>
    • - *
    • 848 - invalid session id.
    • - *
    • 872 - invalid tranport token.
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see Transport::getHubInitiatedTransfers - */ - public function xr_getHubInitiatedTransfers($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - require_once('../Transport.php'); - $tr = new Transport($this); - $res = $tr->getHubInitiatedTransfers(); - if (PEAR::isError($res)) { - $ec0 = intval($res->getCode()); - $ec = ($ec0 == GBERR_SESS || $ec0 == TRERR_TOK ? 800+$ec0 : 805 ); - return new XML_RPC_Response(0, $ec, - "xr_getHubInitiatedTransfers: ".$res->getMessage()." ".$res->getUserInfo() - ); - } - return new XML_RPC_Response(XML_RPC_encode($res)); - } - - /** - * Start of download initiated by hub - * - * The XML-RPC name of this method is "locstor.startHubInitiatedTransfer". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • trtok : string - transport token obtained from - * the getHubInitiatedTransfers method
    • - *
    - * - * On success, returns a XML-RPC struct with the following fields: - *
      - *
    • trtok : string - transport token
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_startHubInitiatedTransfer: - * <message from lower layer>
    • - *
    • 848 - invalid session id.
    • - *
    • 872 - invalid tranport token.
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see Transport::startHubInitiatedTransfer - */ - public function xr_startHubInitiatedTransfer($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - require_once('../Transport.php'); - $tr = new Transport($this); - $res = $tr->startHubInitiatedTransfer($r['trtok']); - if (PEAR::isError($res)) { - $ec0 = intval($res->getCode()); - $ec = ($ec0 == GBERR_SESS || $ec0 == TRERR_TOK ? 800+$ec0 : 805 ); - return new XML_RPC_Response(0, $ec, - "xr_startHubInitiatedTransfer: ".$res->getMessage()." ".$res->getUserInfo() - ); - } - return new XML_RPC_Response(XML_RPC_encode(array('trtok'=>$res))); - } - - /* ------------- special methods for audioClip/webstream object transport */ - - /** - * Start upload of audioclip or playlist from local storageServer to hub - * - * The XML-RPC name of this method is "locstor.upload2Hub". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • sessid : string - session id
    • - *
    • gunid: string - global unique id of object being transported - *
    • - *
    - * - * On success, returns a XML-RPC struct with the following fields: - *
      - *
    • trtok : string - transport token
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_upload2Hub: - * <message from lower layer>
    • - *
    • 848 - invalid session id.
    • - *
    • 872 - invalid tranport token.
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see Transport::upload2Hub - */ - public function xr_upload2Hub($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - require_once('../Transport.php'); - $tr = new Transport($this); - $res = $tr->upload2Hub($r['gunid']); - if (PEAR::isError($res)) { - $ec0 = intval($res->getCode()); - $ec = ($ec0 == GBERR_SESS || $ec0 == TRERR_TOK ? 800+$ec0 : 805 ); - return new XML_RPC_Response(0, $ec, - "xr_upload2Hub: ".$res->getMessage()." ".$res->getUserInfo() - ); - } - return new XML_RPC_Response(XML_RPC_encode(array('trtok'=>$res))); - } - - /** - * Start download of audioclip or playlist from hub to local storageServer - * - * The XML-RPC name of this method is "locstor.downloadFromHub". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • sessid : string - session id
    • - *
    • gunid: string - global unique id of object being transported - *
    • - *
    - * - * On success, returns a XML-RPC struct with the following fields: - *
      - *
    • trtok : string - transport token
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_downloadFromHub: - * <message from lower layer>
    • - *
    • 848 - invalid session id.
    • - *
    • 872 - invalid tranport token.
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see Transport::downloadFromHub - */ - public function xr_downloadFromHub($input) - { - list($ok, $par) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $par; - } - require_once('../Transport.php'); - $tr = new Transport($this); - $uid = Alib::GetSessUserId($par['sessid']); - $res = $tr->downloadFromHub($uid, $par['gunid']); - if (PEAR::isError($res)) { - $ec0 = intval($res->getCode()); - $ec = ($ec0 == GBERR_SESS || $ec0 == TRERR_TOK ? 800+$ec0 : 805 ); - return new XML_RPC_Response(0, $ec, - "xr_downloadFromHub: ".$res->getMessage()." ".$res->getUserInfo() - ); - } - return new XML_RPC_Response(XML_RPC_encode(array('trtok'=>$res))); - } - - /* ------------------------------------------------ global-search methods */ - /** - * Start search job on network hub - * - * The XML-RPC name of this method is "locstor.globalSearch". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • sessid : string - session id
    • - *
    • criteria : hash, LS criteria format - see searchMetadata method - *
    • - *
    - * - * On success, returns a XML-RPC struct with the following fields: - *
      - *
    • trtok : string - transport token
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_globalSearch: - * <message from lower layer>
    • - *
    • 848 - invalid session id.
    • - *
    • 874 - invalid hub connection configuration.
    • - *
    • 872 - invalid tranport token.
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see Transport::globalSearch - */ -// public function xr_globalSearch($input) -// { -// list($ok, $r) = XR_LocStor::xr_getParams($input); -// if (!$ok) { -// return $r; -// } -// require_once('../Transport.php'); -// $tr = new Transport($this); -// $res = $tr->globalSearch($r['criteria']); -// if (PEAR::isError($res)) { -// $ec0 = intval($res->getCode()); -// $ec = ($ec0 == GBERR_SESS || $ec0 == TRERR_TOK ? 800+$ec0 : 805 ); -// return new XML_RPC_Response(0, $ec, -// "xr_globalSearch: ".$res->getMessage()." ".$res->getUserInfo() -// ); -// } -// return new XML_RPC_Response(XML_RPC_encode(array('trtok'=>$res))); -// } - - /** - * Get results from search job on network hub. - * (returns error if not finished) - * - * The XML-RPC name of this method is "locstor.getSearchResults". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • trtok : string - transport token
    • - *
    - * - * On success, returns a XML-RPC array of structs with 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
      • - *
      - *
    • - *
    - * (cnt value may be greater than size of result array - see limit param) - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_getSearchResults: - * <message from lower layer>
    • - *
    • 848 - invalid session id.
    • - *
    • 872 - invalid tranport token.
    • - *
    • 873 - not finished.
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see Transport::getSearchResults - */ -// public function xr_getSearchResults($input) -// { -// list($ok, $r) = XR_LocStor::xr_getParams($input); -// if (!$ok) { -// return $r; -// } -// require_once('../Transport.php'); -// $tr = new Transport($this); -// $res = $tr->getSearchResults($r['trtok']); -// if (PEAR::isError($res)) { -// $ec0 = intval($res->getCode()); -// $ec = ( -// $ec0 == GBERR_SESS || $ec0 == TRERR_TOK || $ec0 == TRERR_NOTFIN -// ? 800+$ec0 : 805 ); -// return new XML_RPC_Response(0, $ec, -// "xr_getSearchResults: ".$res->getMessage()." ".$res->getUserInfo() -// ); -// } -// return new XML_RPC_Response(XML_RPC_encode($res)); -// } - - /** - * OBSOLETE - * Starts upload audioclip to remote archive - * - * The XML-RPC name of this method is "locstor.uploadToArchive". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • sessid : string - session id
    • - *
    • gunid : string - global unique id
    • - *
    - * - * On success, returns a XML-RPC struct with single field: - *
      - *
    • trtok : string - transport token
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_uploadToArchive: - * <message from lower layer>
    • - *
    • 848 - invalid session id.
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see Transport::uploadToArchive - */ -// public function xr_uploadToArchive($input) -// { -// list($ok, $r) = XR_LocStor::xr_getParams($input); -// if (!$ok) { -// return $r; -// } -// require_once(dirname(__FILE__).'/../Transport.php'); -// $tr = new Transport($this); -// $res = $tr->uploadToArchive($r['gunid'], $r['sessid']); -// if (PEAR::isError($res)) { -// $ec0 = intval($res->getCode()); -// $ec = ($ec0 == GBERR_SESS ? 800+$ec0 : 805 ); -// return new XML_RPC_Response(0, $ec, -// "xr_getAudioClip: ".$res->getMessage()." ".$res->getUserInfo() -// ); -// } -// return new XML_RPC_Response(XML_RPC_encode(array('trtok'=>$res))); -// } - - /** - * OBSOLETE - * Starts download audioclip from remote archive - * - * The XML-RPC name of this method is "locstor.downloadFromArchive". - * - * The input parameters are an XML-RPC struct with the following - * fields: - *
      - *
    • sessid : string - session id
    • - *
    • gunid : string - global unique id
    • - *
    - * - * On success, returns a XML-RPC struct with single field: - *
      - *
    • trtok : string - transport token
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_downloadFromArchive: - * <message from lower layer>
    • - *
    • 848 - invalid session id.
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see Transport::downloadFromArchive - */ -// public function xr_downloadFromArchive($input) -// { -// list($ok, $r) = XR_LocStor::xr_getParams($input); -// if (!$ok) { -// return $r; -// } -// require_once(dirname(__FILE__).'/../Transport.php'); -// $tr = new Transport($this); -// $res = $tr->downloadFromArchive($r['gunid'], $r['sessid']); -// if (PEAR::isError($res)) { -// $ec0 = intval($res->getCode()); -// $ec = ($ec0 == GBERR_SESS ? 800+$ec0 : 805 ); -// return new XML_RPC_Response(0, $ec, -// "xr_getAudioClip: ".$res->getMessage()." ".$res->getUserInfo() -// ); -// } -// return new XML_RPC_Response(XML_RPC_encode(array('trtok'=>$res))); -// } - - /* ================================================ methods for debugging */ - /** - * Reset storageServer for debugging. - * - * The XML-RPC name of this method is "locstor.resetStorage". - * - * The input parameters are an empty XML-RPC struct, - * or struct with the following optional fields: - *
      - *
    • loadSampleData : boolean - load sample data? (default: true) - *
    • - *
    • invalidateSessionIds : boolean - invalidate active session IDs? - * (default: false) - *
    • - *
    - * - * On success, returns the same result as searchMetadata with filetype - * 'all' and no conditions, ordered by filetype and dc:title - * i.e. XML-RPC array of structs with fields: - *
      - *
    • cnt : integer - number of inserted files
    • - *
    • 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
      • - *
      - *
    • - *
    - * - * On errors, returns an XML-RPC error response. - * The possible error codes and error message are: - *
      - *
    • 3 - Incorrect parameters passed to method: - * Wanted ... , got ... at param
    • - *
    • 801 - wrong 1st parameter, struct expected.
    • - *
    • 805 - xr_resetStorage: - * <message from lower layer>
    • - *
    - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - * @see LocStor::getAudioClip - */ -// public function xr_resetStorage($input) -// { -// list($ok, $r) = XR_LocStor::xr_getParams($input); -// if (!$ok) { -// return $r; -// } -// $res = $this->resetStorage( -// isset($r['loadSampleData']) ? $r['loadSampleData'] : TRUE, -// !(isset($r['invalidateSessionIds']) ? $r['invalidateSessionIds'] : FALSE) -// ); -// if (PEAR::isError($res)) { -// return new XML_RPC_Response(0, 805, -// "xr_getAudioClip: ".$res->getMessage()." ".$res->getUserInfo() -// ); -// } -// return new XML_RPC_Response(XML_RPC_encode($res)); -// } - - /** - * Test XMLRPC - strupper and return given string, - * also return loginname of logged user - * - debug method only - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - */ - public function xr_test($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - return new XML_RPC_Response(XML_RPC_encode(array( - 'str'=>strtoupper($r['teststring']), - 'login' => Alib::GetSessLogin($r['sessid']), - 'sessid'=>$r['sessid'] - ))); - } - - /** - * Open writable URL for put method - debug method only - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - */ - public function xr_openPut($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - $res = $this->bsOpenPut(); - if (PEAR::isError($res)) { - return new XML_RPC_Response(0, 805, - "xr_getAudioClip: ".$res->getMessage()." ".$res->getUserInfo() - ); - } - return new XML_RPC_Response(XML_RPC_encode($res)); - } - - - /** - * Close writable URL - debug method only - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - */ - public function xr_closePut($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - $res = $this->bsClosePut($r['token'], $r['chsum']); - if (PEAR::isError($res)) { - return new XML_RPC_Response(0, 805, - "xr_getAudioClip: ".$res->getMessage()." ".$res->getUserInfo() - ); - } - return new XML_RPC_Response(XML_RPC_encode(array( - 'fname'=>$res['fname'], - 'owner'=>$res['owner'], - ))); - } - - /** - * Simple ping method - return string - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - */ - function xr_ping($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - $res = date("Ymd-H:i:s")." -- reply from remote node: {$r['par']}"; - return new XML_RPC_Response(XML_RPC_encode($res)); - } - - - /** - * @param XML_RPC_Message $input - * @return XML_RPC_Response - */ - function xr_uploadOpen($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - $res = $this->uploadOpen($r['sessid'], $r['chsum']); - if (PEAR::isError($res)) - return new XML_RPC_Response(0, 803, - "xr_uploadOpen: ".$res->getMessage(). - " ".$res->getUserInfo() - ); - return new XML_RPC_Response(XML_RPC_encode($res)); - } - - - /** - * Check state of file upload - * - * @param XML_RPC_Message $input - * @return XML_RPC_Response - */ - function xr_uploadCheck($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - $res = $this->uploadCheck($r['token']); - if (PEAR::isError($res)) - return new XML_RPC_Response(0, 803, - "xr_uploadCheck: ".$res->getMessage(). - " ".$res->getUserInfo() - ); - return new XML_RPC_Response(XML_RPC_encode($res)); - } - - - /** - * @param XML_RPC_Message $input - * @return XML_RPC_Response - */ - function xr_uploadClose($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - $res = $this->uploadClose($r['token'], $r['trtype'], $r['pars']); - if (PEAR::isError($res)) { - $code = 803; - // Special case for duplicate file - give back - // different error code so we can display nice user message. - if ($res->getCode() == GBERR_GUNID) { - $code = 888; - } - return new XML_RPC_Response(0, $code, - "xr_uploadClose: ".$res->getMessage(). - " ".$res->getUserInfo() - ); - } - return new XML_RPC_Response(XML_RPC_encode($res)); - } - - - /** - * @param XML_RPC_Message $input - * @return XML_RPC_Response - */ - function xr_downloadOpen($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - $res = $this->downloadOpen($r['sessid'], $r['trtype'], $r['pars']); - if (PEAR::isError($res)) - return new XML_RPC_Response(0, 803, - "xr_downloadOpen: ".$res->getMessage(). - " ".$res->getUserInfo() - ); - return new XML_RPC_Response(XML_RPC_encode($res)); - } - - - /** - * @param XML_RPC_Message $input - * @return XML_RPC_Response - */ - function xr_downloadClose($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - $res = $this->downloadClose($r['token'], $r['trtype']); - if (PEAR::isError($res)) - return new XML_RPC_Response(0, 803, - "xr_downloadClose: ".$res->getMessage(). - " ".$res->getUserInfo() - ); - return new XML_RPC_Response(XML_RPC_encode($res)); - } - - - /** - * @param XML_RPC_Message $input - * @return XML_RPC_Response - */ - function xr_prepareHubInitiatedTransfer($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - foreach (array('trtype'=>NULL, 'direction'=>'up', 'pars'=>array()) as $k => $dv) { - if (!isset($r[$k])) { - $r[$k] = $dv; - } - } - $res = $this->prepareHubInitiatedTransfer( - $r['target'], $r['trtype'], $r['direction'], $r['pars']); - if (PEAR::isError($res)) - return new XML_RPC_Response(0, 803, - "xr_prepareHubInitiatedTransfer: ".$res->getMessage(). - " ".$res->getUserInfo() - ); - return new XML_RPC_Response(XML_RPC_encode($res)); - } - - - /** - * @param XML_RPC_Message $input - * @return XML_RPC_Response - */ - function xr_listHubInitiatedTransfers($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - foreach (array('target'=>NULL, 'direction'=>NULL, 'trtok'=>NULL) as $k=>$dv) { - if (!isset($r[$k])) { - $r[$k] = $dv; - } - } - $res = $this->listHubInitiatedTransfers( - $r['target'], $r['direction'], $r['trtok']); - if (PEAR::isError($res)) - return new XML_RPC_Response(0, 803, - "xr_listHubInitiatedTransfers: ".$res->getMessage(). - " ".$res->getUserInfo() - ); - return new XML_RPC_Response(XML_RPC_encode($res)); - } - - - /** - * @param XML_RPC_Message $input - * @return XML_RPC_Response - */ - function xr_setHubInitiatedTransfer($input) - { - list($ok, $r) = XR_LocStor::xr_getParams($input); - if (!$ok) { - return $r; - } - $res = $this->setHubInitiatedTransfer( - $r['target'], $r['trtok'], $r['state']); - if (PEAR::isError($res)) - return new XML_RPC_Response(0, 803, - "xr_setHubInitiatedTransfer: ".$res->getMessage(). - " ".$res->getUserInfo() - ); - return new XML_RPC_Response(XML_RPC_encode($res)); - } - - /* ==================================================== "private" methods */ - /** - * Check and convert struct of parameters - * - * @param XML_RPC_Message $input - * @return array - * Array of two items: first item is boolean, indicating - * successful decode. - * On success, the second param is an array of values. - * On failure, the second param is an XML_RPC_Response object. - */ - protected static function xr_getParams($input) - { - $p = $input->getParam(0); - if (isset($p) && ($p->scalartyp()=="struct")) { - $r = XML_RPC_decode($p); - return array(TRUE, $r); - } else { - return array(FALSE, new XML_RPC_Response(0, 801, "wrong 1st parameter, struct expected." )); - } - } - -} // class XR_LocStor - - diff --git a/application/models/xmlrpc/index.php b/application/models/xmlrpc/index.php deleted file mode 100644 index 07176fc80..000000000 --- a/application/models/xmlrpc/index.php +++ /dev/null @@ -1,3 +0,0 @@ - - *
  • token : string, put token returned by appropriate - * XMLRPC call
  • - * - * - * On success, returns HTTP return code 200. - * - * On errors, returns HTTP return code >200 - * The possible error codes are: - *
      - *
    • 400 - Incorrect parameters passed to method
    • - *
    • 403 - Access denied
    • - *
    • 500 - Application error
    • - *
    - * - * @see XR_LocStor - * @package Airtime - * @subpackage storageServer - */ - -define('USE_FLOCK', TRUE); - -require_once(dirname(__FILE__).'/../../conf.php'); -require_once('DB.php'); -require_once(dirname(__FILE__).'/../LocStor.php'); - -PEAR::setErrorHandling(PEAR_ERROR_RETURN); -$CC_DBC = DB::connect($CC_CONFIG['dsn'], TRUE); -$CC_DBC->setFetchMode(DB_FETCHMODE_ASSOC); -$gb = new LocStor(); - -function http_error($code, $err) -{ - header("HTTP/1.1 $code"); - header("Content-type: text/plain; charset=UTF-8"); - echo "$err\r\n"; - flush(); - exit; -} - -if (preg_match("|^[0-9a-fA-F]{16}$|", $_REQUEST['token'])) { - $token = $_REQUEST['token']; -} else { - http_error(400, "Error on token parameter. ({$_REQUEST['token']})"); -} - -$tc = BasicStor::bsCheckToken($token, 'put'); -if (PEAR::isError($tc)) { - http_error(500, $ex->getMessage()); -} -if (!$tc) { - http_error(403, "put.php: Token not valid ($token)."); -} - -header("Content-type: text/plain"); - -$destfile = $CC_CONFIG['accessDir']."/{$token}"; - -/* PUT data comes in on the input stream */ -$putdata = @fopen("php://input", "r") or - http_error(500, "put.php: Can't read input"); - -/* Open a file for writing */ -$fp = @fopen($destfile, "ab") or - http_error(500, "put.php: Can't write to destination file (token=$token)"); - -if ( USE_FLOCK ) { - // lock the file - $lockres = flock($fp,LOCK_EX+LOCK_NB); - if ($lockres !== TRUE) { - http_error(409, "put.php: file locked (token=$token)"); - } -} - -/* Read the data 1 KB at a time and write to the file */ -while ($data = fread($putdata, 1024)){ - fwrite($fp, $data); -} - -if ( USE_FLOCK ) { - // unlock the file - flock($fp,LOCK_UN); -} - -/* Close the streams */ -fclose($fp); -fclose($putdata); - -header("HTTP/1.1 200"); - diff --git a/application/models/xmlrpc/schedulerPhpClient.php b/application/models/xmlrpc/schedulerPhpClient.php deleted file mode 100644 index c591f5bbd..000000000 --- a/application/models/xmlrpc/schedulerPhpClient.php +++ /dev/null @@ -1,510 +0,0 @@ - - *
  • m
  • full method name (include optional prefix) - *
  • p
  • array of input parameter names - *
  • t
  • array of input parameter types - *
  • r
  • array of result element names (not used there at present) - *
  • e
  • array of error codes/messages (not used there at present) - * - */ -$mdefs = array( - "listMethods" => array('m'=>"system.listMethods", 'p'=>NULL, 't'=>NULL), - "AddAudioClipToPlaylistMethod" => array( - 'm'=>'addAudioClipToPlaylist', - 'p'=>array('sessionId'/*string*/, 'playlistId'/*string*/, 'audioClipId'/*string*/, 'relativeOffset'/*int*/, 'clipStart'/*int*/, 'clipEnd'/*int*/, 'clipLength'/*int*/), - 't'=>array('string', 'string', 'string', 'int', 'int', 'int', 'int'), - 'r'=>array('playlistElementId'/*string*/), - 'e'=>array( - '301'=>'invalid argument format', - '302'=>'missing playlist ID argument', - '303'=>'missing audio clip ID argument', - '304'=>'missing relative offset argument', - '305'=>'playlist not found', - '306'=>'playlist has not been opened for editing', - '307'=>'audio clip does not exist', - '308'=>'two audio clips at the same relative offset', - '320'=>'missing session ID argument', - ) - ), - "CreatePlaylistMethod" => array( - 'm'=>'createPlaylist', - 'p'=>array('sessionId'/*string*/), - 't'=>array('string'), - 'r'=>array('playlist'/*string*/), - 'e'=>array( - '201'=>'invalid argument format', - '202'=>'could not create playlist', - '220'=>'missing session ID argument', - ) - ), - "DeletePlaylistMethod" => array( - 'm'=>'deletePlaylist', - 'p'=>array('sessionId'/*string*/, 'playlistId'/*string*/), - 't'=>array('string', 'string'), - 'r'=>array(), - 'e'=>array( - '901'=>'invalid argument format', - '902'=>'missing playlist ID argument', - '903'=>'playlist not found', - '904'=>'playlist is locked', - '905'=>'playlist could not be deleted', - '920'=>'missing session ID argument', - ) - ), - "DisplayAudioClipMethod" => array( - 'm'=>'displayAudioClip', - 'p'=>array('sessionId'/*string*/, 'audioClipId'/*string*/), - 't'=>array('string', 'string'), - 'r'=>array('audioClip'/*string*/), - 'e'=>array( - '601'=>'invalid argument format', - '602'=>'argument is not an audio clip ID', - '603'=>'audio clip not found', - '620'=>'missing session ID argument', - ) - ), - "DisplayAudioClipsMethod" => array( - 'm'=>'displayAudioClips', - 'p'=>array('sessionId'/*string*/), - 't'=>array('string'), - 'r'=>array(array('audioClip'/*string*/)), - 'e'=>array( - '1801'=>'invalid argument format', - '1802'=>'XML-RPC error', - '1820'=>'missing session ID argument', - ) - ), - "DisplayPlaylistMethod" => array( - 'm'=>'displayPlaylist', - 'p'=>array('sessionId'/*string*/, 'playlistId'/*string*/), - 't'=>array('string', 'string'), - 'r'=>array('playlist'/*string*/), - 'e'=>array( - '1001'=>'invalid argument format', - '1002'=>'argument is not a playlist ID', - '1003'=>'playlist not found', - '1020'=>'missing session ID argument', - ) - ), - "DisplayPlaylistsMethod" => array( - 'm'=>'displayPlaylists', - 'p'=>array('sessionId'/*string*/), - 't'=>array('string'), - 'r'=>array(array('playlist'/*string*/)), - 'e'=>array( - '1701'=>'invalid argument format', - '1702'=>'XML-RPC error', - '1720'=>'missing session ID argument', - ) - ), - "DisplayScheduleMethod" => array( - 'm'=>'displaySchedule', - 'p'=>array('sessionId'/*string*/, 'from'/*datetime*/, 'to'/*datetime*/), - 't'=>array('string', 'dateTime.iso8601', 'dateTime.iso8601'), - 'r'=>array(array('id'/*int*/, 'playlistId'/*string*/, 'start'/*datetime*/, 'end'/*datetime*/)), - 'e'=>array( - '1101'=>'invalid argument format', - '1102'=>"missing or invalid 'from' argument", - '1103'=>"missing or invalid 'to' argument", - '1120'=>'missing session ID argument', - ) - ), - "GeneratePlayReportMethod" => array( - 'm'=>'generatePlayReport', - 'p'=>array('sessionId'/*string*/, 'from'/*datetime*/, 'to'/*datetime*/), - 't'=>array('string', 'dateTime.iso8601', 'dateTime.iso8601'), - 'r'=>array(array('audioClipId'/*string*/, 'timestamp'/*datetime*/)), - 'e'=>array( - '1501'=>'invalid argument format', - '1502'=>"missing or invalid 'from' argument", - '1503'=>"missing or invalid 'to' argument", - '1520'=>'missing session ID argument', - ) - ), - "GetSchedulerTimeMethod" => array( - 'm'=>'getSchedulerTime', - 'p'=>array(), - 't'=>array(), - 'r'=>array('schedulerTime'/*datetime*/), - 'e'=>array( -) - ), - "GetVersionMethod" => array( - 'm'=>'getVersion', - 'p'=>array(), - 't'=>array(), - 'r'=>array('version'/*string*/), - 'e'=>array() - ), - "LoginMethod" => array( - 'm'=>'login', - 'p'=>array('login'/*string*/, 'password'/*string*/), - 't'=>array('string', 'string'), - 'r'=>array('sessionId'/*string*/), - 'e'=>array( - '2001'=>'invalid argument format', - '2002'=>'missing login argument', - '2003'=>'missing password argument', - '2004'=>'the authentication server reported an error', - ) - ), - "LogoutMethod" => array( - 'm'=>'logout', - 'p'=>array('sessionId'/*string*/), - 't'=>array('string'), - 'r'=>array(), - 'e'=>array( - '2101'=>'invalid argument format', - '2120'=>'missing session ID argument', - '2104'=>'the authentication server reported an error', - ) - ), - "OpenPlaylistForEditingMethod" => array( - 'm'=>'openPlaylistForEditing', - 'p'=>array('sessionId'/*string*/, 'playlistId'/*string*/), - 't'=>array('string', 'string'), - 'r'=>array('playlist'/*string*/), - 'e'=>array( - '101'=>'invalid argument format', - '102'=>'argument is not a playlist ID', - '104'=>'could not open playlist for editing', - '120'=>'missing session ID argument', - ) - ), - "RemoveAudioClipFromPlaylistMethod" => array( - 'm'=>'removeAudioClipFromPlaylist', - 'p'=>array('sessionId'/*string*/, 'playlistId'/*string*/, 'playlistElementId'/*string*/), - 't'=>array('string', 'string', 'string'), - 'r'=>array(), - 'e'=>array( - '401'=>'invalid argument format', - '402'=>'missing playlist ID argument', - '403'=>'missing relative offset argument', - '404'=>'playlist does not exist', - '405'=>'playlist has not been opened for editing', - '406'=>'no audio clip at the specified relative offset', - '420'=>'missing session ID argument', - ) - ), - "RemoveFromScheduleMethod" => array( - 'm'=>'removeFromSchedule', - 'p'=>array('sessionId'/*string*/, 'scheduleEntryId'/*string*/), - 't'=>array('string', 'string'), - 'r'=>array(), - 'e'=>array( - '1201'=>'invalid argument format', - '1202'=>'missing schedule entry ID argument', - '1203'=>'schedule entry not found', - '1220'=>'missing session ID argument', - ) - ), - "RescheduleMethod" => array( - 'm'=>'reschedule', - 'p'=>array('sessionId'/*string*/, 'scheduleEntryId'/*string*/, 'playtime'/*datetime*/), - 't'=>array('string', 'string', 'dateTime.iso8601'), - 'r'=>array(), - 'e'=>array( - '1301'=>'invalid argument format', - '1302'=>'missing schedule entry ID argument', - '1303'=>'missing playtime argument', - '1304'=>'schedule entry not found', - '1305'=>'could not reschedule entry', - '1320'=>'missing session ID argument', - ) - ), -// "ResetStorageMethod" => array( -// 'm'=>'resetStorage', -// 'p'=>array(), -// 't'=>array(), -// 'r'=>array(), -// 'e'=>array('3001'=>'storage client reported an error'), -// ), - "RevertEditedPlaylistMethod" => array( - 'm'=>'revertEditedPlaylist', - 'p'=>array('sessionId'/*string*/, 'playlistId'/*string*/), - 't'=>array('string', 'string'), - 'r'=>array(), - 'e'=>array( - '801'=>'invalid argument format', - '802'=>'argument is not a playlist ID', - '803'=>'playlist not found', - '804'=>'could not revert playlist', - '820'=>'missing session ID argument', - ) - ), - "SavePlaylistMethod" => array( - 'm'=>'savePlaylist', - 'p'=>array('sessionId'/*string*/, 'playlistId'/*string*/), - 't'=>array('string', 'string'), - 'r'=>array(), - 'e'=>array( - '701'=>'invalid argument format', - '702'=>'argument is not a playlist ID', - '703'=>'playlist not found', - '705'=>'could not save playlist', - '720'=>'missing session ID argument', - ) - ), - "UpdateFadeInFadeOutMethod" => array( - 'm'=>'updateFadeInFadeOut', - 'p'=>array('sessionId'/*string*/, 'playlistId'/*string*/, 'playlistElementId'/*string*/, 'fadeIn'/*int*/, 'fadeOut'/*int*/), - 't'=>array('string', 'string', 'string', 'int', 'int'), - 'r'=>array(), - 'e'=>array( - '1601'=>'invalid argument format', - '1602'=>'missing playlist ID argument', - '1603'=>'missing playlist element ID argument', - '1604'=>'missing fade in argument', - '1605'=>'missing fade out argument', - '1606'=>'playlist does not exist', - '1607'=>'playlist has not been opened for editing', - '1608'=>'error executing setFadeInfo() method', - '1620'=>'missing session ID argument', - ) - ), - "UploadPlaylistMethod" => array( - 'm'=>'uploadPlaylist', - 'p'=>array('sessionId'/*string*/, 'playlistId'/*string*/, 'playtime'/*datetime*/), - 't'=>array('string', 'string', 'dateTime.iso8601'), - 'r'=>array('scheduleEntryId'/*string*/), - 'e'=>array( - '1401'=>'invalid argument format', - '1402'=>'missing playlist ID argument', - '1403'=>'missing playtime argument', - '1404'=>'playlist not found', - '1405'=>'timeframe not available', - '1406'=>'could not schedule playlist', - '1420'=>'missing session ID argument', - ) - ), - "ValidatePlaylistMethod" => array( - 'm'=>'validatePlaylist', - 'p'=>array('sessionId'/*string*/, 'playlistId'/*string*/), - 't'=>array('string', 'string'), - 'r'=>array('valid'/*bool*/), - 'e'=>array( - '501'=>'invalid argument format', - '502'=>'missing playlist ID argument', - '503'=>'playlist does not exist', - '504'=>'playlist has not been opened for editing', - '520'=>'missing session ID argument', - ) - ), - "LoginGB" => array( - 'm'=>'locstor.login', - 'p'=>array('login'/*string*/, 'pass'/*string*/), - 't'=>array('string', 'string'), - 'r'=>array('sessid'/*string*/), - 'e'=>array( - '2001'=>'invalid argument format', - '2002'=>'missing login argument', - '2003'=>'missing password argument', - '2004'=>'the authentication server reported an error', - ) - ), - "LogoutGB" => array( - 'm'=>'locstor.logout', - 'p'=>array('sessid'/*string*/), - 't'=>array('string'), - 'r'=>array('status'/*boolean*/), - 'e'=>array( - '2001'=>'invalid argument format', - '2002'=>'missing login argument', - '2003'=>'missing password argument', - '2004'=>'the authentication server reported an error', - ) - ), -); - -/* ======================================================== class definitions */ - -class SchedulerPhpClient { - /** - * Array with methods description - * @var array - */ - private $mdefs = array(); - - /** - * XMLRPC client object reference - */ - private $client = NULL; - - /** - * Verbosity flag - */ - private $verbose = FALSE; - - /** - * XMLRPC debug flag - */ - private $debug = 0; - - /** - * Constructor - please DON'T CALL IT, use factory method instead - * - * @param DB $dbc - * @param array $mdefs - * hash array with methods description - * @param array $config - * hash array with configuration - * @param int $debug - * XMLRPC debug flag - * @param boolean $verbose - * verbosity flag - */ - public function __construct($mdefs, $debug=0, $verbose=FALSE) - { - global $CC_DBC, $CC_CONFIG; - $this->mdefs = $mdefs; - $this->debug = $debug; - $this->verbose = $verbose; - $confPrefix = "scheduler"; - //$confPrefix = "storage"; - $serverPath = - "http://{$CC_CONFIG["{$confPrefix}UrlHost"]}:{$CC_CONFIG["{$confPrefix}UrlPort"]}". - "{$CC_CONFIG["{$confPrefix}UrlPath"]}/{$CC_CONFIG["{$confPrefix}XMLRPC"]}"; - //$serverPath = "http://localhost:80/campcasterStorageServerCVS/xmlrpc/xrLocStor.php"; - if ($this->verbose) { - echo "serverPath: $serverPath\n"; - } - $url = parse_url($serverPath); - $this->client = new XML_RPC_Client($url['path'], $url['host'], $url['port']); - } - - - /** - * Factory, create object instance - * - * In fact it doesn't create instance of SchedulerPhpClient, but - * dynamically extend this class with set of methods based on $mdefs array - * (using eval function) and instantiate resulting class - * SchedulerPhpClientCore instead. - * Each new method in this subclass accepts parameters according to $mdefs - * array, call wrapper callMethod(methodname, parameters) and return its - * result. - * - * @param array $mdefs - * hash array with methods description - * @param int $debug - * XMLRPC debug flag - * @param boolean $verbose - * verbosity flag - * @return SchedulerPhpClientCore - */ - function &factory($mdefs, $debug=0, $verbose=FALSE) - { - global $CC_DBC, $CC_CONFIG; - $f = ''; - foreach ($mdefs as $fn => $farr) { - $f .= - ' function '.$fn.'(){'."\n". - ' $pars = func_get_args();'."\n". - ' $r = $this->callMethod("'.$fn.'", $pars);'."\n". - ' return $r;'."\n". - ' }'."\n"; - } - $e = - "class SchedulerPhpClientCore extends SchedulerPhpClient{\n". - "$f\n". - "}\n"; -# echo $e; - if (FALSE === eval($e)) { - return $CC_DBC->raiseError("Eval failed"); - } - $spc = new SchedulerPhpClientCore($mdefs, $debug, $verbose); - return $spc; - } - - - /** - * XMLRPC methods wrapper - * Encode XMLRPC request message, send it, receive and decode response. - * - * @param string $method - * method name - * @param array $gettedPars - * returned by func_get_args() in called method - * @return array - * PHP hash with response - */ - function callMethod($method, $gettedPars) - { - $parr = array(); - $XML_RPC_val = new XML_RPC_Value; - foreach ($this->mdefs[$method]['p'] as $i => $p) { - $parr[$p] = new XML_RPC_Value; - $parr[$p]->addScalar($gettedPars[$i], $this->mdefs[$method]['t'][$i]); - } - $XML_RPC_val->addStruct($parr); - $fullmethod = $this->mdefs[$method]['m']; - $msg = new XML_RPC_Message($fullmethod, array($XML_RPC_val)); - if ($this->verbose) { - echo "parr:\n"; - var_dump($parr); - echo "message:\n"; - echo $msg->serialize()."\n"; - } - $this->client->setDebug($this->debug); - $res = $this->client->send($msg); - if ($res->faultCode() > 0) { - return PEAR::raiseError( - "SchedulerPhpClient::$method:".$res->faultString()." ". - $res->faultCode()."\n", $res->faultCode(), - PEAR_ERROR_RETURN - ); - } - if ($this->verbose) { - echo "result:\n"; - echo $res->serialize(); - } - $val = $res->value(); - $resp = XML_RPC_decode($res->value()); - return $resp; - } - -} // class SchedulerPhpClient - -/* ======================================================== class definitions */ - -/** - * Example of use: - * - * / - - -// db object handling: -$CC_DBC = DB::connect($CC_CONFIG['dsn'], TRUE); -$CC_DBC->setFetchMode(DB_FETCHMODE_ASSOC); -$CC_DBC->setErrorHandling(PEAR_ERROR_RETURN); - -// scheduler client instantiation: -$spc = SchedulerPhpClient::factory($mdefs); -#$spc = SchedulerPhpClient::factory($mdefs, 0, TRUE); -if(PEAR::isError($spc)){ echo $spc->getMessage."\n"; exit; } - -// call of chosen function by name according to key values in $mdefs array: -// (for testing on storageServer XMLRPC I've changes confPrefix in -// SchedulerPhpClient constructor from 'scheduler' to 'storage' value) -#$r = $spc->LoginGB('root', 'q'); var_dump($r); -#$r = $spc->LogoutGB(''); var_dump($r); - -#$r = $spc->DisplayScheduleMethod($this->Base->sessid, '2005-01-01 00:00:00.000000', '2005-02-01 00:00:00.000000'); var_dump($r); -#$r = $spc->DisplayScheduleMethod('dummySessionId2-1681692777', '2005-01-01 00:00:00.000000', '2005-02-01 00:00:00.000000'); var_dump($r); -$r = $spc->DisplayScheduleMethod($this->Base->sessid, '20040101T00:00:00', '20050401T00:00:00'); var_dump($r); -#$r = $spc->LoginMethod('root', 'q'); var_dump($r); -#$r = $spc->LogoutMethod('dummySessionId3-1714636915'); var_dump($r); -#$r = $spc->listMethods(); var_dump($r); -#$r = $spc->GetSchedulerTimeMethod(); var_dump($r); -================= */ - diff --git a/application/models/xmlrpc/simpleGet.php b/application/models/xmlrpc/simpleGet.php deleted file mode 100644 index 19b723605..000000000 --- a/application/models/xmlrpc/simpleGet.php +++ /dev/null @@ -1,127 +0,0 @@ - - *
  • sessid : string, session ID
  • - *
  • id : string, global unique ID of requested file
  • - * - * - * On success, returns HTTP return code 200 and requested file. - * - * On errors, returns HTTP return code >200 - * The possible error codes are: - *
      - *
    • 400 - Incorrect parameters passed to method
    • - *
    • 403 - Access denied
    • - *
    • 404 - File not found
    • - *
    • 500 - Application error
    • - *
    - * - */ - -require_once(dirname(__FILE__).'/../../conf.php'); -require_once('DB.php'); -require_once(dirname(__FILE__).'/../LocStor.php'); -require_once(dirname(__FILE__).'/../MetaData.php'); - -$CC_DBC = DB::connect($CC_CONFIG['dsn'], TRUE); -$CC_DBC->setErrorHandling(PEAR_ERROR_RETURN); -$CC_DBC->setFetchMode(DB_FETCHMODE_ASSOC); - -$locStor = new LocStor(); - -function http_error($code, $err) -{ - header("HTTP/1.1 $code"); - header("Content-type: text/plain; charset=UTF-8"); - echo "$err\r\n"; - exit; -} - -/** - * This function encodes an filename to - * be transferred via HTTP header. - * - * @param string $p_string utf8 filename - * @return string HTTP header encoded filename - */ -function sg_2hexstring($p_string) -{ - for ($x=0; $x < strlen($p_string); $x++) { - $return .= '%' . bin2hex($p_string[$x]); - } - return $return; -} - -// parameter checking: -if (preg_match("|^[0-9a-fA-F]{32}$|", $_REQUEST['sessid'])) { - $sessid = $_REQUEST['sessid']; -} else { - http_error(400, "Error on sessid parameter. ({$_REQUEST['sessid']})"); -} -if (preg_match("|^[0-9a-fA-F]{16}$|", $_REQUEST['id'])) { - $gunid = $_REQUEST['id']; -} else { - http_error(400, "Error on id parameter. ({$_REQUEST['id']})"); -} - -// stored file recall: -$ac = StoredFile::RecallByGunid($gunid); -if (PEAR::isError($ac)) { - switch ($ac->getCode()) { - case GBERR_DENY: - http_error(403, "403 ".$ac->getMessage()); - case GBERR_FILENEX: - case GBERR_FOBJNEX: - http_error(404, "404 File not found"); - default: - http_error(500, "500 ".$ac->getMessage()); - } -} -$lid = BasicStor::IdFromGunid($gunid); -if (PEAR::isError($lid)) { - http_error(500, $lid->getMessage()); -} -if (($res = BasicStor::Authorize('read', $lid, $sessid)) !== TRUE) { - http_error(403, "403 Access denied"); -} -$ftype = BasicStor::GetObjType($lid); -if (PEAR::isError($ftype)) { - http_error(500, $ftype->getMessage()); -} -switch ($ftype) { - case "audioclip": - $realFname = $ac->getRealFileName(); - $mime = $ac->getMime(); - $md = new MetaData($ac->getGunId(), null); - $fileName = $md->getMetadataValue('dc:title').'.'.$ac->getFileExtension(); - header("Content-type: $mime"); - header("Content-length: ".filesize($realFname)); - header("Content-Disposition: attachment; filename*=".sg_2hexstring($fileName).";"); - readfile($realFname); - break; - case "webstream": - $url = $locStor->bsGetMetadataValue($lid, 'ls:url'); - if (empty($url)) { - http_error(500, "Unable to get ls:url value"); - } - $txt = "Location: $url"; - header($txt); - // echo "$txt\n"; - break; - case "playlist"; - // $md = $locStor->bsGetMetadata($ac->getId(), $sessid); - $md = $locStor->getAudioClip($sessid, $gunid); - // header("Content-type: text/xml"); - header("Content-type: application/smil"); - echo $md; - break; - default: - // var_dump($ftype); - http_error(500, "500 Unknown ftype ($ftype)"); -} diff --git a/application/models/xmlrpc/urldecode b/application/models/xmlrpc/urldecode deleted file mode 100755 index 172c8f73c..000000000 --- a/application/models/xmlrpc/urldecode +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/php -q - \ No newline at end of file diff --git a/application/models/xmlrpc/xrLocStor.php b/application/models/xmlrpc/xrLocStor.php deleted file mode 100644 index 5f5b55040..000000000 --- a/application/models/xmlrpc/xrLocStor.php +++ /dev/null @@ -1,172 +0,0 @@ - - - - - - -faultCode -804 - - -faultString -"); -ini_set("error_append_string", " - - - - -"); -header("Content-type: text/xml"); - -/* ================================================================= includes */ -require_once(dirname(__FILE__).'/../../conf.php'); -require_once('DB.php'); -require_once("XML/RPC/Server.php"); -require_once('XR_LocStor.php'); - -/* ============================================ setting default error handler */ -function errHndl($errno, $errmsg, $filename, $linenum, $vars) -{ - switch ($errno) { - case E_WARNING: - case E_NOTICE: - case E_USER_WARNING: - case E_USER_NOTICE: - return; - break; - default: - $xr = new XML_RPC_Response(0, 805, - htmlspecialchars("ERROR:xrLocStor: $errno $errmsg ($filename:$linenum)")); - header("Content-type: text/xml"); - echo $xr->serialize(); - exit($errno); - } -} -$old_error_handler = set_error_handler("errHndl", E_ALL); - -/* ============================================================= runable code */ -$CC_DBC = DB::connect($CC_CONFIG['dsn'], TRUE); -if (PEAR::isError($CC_DBC)) { - trigger_error("DB::connect: ".$CC_DBC->getMessage()." ".$CC_DBC->getUserInfo(),E_USER_ERROR); -} -$CC_DBC->setErrorHandling(PEAR_ERROR_RETURN); -$CC_DBC->setFetchMode(DB_FETCHMODE_ASSOC); - -$locStor = new XR_LocStor(); - -$methods = array( - 'test' => 'Tests toupper and checks sessid, params: '. - 'teststring, sessid.', - 'getVersion' => 'Get version of Airtime.', -// 'authenticate' => 'Checks authentication.', - 'login' => 'Login to storage.', - 'logout' => 'Logout from storage.', - 'existsAudioClip' => 'Checks if an audio clip with the specified '. - 'id is stored in local storage.', - 'storeAudioClipOpen' => 'Open channel to store a new audio clip '. - 'or replace an existing one.', - 'storeAudioClipClose' => 'Close channel to store a new audio clip'. - ' or replace an existing one.', - 'downloadRawAudioDataOpen'=> 'Create and return downloadable URL'. - 'for audio file', - 'downloadRawAudioDataClose'=>'Discard downloadable URL for audio file', - 'downloadMetadataOpen' => 'Create and return downloadable URL'. - 'for metadata', - 'downloadMetadataClose' => 'Discard downloadable URL for metadata', - 'openPut' => 'openPut', - 'closePut' => 'closePut', - 'deleteAudioClip' => 'Delete an existing Audio clip.', - 'updateAudioClipMetadata' => 'Update the metadata of an Audio clip '. - 'stored in Local storage.', - 'searchMetadata' => 'Search through the metadata of stored '. - 'files, return all matching clip ids.', - 'browseCategory' =>'Return values of specified metadata category.', - 'accessRawAudioData' => 'Get access to raw audio data.', - 'releaseRawAudioData' => 'Release access to raw audio data.', - 'getAudioClip' => 'Return the contents of an Audio clip.', -// 'resetStorage' => 'Reset storageServer for debugging.', - 'storeWebstream' => 'Store audio stream identified by URL', - - 'createPlaylist' => 'Create a new Playlist metafile.', - 'editPlaylist' => 'Open a Playlist metafile for editing.', - 'savePlaylist' => 'Save a Playlist metafile.', - 'revertEditedPlaylist' => 'RollBack playlist changes to the locked state.', - 'deletePlaylist' => 'Delete a Playlist metafile.', - 'accessPlaylist' => 'Open readable URL to a Playlist metafile.', - 'releasePlaylist' => 'Release readable URL from accessPlaylist.', - 'existsPlaylist' => 'Check whether a Playlist exists.', - 'playlistIsAvailable' => 'Check whether a Playlist is available '. - 'for editing.', - 'exportPlaylistOpen' => 'Create a tarfile with playlist export.', - 'exportPlaylistClose' => 'Close playlist export.', - 'importPlaylistOpen' => 'Open writable handle for playlist import.', - 'importPlaylistClose' => 'Close import-handle and import playlist.', - - 'renderPlaylistToFileOpen' => 'Render playlist to ogg file (open handle)', - 'renderPlaylistToFileCheck' => 'Render playlist to ogg file (check results)', - 'renderPlaylistToFileClose' => 'Render playlist to ogg file (close handle)', - - 'renderPlaylistToStorageOpen' => 'Render playlist to storage media clip (open handle)', - 'renderPlaylistToStorageCheck' => 'Render playlist to storage media clip (check results)', - - 'renderPlaylistToRSSOpen' => 'Render playlist to RSS file (open handle)', - 'renderPlaylistToRSSCheck' => 'Render playlist to RSS file (check results)', - 'renderPlaylistToRSSClose' => 'Render playlist to RSS file (close handle)', - - 'createBackupOpen' => 'Create backup of storage (open handle)', - 'createBackupCheck' => 'Create backup of storage (check results)', - 'createBackupClose' => 'Create backup of storage (close handle)', - - 'restoreBackupOpen' => 'Restore a backup file (open handle)', - 'restoreBackupClosePut' => 'Restore a backup file (close PUT handle)', - 'restoreBackupCheck' => 'Restore a backup file (check results)', - 'restoreBackupClose' => 'Restore a backup file (close handle)', - - 'loadPref' => 'Load user preference value.', - 'savePref' => 'Save user preference value.', - 'delPref' => 'Delete user preference record.', - 'loadGroupPref' => 'Read group preference record.', - 'saveGroupPref' => 'Delete user preference record.', - - 'getTransportInfo' => 'Common "check" method and info getter for transports', - 'turnOnOffTransports' => 'Turn transports on/off, optionaly return current state', - 'doTransportAction' => 'Pause, resume or cancel transport', - 'uploadFile2Hub' => 'Open async file transfer from local storageServer to network hub', - 'getHubInitiatedTransfers' => 'Get list of prepared transfers initiated by hub', - 'startHubInitiatedTransfer' => 'Start of download initiated by hub', - 'upload2Hub' => 'Start upload of audioclip or playlist from local storageServer to hub', - 'downloadFromHub' => 'Start download of audioclip or playlist from hub to local storageServer', -// 'globalSearch' => 'Start search job on network hub', -// 'getSearchResults' => 'Get results from search job on network hub', - - 'uploadOpen' => 'Open file-layer upload', - 'uploadCheck' => 'Check the checksum of uploaded file', - 'uploadClose' => 'Close file-layer upload', - 'downloadOpen' => 'Open file-layer download', -// 'downloadCheck' => 'Check the checksum of downloaded file', - 'downloadClose' => 'Close file-layer download', - 'prepareHubInitiatedTransfer' => 'Prepare hub initiated transfer', - 'listHubInitiatedTransfers' => 'List hub initiated transfers', - 'setHubInitiatedTransfer' => 'Set state of hub initiated transfers', - 'ping' => 'Echo request', -); - -$defs = array(); -foreach ($methods as $method => $description) { - $defs["locstor.$method"] = array( - "function" => array(&$locStor, "xr_$method"), - // NOTE: the way this signature is set up, every function must take at least one parameter! - "signature" => array( - array($GLOBALS['XML_RPC_Struct'], $GLOBALS['XML_RPC_Struct']) - ), - "docstring" => $description - ); -} - -$s = new XML_RPC_Server($defs); - - diff --git a/application/models/xmlrpc/xr_cli_test.php b/application/models/xmlrpc/xr_cli_test.php deleted file mode 100644 index 47f890ba5..000000000 --- a/application/models/xmlrpc/xr_cli_test.php +++ /dev/null @@ -1,367 +0,0 @@ -debug = 1; - echo "ServerPath: $serverPath\n"; - echo "Host: {$url['host']}, path: {$url['path']}\n"; - echo "Method: $method\n"; - echo "Parameters:\n"; - //var_dump($pars); -} - -$infos = array( - "listMethods" => array('m'=>"system.listMethods", 'p'=>NULL), - "methodHelp" => array('m'=>"system.methodHelp", 'p'=>0), - "methodSignature" => array('m'=>"system.methodSignature", 'p'=>0), - "test" => array('m'=>"locstor.test", 'p'=>array('sessid', 'teststring')), - "ping" => array('m'=>"locstor.ping", 'p'=>array("par")), - "getVersion" => array('m'=>"locstor.getVersion", 'p'=>array("str"), 'r'=>'version'), - "authenticate" => array('m'=>"locstor.authenticate", 'p'=>array('login', 'pass'), 'r'=>'authenticate'), - "login" => array('m'=>"locstor.login", 'p'=>array('login', 'pass'), 'r'=>'sessid'), - "logout" => array('m'=>"locstor.logout", 'p'=>array('sessid'), 'r'=>'status'), - "storeAudioClipOpen" => array('m'=>"locstor.storeAudioClipOpen", - 'p'=>array('sessid', 'gunid', 'metadata', 'fname', 'chsum'), - 'r'=>array('url', 'token') - ), - "storeAudioClipClose" => array('m'=>"locstor.storeAudioClipClose", - 'p'=>array('sessid', 'token'), 'r'=>'gunid'), - "accessRawAudioData" => array('m'=>"locstor.accessRawAudioData", - 'p'=>array('sessid', 'gunid'), 'r'=>array('url', 'token')), - "releaseRawAudioData" => array('m'=>"locstor.releaseRawAudioData", - 'p'=>array('token'), 'r'=>'status'), - "downloadRawAudioDataOpen" => - array('m'=>"locstor.downloadRawAudioDataOpen", - 'p'=>array('sessid', 'gunid'), 'r'=>array('url', 'token')), - "downloadRawAudioDataClose" => - array('m'=>"locstor.downloadRawAudioDataClose", - 'p'=>array('sessid', 'token'), 'r'=>'gunid'), - "downloadMetadataOpen" => array('m'=>"locstor.downloadMetadataOpen", - 'p'=>array('sessid', 'gunid'), 'r'=>array('url', 'token')), - "downloadMetadataClose" => array('m'=>"locstor.downloadMetadataClose", - 'p'=>array('sessid', 'token'), 'r'=>'gunid'), - - "deleteAudioClip" => - array('m'=>"locstor.deleteAudioClip", - 'p'=>array('sessid', 'gunid', 'forced'), 'r'=>'status'), - "existsAudioClip" => array('m'=>"locstor.existsAudioClip", - 'p'=>array('sessid', 'gunid'), 'r'=>'exists'), - "getAudioClip" => array('m'=>"locstor.getAudioClip", - 'p'=>array('sessid', 'gunid'), 'r'=>'metadata'), - "updateAudioClipMetadata" => array('m'=>"locstor.updateAudioClipMetadata", - 'p'=>array('sessid', 'gunid', 'metadata'), 'r'=>'status'), - "searchMetadata" => array('m'=>"locstor.searchMetadata", 'p'=>NULL), - "browseCategory" => array('m'=>"locstor.browseCategory", 'p'=>NULL), -// "resetStorage" => array('m'=>"locstor.resetStorage", -// 'p'=>array()), -# 'p'=>array('loadSampleData', 'invalidateSessionIds')), - "storeWebstream" => array('m'=>"locstor.storeWebstream", - 'p'=>array('sessid', 'gunid', 'metadata', 'fname', 'url'), - 'r'=>array('gunid') - ), - - "createPlaylist" => array('m'=>"locstor.createPlaylist", - 'p'=>array('sessid', 'plid', 'fname'), 'r'=>'plid'), - "editPlaylist" => array('m'=>"locstor.editPlaylist", - 'p'=>array('sessid', 'plid'), 'r'=>array('url', 'token')), - "savePlaylist" => array('m'=>"locstor.savePlaylist", - 'p'=>array('sessid', 'token', 'newPlaylist'), 'r'=>'plid'), - "revertEditedPlaylist" => array('m'=>"locstor.revertEditedPlaylist", - 'p'=>array('sessid', 'token'), 'r'=>'plid'), - "deletePlaylist" => array('m'=>"locstor.deletePlaylist", - 'p'=>array('sessid', 'plid', 'forced'), 'r'=>'status'), - "accessPlaylist" => array('m'=>"locstor.accessPlaylist", - 'p'=>array('sessid', 'plid'), 'r'=>array('url', 'token')), - "releasePlaylist" => array('m'=>"locstor.releasePlaylist", - 'p'=>array('token'), 'r'=>'plid'), - "existsPlaylist" => array('m'=>"locstor.existsPlaylist", - 'p'=>array('sessid', 'plid'), 'r'=>'exists'), - "playlistIsAvailable" => array('m'=>"locstor.playlistIsAvailable", - 'p'=>array('sessid', 'plid'), 'r'=>array('available', 'ownerid', 'ownerlogin')), - - "exportPlaylistOpen" => array('m'=>"locstor.exportPlaylistOpen", - 'p'=>array('sessid', 'plids', 'type', 'standalone'), - 'r'=>array('url', 'token')), - "exportPlaylistClose" => array('m'=>"locstor.exportPlaylistClose", - 'p'=>array('token'), 'r'=>array('status')), - "importPlaylistOpen" => array('m'=>"locstor.importPlaylistOpen", - 'p'=>array('sessid', 'chsum'), 'r'=>array('url', 'token')), - "importPlaylistClose" => array('m'=>"locstor.importPlaylistClose", - 'p'=>array('token'), 'r'=>array('gunid')), - - "renderPlaylistToFileOpen" => array('m'=>"locstor.renderPlaylistToFileOpen", - 'p'=>array('sessid', 'plid'), - 'r'=>array('token')), - "renderPlaylistToFileCheck" => array('m'=>"locstor.renderPlaylistToFileCheck", - 'p'=>array('token'), 'r'=>array('status', 'url')), - "renderPlaylistToFileClose" => array('m'=>"locstor.renderPlaylistToFileClose", - 'p'=>array('token'), 'r'=>array('status')), - "renderPlaylistToStorageOpen" => array('m'=>"locstor.renderPlaylistToStorageOpen", - 'p'=>array('sessid', 'plid'), - 'r'=>array('token')), - "renderPlaylistToStorageCheck" => array('m'=>"locstor.renderPlaylistToStorageCheck", - 'p'=>array('token'), 'r'=>array('status', 'gunid')), - "renderPlaylistToRSSOpen" => array('m'=>"locstor.renderPlaylistToRSSOpen", - 'p'=>array('sessid', 'plid'), - 'r'=>array('token')), - "renderPlaylistToRSSCheck" => array('m'=>"locstor.renderPlaylistToRSSCheck", - 'p'=>array('token'), 'r'=>array('status', 'url')), - "renderPlaylistToRSSClose" => array('m'=>"locstor.renderPlaylistToRSSClose", - 'p'=>array('token'), 'r'=>array('status')), - - "loadPref" => array('m'=>"locstor.loadPref", - 'p'=>array('sessid', 'key'), 'r'=>'value'), - "savePref" => array('m'=>"locstor.savePref", - 'p'=>array('sessid', 'key', 'value'), 'r'=>'status'), - "delPref" => array('m'=>"locstor.delPref", - 'p'=>array('sessid', 'key'), 'r'=>'status'), - "loadGroupPref" => array('m'=>"locstor.loadGroupPref", - 'p'=>array('sessid', 'group', 'key'), 'r'=>'value'), - "saveGroupPref" => array('m'=>"locstor.saveGroupPref", - 'p'=>array('sessid', 'group', 'key', 'value'), 'r'=>'status'), - - "getTransportInfo" => array('m'=>"locstor.getTransportInfo", - 'p'=>array('trtok'), - 'r'=>array('state', 'realsize', 'expectedsize', 'realsum', 'expectedsum')), - "turnOnOffTransports" => array('m'=>"locstor.turnOnOffTransports", - 'p'=>array('sessid', 'onOff'), 'r'=>array('state')), - "doTransportAction" => array('m'=>"locstor.doTransportAction", - 'p'=>array('sessid', 'trtok', 'action'), 'r'=>array('state')), - "uploadFile2Hub" => array('m'=>"locstor.uploadFile2Hub", - 'p'=>array('sessid', 'filePath'), 'r'=>array('trtok')), - "getHubInitiatedTransfers" => array('m'=>"locstor.getHubInitiatedTransfers", - 'p'=>array('sessid'), 'r'=>array()), - "startHubInitiatedTransfer" => array('m'=>"locstor.startHubInitiatedTransfer", - 'p'=>array('trtok'), 'r'=>array()), - "upload2Hub" => array('m'=>"locstor.upload2Hub", - 'p'=>array('sessid', 'gunid'), 'r'=>array('trtok')), - "downloadFromHub" => array('m'=>"locstor.downloadFromHub", - 'p'=>array('sessid', 'gunid'), 'r'=>array('trtok')), -// "globalSearch" => array('m'=>"locstor.globalSearch", -// 'p'=>array('sessid', 'criteria'), 'r'=>array('trtok')), -// "getSearchResults" => array('m'=>"locstor.getSearchResults", -// 'p'=>array('trtok')), - - "createBackupOpen" => array('m'=>"locstor.createBackupOpen", - 'p'=>array('sessid', 'criteria'), 'r'=>array('token')), - "createBackupCheck" => array('m'=>"locstor.createBackupCheck", -# 'p'=>array('token'), 'r'=>array('status', 'url', 'metafile', 'faultString')), - 'p'=>array('token'), 'r'=>array('status', 'url', 'tmpfile')), - "createBackupClose" => array('m'=>"locstor.createBackupClose", - 'p'=>array('token'), 'r'=>array('status')), - "restoreBackupOpen" => array('m'=>"locstor.restoreBackupOpen", - 'p'=>array('sessid', 'chsum'), 'r'=>array('url', 'token')), - "restoreBackupClosePut" => array('m'=>"locstor.restoreBackupClosePut", - 'p'=>array('sessid', 'token'), 'r'=>array('token')), - "restoreBackupCheck" => array('m'=>"locstor.restoreBackupCheck", - 'p'=>array('token'), 'r'=>array('status', 'faultString')), - "restoreBackupClose" => array('m'=>"locstor.restoreBackupClose", - 'p'=>array('token'), 'r'=>array('status')), - -/* - "uploadToArchive" => array('m'=>"locstor.uploadToArchive", - 'p'=>array('sessid', 'gunid'), 'r'=>'trtok'), - "downloadFromArchive" => array('m'=>"locstor.downloadFromArchive", - 'p'=>array('sessid', 'gunid'), 'r'=>'trtok'), -*/ - - "openPut" => array('m'=>"locstor.openPut", 'p'=>array()), - "closePut" => array('m'=>"locstor.closePut", 'p'=>array()), -); - - -switch ($method) { - case "searchMetadata": -// case "globalSearch": - case "createBackupOpen": - $parr = array( - 'sessid'=>$pars[0], - 'criteria'=>array( - 'filetype'=>'audioclip', - 'operator'=>'and', - 'limit'=> 0, - 'offset'=> 0, - 'conditions'=>array( - array('cat'=>$pars[1], 'op'=>'partial', 'val'=>$pars[2]) - ) - ), - ); - break; - case "browseCategory": - $parr = array( - 'sessid'=>$pars[0], - 'category'=>$pars[1], - 'criteria'=>array( - 'filetype'=>'audioclip', - 'operator'=>'and', - 'limit'=> 0, - 'offset'=> 0, - 'conditions'=>array( - array('cat'=>$pars[2], 'op'=>'partial', 'val'=>$pars[3]) - ) - ), - ); - break; -// case "resetStorage": -// $parr = array( -// 'loadSampleData'=>(boolean)$pars[0], -// 'invalidateSessionIds'=>(boolean)$pars[1], -// ); -// break; - default: - $pinfo = $infos[$method]['p']; - if (is_null($pinfo)) { - $parr = NULL; - } elseif(!is_array($pinfo)) { - $parr = $pars[0]; - #echo "pinfo not null and not array.\n"; exit; - } elseif(count($pinfo) == 0) { - $parr = (object)array(); - } else { - $parr = array(); $i=0; - foreach($pinfo as $it){ - if(isset($pars[$i])) $parr[$it] = $pars[$i]; - $i++; - } - } -} // switch - -$fullmethod = $infos[$method]['m']; -if (is_array($options)) { - $msg = new XML_RPC_Message($fullmethod, array(XML_RPC_encode($options))); -} else { - $msg = new XML_RPC_Message($fullmethod); -} -//$msg = new XML_RPC_Message($fullmethod, array(XML_RPC_encode($parr))); - -if ($verbose) { - echo "parr:\n"; - var_dump($parr); - echo "message:\n"; - echo $msg->serialize()."\n"; -} - -#$client->setDebug(1); -$res = $client->send($msg); -if ($res->faultCode() > 0) { - echo "xr_cli_test.php: ".$res->faultString()." ".$res->faultCode()."\n"; -# echo var_export($res); - exit(1); -} - -if ($verbose) { - echo "result:\n"; - echo $res->serialize(); -} - -$resp = XML_RPC_decode($res->value()); -if (isset($infos[$method]['r'])) { - $pom = $infos[$method]['r']; - if (is_array($pom)) { - foreach ($pom as $k => $it) { - $pom[$k] = $resp[$it]; - } - echo join(' ', $pom)."\n"; - } else { - switch ($pom) { - case "status": - case "exists": - echo ($resp[$pom]=='1' ? "TRUE" : "FALSE" )."\n"; - break; - default: - echo "{$resp[$pom]}\n"; - } - } -} else { - switch ($method) { - case "searchMetadata": -// case "getSearchResults": - $acCnt = 0; - $acGunids = array(); - $plCnt = 0; - $plGunids = array(); - $fld = (isset($options['category']) ? $options['category'] : 'gunid' ); - foreach ($resp['results'] as $k => $v) { - if ($v['type']=='audioclip') { - $acCnt++; - $acGunids[] = $v[$fld]; - } - if ($v['type']=='playlist') { - $plCnt++; - $plGunids[] = $v[$fld]; - } - } - echo "AC({$acCnt}): ". - join(", ", $acGunids). - " | PL({$plCnt}): ". - join(", ", $plGunids). - "\n"; - break; - case "browseCategory": - echo "RES({$resp['cnt']}): ". - join(", ", $resp['results']). - "\n"; - break; - default: - print_r($resp); - echo "\n"; - } -} - diff --git a/application/models/xmlrpc/xr_web_test.php b/application/models/xmlrpc/xr_web_test.php deleted file mode 100644 index 34537170c..000000000 --- a/application/models/xmlrpc/xr_web_test.php +++ /dev/null @@ -1,373 +0,0 @@ -'.htmlspecialchars($p_printValue)."\n"; - echo $str; - return $selected; -} // fn camp_html_select_option - - -$serverPath = - "http://{$CC_CONFIG['storageUrlHost']}:{$CC_CONFIG['storageUrlPort']}". - "{$CC_CONFIG['storageUrlPath']}/{$CC_CONFIG['storageXMLRPC']}"; -$serverPath = camp_session_get("storageserver_xmlrpc_path", $serverPath); -$f_selectedMethod = camp_session_get("f_selectedMethod", "listMethods"); -$url = parse_url($serverPath); -$client = new XML_RPC_Client($url['path'], $url['host']); - -$methodDefs = array( - "listMethods" => array('m'=>"system.listMethods", 'p'=>NULL), - "methodHelp" => array('m'=>"system.methodHelp", 'p'=>0), - "methodSignature" => array('m'=>"system.methodSignature", 'p'=>0), - "test" => array('m'=>"locstor.test", 'p'=>array('sessid', 'teststring')), - "getVersion" => array('m'=>"locstor.getVersion", 'p'=>array(), 'r'=>'version'), - "authenticate" => array('m'=>"locstor.authenticate", 'p'=>array('login', 'pass'), 'r'=>'authenticate'), - "login" => array('m'=>"locstor.login", 'p'=>array('login', 'pass'), 'r'=>'sessid'), - "logout" => array('m'=>"locstor.logout", 'p'=>array('sessid'), 'r'=>'status'), - "storeAudioClipOpen" => array('m'=>"locstor.storeAudioClipOpen", - 'p'=>array('sessid', 'gunid', 'metadata', 'fname', 'chsum'), - 'r'=>array('url', 'token') - ), - "storeAudioClipClose" => array('m'=>"locstor.storeAudioClipClose", - 'p'=>array('sessid', 'token'), 'r'=>'gunid'), - "accessRawAudioData" => array('m'=>"locstor.accessRawAudioData", - 'p'=>array('sessid', 'gunid'), 'r'=>array('url', 'token')), - "releaseRawAudioData" => array('m'=>"locstor.releaseRawAudioData", - 'p'=>array('token'), 'r'=>'status'), - "downloadRawAudioDataOpen" => - array('m'=>"locstor.downloadRawAudioDataOpen", - 'p'=>array('sessid', 'gunid'), 'r'=>array('url', 'token')), - "downloadRawAudioDataClose" => - array('m'=>"locstor.downloadRawAudioDataClose", - 'p'=>array('sessid', 'token'), 'r'=>'gunid'), - "downloadMetadataOpen" => array('m'=>"locstor.downloadMetadataOpen", - 'p'=>array('sessid', 'gunid'), 'r'=>array('url', 'token')), - "downloadMetadataClose" => array('m'=>"locstor.downloadMetadataClose", - 'p'=>array('sessid', 'token'), 'r'=>'gunid'), - - "deleteAudioClip" => - array('m'=>"locstor.deleteAudioClip", - 'p'=>array('sessid', 'gunid', 'forced'), 'r'=>'status'), - "existsAudioClip" => array('m'=>"locstor.existsAudioClip", - 'p'=>array('sessid', 'gunid'), 'r'=>'exists'), - "getAudioClip" => array('m'=>"locstor.getAudioClip", - 'p'=>array('sessid', 'gunid'), 'r'=>'metadata'), - "updateAudioClipMetadata" => array('m'=>"locstor.updateAudioClipMetadata", - 'p'=>array('sessid', 'gunid', 'metadata'), 'r'=>'status'), - "searchMetadata" => array('m'=>"locstor.searchMetadata", 'p'=>NULL), - "browseCategory" => array('m'=>"locstor.browseCategory", 'p'=>NULL), -// "resetStorage" => array('m'=>"locstor.resetStorage", -// 'p'=>array()), - "storeWebstream" => array('m'=>"locstor.storeWebstream", - 'p'=>array('sessid', 'gunid', 'metadata', 'fname', 'url'), - 'r'=>array('gunid') - ), - - "createPlaylist" => array('m'=>"locstor.createPlaylist", - 'p'=>array('sessid', 'plid', 'fname'), 'r'=>'plid'), - "editPlaylist" => array('m'=>"locstor.editPlaylist", - 'p'=>array('sessid', 'plid'), 'r'=>array('url', 'token')), - "savePlaylist" => array('m'=>"locstor.savePlaylist", - 'p'=>array('sessid', 'token', 'newPlaylist'), 'r'=>'plid'), - "revertEditedPlaylist" => array('m'=>"locstor.revertEditedPlaylist", - 'p'=>array('sessid', 'token'), 'r'=>'plid'), - "deletePlaylist" => array('m'=>"locstor.deletePlaylist", - 'p'=>array('sessid', 'plid', 'forced'), 'r'=>'status'), - "accessPlaylist" => array('m'=>"locstor.accessPlaylist", - 'p'=>array('sessid', 'plid'), 'r'=>array('url', 'token')), - "releasePlaylist" => array('m'=>"locstor.releasePlaylist", - 'p'=>array('token'), 'r'=>'plid'), - "existsPlaylist" => array('m'=>"locstor.existsPlaylist", - 'p'=>array('sessid', 'plid'), 'r'=>'exists'), - "playlistIsAvailable" => array('m'=>"locstor.playlistIsAvailable", - 'p'=>array('sessid', 'plid'), 'r'=>array('available', 'ownerid', 'ownerlogin')), - - "exportPlaylistOpen" => array('m'=>"locstor.exportPlaylistOpen", - 'p'=>array('sessid', 'plids', 'type', 'standalone'), - 'r'=>array('url', 'token')), - "exportPlaylistClose" => array('m'=>"locstor.exportPlaylistClose", - 'p'=>array('token'), 'r'=>array('status')), - "importPlaylistOpen" => array('m'=>"locstor.importPlaylistOpen", - 'p'=>array('sessid', 'chsum'), 'r'=>array('url', 'token')), - "importPlaylistClose" => array('m'=>"locstor.importPlaylistClose", - 'p'=>array('token'), 'r'=>array('gunid')), - - "renderPlaylistToFileOpen" => array('m'=>"locstor.renderPlaylistToFileOpen", - 'p'=>array('sessid', 'plid'), - 'r'=>array('token')), - "renderPlaylistToFileCheck" => array('m'=>"locstor.renderPlaylistToFileCheck", - 'p'=>array('token'), 'r'=>array('status', 'url')), - "renderPlaylistToFileClose" => array('m'=>"locstor.renderPlaylistToFileClose", - 'p'=>array('token'), 'r'=>array('status')), - "renderPlaylistToStorageOpen" => array('m'=>"locstor.renderPlaylistToStorageOpen", - 'p'=>array('sessid', 'plid'), - 'r'=>array('token')), - "renderPlaylistToStorageCheck" => array('m'=>"locstor.renderPlaylistToStorageCheck", - 'p'=>array('token'), 'r'=>array('status', 'gunid')), - "renderPlaylistToRSSOpen" => array('m'=>"locstor.renderPlaylistToRSSOpen", - 'p'=>array('sessid', 'plid'), - 'r'=>array('token')), - "renderPlaylistToRSSCheck" => array('m'=>"locstor.renderPlaylistToRSSCheck", - 'p'=>array('token'), 'r'=>array('status', 'url')), - "renderPlaylistToRSSClose" => array('m'=>"locstor.renderPlaylistToRSSClose", - 'p'=>array('token'), 'r'=>array('status')), - - "loadPref" => array('m'=>"locstor.loadPref", - 'p'=>array('sessid', 'key'), 'r'=>'value'), - "savePref" => array('m'=>"locstor.savePref", - 'p'=>array('sessid', 'key', 'value'), 'r'=>'status'), - "delPref" => array('m'=>"locstor.delPref", - 'p'=>array('sessid', 'key'), 'r'=>'status'), - "loadGroupPref" => array('m'=>"locstor.loadGroupPref", - 'p'=>array('sessid', 'group', 'key'), 'r'=>'value'), - "saveGroupPref" => array('m'=>"locstor.saveGroupPref", - 'p'=>array('sessid', 'group', 'key', 'value'), 'r'=>'status'), - - "getTransportInfo" => array('m'=>"locstor.getTransportInfo", - 'p'=>array('trtok'), - 'r'=>array('state', 'realsize', 'expectedsize', 'realsum', 'expectedsum')), - "turnOnOffTransports" => array('m'=>"locstor.turnOnOffTransports", - 'p'=>array('sessid', 'onOff'), 'r'=>array('state')), - "doTransportAction" => array('m'=>"locstor.doTransportAction", - 'p'=>array('sessid', 'trtok', 'action'), 'r'=>array('state')), - "uploadFile2Hub" => array('m'=>"locstor.uploadFile2Hub", - 'p'=>array('sessid', 'filePath'), 'r'=>array('trtok')), - "getHubInitiatedTransfers" => array('m'=>"locstor.getHubInitiatedTransfers", - 'p'=>array('sessid'), 'r'=>array()), - "startHubInitiatedTransfer" => array('m'=>"locstor.startHubInitiatedTransfer", - 'p'=>array('trtok'), 'r'=>array()), - "upload2Hub" => array('m'=>"locstor.upload2Hub", - 'p'=>array('sessid', 'gunid'), 'r'=>array('trtok')), - "downloadFromHub" => array('m'=>"locstor.downloadFromHub", - 'p'=>array('sessid', 'gunid'), 'r'=>array('trtok')), -// "globalSearch" => array('m'=>"locstor.globalSearch", -// 'p'=>array('sessid', 'criteria'), 'r'=>array('trtok')), -// "getSearchResults" => array('m'=>"locstor.getSearchResults", -// 'p'=>array('trtok')), - - "createBackupOpen" => array('m'=>"locstor.createBackupOpen", - 'p'=>array('sessid', 'criteria'), 'r'=>array('token')), - "createBackupCheck" => array('m'=>"locstor.createBackupCheck", -# 'p'=>array('token'), 'r'=>array('status', 'url', 'metafile', 'faultString')), - 'p'=>array('token'), 'r'=>array('status', 'url', 'tmpfile')), - "createBackupClose" => array('m'=>"locstor.createBackupClose", - 'p'=>array('token'), 'r'=>array('status')), - "restoreBackupOpen" => array('m'=>"locstor.restoreBackupOpen", - 'p'=>array('sessid', 'chsum'), 'r'=>array('url', 'token')), - "restoreBackupClosePut" => array('m'=>"locstor.restoreBackupClosePut", - 'p'=>array('sessid', 'token'), 'r'=>array('token')), - "restoreBackupCheck" => array('m'=>"locstor.restoreBackupCheck", - 'p'=>array('token'), 'r'=>array('status', 'faultString')), - "restoreBackupClose" => array('m'=>"locstor.restoreBackupClose", - 'p'=>array('token'), 'r'=>array('status')), - "openPut" => array('m'=>"locstor.openPut", 'p'=>array()), - "closePut" => array('m'=>"locstor.closePut", 'p'=>array()), -); - -if (isset($_REQUEST['go_button'])) { - // Get the parameters - $methodParams = $methodDefs[$f_selectedMethod]['p']; - foreach ($methodParams as $methodParamName) { - $inputParamName = "param_".$methodParamName; - $xmlParameters[$methodParamName] = $_REQUEST[$inputParamName]; - $_SESSION[$inputParamName] = $_REQUEST[$inputParamName]; - } - - // Create the XML-RPC message - $actualMethod = $methodDefs[$f_selectedMethod]['m']; - $msg = new XML_RPC_Message($actualMethod, array(XML_RPC_encode($xmlParameters))); - $sentMessage = $msg->serialize(); - - // Send it - $sendResult = $client->send($msg); - if ($sendResult->faultCode() > 0) { - $errorMsg = "xr_cli_test.php: ".$sendResult->faultString()." ".$sendResult->faultCode()."\n"; - } else { - // If successful - $xmlResponse = XML_RPC_decode($sendResult->value()); - - // Special case state handling - switch ($f_selectedMethod) { - case "login": - // Remember the login session ID so we can use it to call - // other methods. - $loggedIn = true; - $_SESSION['xmlrpc_session_id'] = $xmlResponse['sessid']; - break; - case "logout": - unset($_SESSION['xmlrpc_session_id']); - break; - case "storeAudioClipOpen": - $_SESSION['xmlrpc_token'] = $xmlResponse['token']; - $_SESSION['xmlrpc_put_url'] = $xmlResponse['url']; - break; - } - - if (isset($methodDefs[$method]['r'])) { - $expectedResult = $methodDefs[$method]['r']; - if (is_array($expectedResult)) { - foreach ($expectedResult as $resultName) { - $actualResults[$resultName] = $xmlResponse[$resultName]; - } - echo join(' ', $actualResults)."\n"; - } else { - switch ($expectedResult) { - case "status": - case "exists": - echo ($xmlResponse[$expectedResult]=='1' ? "TRUE" : "FALSE" )."\n"; - break; - default: - echo "{$xmlResponse[$expectedResult]}\n"; - } - } - } else { - switch ($method) { - case "searchMetadata": -// case "getSearchResults": - $acCnt = 0; - $acGunids = array(); - $plCnt = 0; - $plGunids = array(); - $fld = (isset($options['category']) ? $options['category'] : 'gunid' ); - foreach ($xmlResponse['results'] as $k => $v) { - if ($v['type']=='audioclip') { - $acCnt++; - $acGunids[] = $v[$fld]; - } - if ($v['type']=='playlist') { - $plCnt++; - $plGunids[] = $v[$fld]; - } - } - echo "AC({$acCnt}): ". - join(", ", $acGunids). - " | PL({$plCnt}): ". - join(", ", $plGunids). - "\n"; - break; - case "browseCategory": - echo "RES({$xmlResponse['cnt']}): ". - join(", ", $xmlResponse['results']). - "\n"; - break; - default: - //print_r($xmlResponse); - } - } - } -} -?> - - -
    -StorageServer path :
    -Method: - -
    -Parameters: -"; -} else { - echo ""; - foreach ($methodParams as $methodParamName) { - $value = ""; - if ($methodParamName == "sessid" && isset($_SESSION['xmlrpc_session_id'])) { - $value = $_SESSION['xmlrpc_session_id']; - } elseif ($methodParamName == "token" && isset($_SESSION['xmlrpc_token'])) { - $value = $_SESSION['xmlrpc_token']; - } elseif (isset($_SESSION["param_".$methodParamName])) { - $value = $_SESSION["param_".$methodParamName]; - } - echo ""; - echo ""; ?> - "; -} -?> -
    - - - - -
    "; -} -if (isset($sentMessage)) { - ?> - Sent message:
    - -
    - - Error:
    - -
    - - Response:
    - -
    - - - diff --git a/application/views/helpers/SoundCloudLink.php b/application/views/helpers/SoundCloudLink.php new file mode 100644 index 000000000..9f4d321e5 --- /dev/null +++ b/application/views/helpers/SoundCloudLink.php @@ -0,0 +1,22 @@ +getRequest(); + $host = $request->getHttpHost(); + $controller = $request->getControllerName(); + $action = $request->getActionName(); + + $redirectUrl = "http://{$host}/{$controller}/{$action}"; + + $soundcloud = new Services_Soundcloud('2CLCxcSXYzx7QhhPVHN4A', 'pZ7beWmF06epXLHVUP1ufOg2oEnIt9XhE8l8xt0bBs', $redirectUrl); + $authorizeUrl = $soundcloud->getAuthorizeUrl(); + + return $authorizeUrl; + } +} + diff --git a/application/views/scripts/airtime-recorder/index.phtml b/application/views/scripts/airtime-recorder/index.phtml new file mode 100644 index 000000000..0a04fb79c --- /dev/null +++ b/application/views/scripts/airtime-recorder/index.phtml @@ -0,0 +1 @@ +

    View script for controller AirtimeRecorder and script/action name index
    \ No newline at end of file diff --git a/application/views/scripts/recorder/get-show-schedule.phtml b/application/views/scripts/recorder/get-show-schedule.phtml new file mode 100644 index 000000000..72a5d839b --- /dev/null +++ b/application/views/scripts/recorder/get-show-schedule.phtml @@ -0,0 +1 @@ +

    View script for controller Recorder and script/action name getShowSchedule
    \ No newline at end of file diff --git a/application/views/scripts/recorder/index.phtml b/application/views/scripts/recorder/index.phtml new file mode 100644 index 000000000..60ca18d3a --- /dev/null +++ b/application/views/scripts/recorder/index.phtml @@ -0,0 +1 @@ +

    View script for controller Recorder and script/action name index
    \ No newline at end of file diff --git a/library/soundcloud-api/README.md b/library/soundcloud-api/README.md new file mode 100644 index 000000000..68df3a21a --- /dev/null +++ b/library/soundcloud-api/README.md @@ -0,0 +1,114 @@ +# SoundCloud PHP API Wrapper + +## Introduction + +A wrapper for the SoundCloud API written in PHP with support for authentication using [OAuth 2.0](http://oauth.net/2/). + +The wrapper got a real overhaul with version 2.0. The current version was written with [PEAR](http://pear.php.net/) in mind and can easily by distributed as a PEAR package. + +## Getting started + +Check out the [getting started](https://github.com/mptre/php-soundcloud/wiki/OAuth-2) wiki entry for further reference on how to get started. Also make sure to check out the [demo application](https://github.com/mptre/ci-soundcloud) for some example code. + + +## Examples + +The wrapper includes convenient methods used to perform HTTP requests on behalf of the authenticated user. Below you'll find a few quick examples. + +Ofcourse you need to handle the authentication first before being able to request and modify protect resources as demonstrated below. Therefor I refer to the [demo application](https://github.com/mptre/ci-soundcloud) which got some example code on how to handle authentication. + +### GET + +
    try {
    +    $response = json_decode($soundcloud->get('me'), true);
    +} catch (Services_Soundcloud_Invalid_Http_Response_Code_Exception $e) {
    +    exit($e->getMessage());
    +}
    + +### POST + +
    $comment = <<<EOH
    +<comment>
    +    <body>Yeah!</body>
    +</comment>
    +EOH;
    +
    +try {
    +    $response = json_decode(
    +        $soundcloud->post(
    +            'tracks/1/comments',
    +            $comment,
    +            array(CURLOPT_HTTPHEADER => array('Content-Type: application/xml'))
    +        ),
    +        true
    +    );
    +} catch (Services_Soundcloud_Invalid_Http_Response_Code_Exception $e) {
    +    exit($e->getMessage());
    +}
    + +### PUT + +
    $track = <<<EOH
    +<track>
    +    <downloadable>true</downloadable>
    +</track>
    +EOH;
    +
    +try {
    +    $response = json_decode(
    +        $soundcloud->put(
    +            'tracks/1',
    +            $track,
    +            array(CURLOPT_HTTPHEADER => array('Content-Type: application/xml'))
    +        ),
    +        true
    +    );
    +} catch (Services_Soundcloud_Invalid_Http_Response_Code_Exception $e) {
    +    exit($e->getMessage());
    +}
    + +### DELETE + +
    try {
    +    $response = json_decode($soundcloud->delete('tracks/1'), true);
    +} catch (Services_Soundcloud_Invalid_Http_Response_Code_Exception $e) {
    +    exit($e->getMessage());
    +}
    + +### DOWNLOAD TRACK + +
    try {
    +    $track = $soundcloud->download(1337);
    +} catch (Services_Soundcloud_Invalid_Http_Response_Code_Exception $e) {
    +    exit($e->getMessage());
    +}
    +
    +// do something clever with $track. Save to file perhaps?
    + +## Feedback and questions + +Found a bug or missing a feature? Don't hesitate to create a new issue here on GitHub. Or contact me [directly](https://github.com/mptre). + +Also make sure to check out the official [documentation](https://github.com/soundcloud/api/wiki/) and the join [Google Group](https://groups.google.com/group/soundcloudapi?pli=1) in order to stay updated. + +## License + +Copyright (c) 2011 Anton Lindqvist + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/library/soundcloud-api/Services/Soundcloud.php b/library/soundcloud-api/Services/Soundcloud.php new file mode 100644 index 000000000..531380151 --- /dev/null +++ b/library/soundcloud-api/Services/Soundcloud.php @@ -0,0 +1,713 @@ + + * @copyright 2010 Anton Lindqvist + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://github.com/mptre/php-soundcloud + */ +class Services_Soundcloud { + + /** + * Custom cURL option. + * + * @access public + * + * @var integer + */ + const CURLOPT_OAUTH_TOKEN = 173; + + /** + * Access token returned by the service provider after a successful authentication. + * + * @access private + * + * @var string + */ + private $_accessToken; + + /** + * Version of the API to use. + * + * @access private + * + * @var integer + */ + private static $_apiVersion = 1; + + /** + * Supported audio MIME types. + * + * @access private + * + * @var array + */ + private static $_audioMimeTypes = array( + 'aac' => 'video/mp4', + 'aiff' => 'audio/x-aiff', + 'flac' => 'audio/flac', + 'mp3' => 'audio/mpeg', + 'ogg' => 'audio/ogg', + 'wav' => 'audio/x-wav' + ); + + /** + * OAuth client id. + * + * @access private + * + * @var string + */ + private $_clientId; + + /** + * OAuth client secret. + * + * @access private + * + * @var string + */ + private $_clientSecret; + + /** + * Development mode. + * + * @access private + * + * @var boolean + */ + private $_development; + + /** + * Available API domains. + * + * @access private + * + * @var array + */ + private static $_domains = array( + 'development' => 'sandbox-soundcloud.com', + 'production' => 'soundcloud.com' + ); + + /** + * HTTP response body from the last request. + * + * @access private + * + * @var string + */ + private $_lastHttpResponseBody; + + /** + * HTTP response code from the last request. + * + * @access private + * + * @var integer + */ + private $_lastHttpResponseCode; + + /** + * HTTP response headers from last request. + * + * @access private + * + * @var array + */ + private $_lastHttpResponseHeaders; + + /** + * OAuth paths. + * + * @access private + * + * @var array + */ + private static $_paths = array( + 'authorize' => 'connect', + 'access_token' => 'oauth2/token', + ); + + /** + * OAuth redirect uri. + * + * @access private + * + * @var string + */ + private $_redirectUri; + + /** + * API response format MIME type. + * + * @access private + * + * @var string + */ + private $_requestFormat; + + /** + * Available response formats. + * + * @access private + * + * @var array + */ + private static $_responseFormats = array( + '*' => '*/*', + 'json' => 'application/json', + 'xml' => 'application/xml' + ); + + /** + * HTTP user agent. + * + * @access private + * + * @var string + */ + private static $_userAgent = 'PHP-SoundCloud'; + + /** + * Class version. + * + * @var string + */ + public $version; + + /** + * Constructor. + * + * @param string $clientId OAuth client id + * @param string $clientSecret OAuth client secret + * @param string $redirectUri OAuth redirect uri + * @param boolean $development Sandbox mode + * + * @throws Services_Soundcloud_Missing_Client_Id_Exception when missing client id + * @return void + */ + function __construct($clientId, $clientSecret, $redirectUri = null, $development = false) { + if (empty($clientId)) { + throw new Services_Soundcloud_Missing_Client_Id_Exception(); + } + + $this->_clientId = $clientId; + $this->_clientSecret = $clientSecret; + $this->_redirectUri = $redirectUri; + $this->_development = $development; + $this->_responseFormat = self::$_responseFormats['json']; + $this->version = Services_Soundcloud_Version::get(); + } + + /** + * Get authorization URL. + * + * @param array $params Optional query string parameters + * + * @return string + * @see Soundcloud::_buildUrl() + */ + function getAuthorizeUrl($params = array()) { + $defaultParams = array( + 'client_id' => $this->_clientId, + 'redirect_uri' => $this->_redirectUri, + 'response_type' => 'code' + ); + $params = array_merge($defaultParams, $params); + + return $this->_buildUrl(self::$_paths['authorize'], $params, false); + } + + /** + * Get access token URL. + * + * @param array $params Optional query string parameters + * + * @return string + * @see Soundcloud::_buildUrl() + */ + function getAccessTokenUrl($params = array()) { + return $this->_buildUrl(self::$_paths['access_token'], $params, false); + } + + /** + * Retrieve access token. + * + * @param string $code OAuth code returned from the service provider + * @param array $postData Optional post data + * @param array $curlOptions Optional cURL options + * + * @return mixed + * @see Soundcloud::_getAccessToken() + */ + function accessToken($code, $postData = array(), $curlOptions = array()) { + $defaultPostData = array( + 'code' => $code, + 'client_id' => $this->_clientId, + 'client_secret' => $this->_clientSecret, + 'redirect_uri' => $this->_redirectUri, + 'grant_type' => 'authorization_code' + ); + $postData = array_merge($defaultPostData, $postData); + + return $this->_getAccessToken($postData, $curlOptions); + } + + /** + * Refresh access token. + * + * @param string $refreshToken + * @param array $postData Optional post data + * @param array $curlOptions Optional cURL options + * + * @return mixed + * @see Soundcloud::_getAccessToken() + */ + function accessTokenRefresh($refreshToken, $postData = array(), $curlOptions = array()) { + $defaultPostData = array( + 'refresh_token' => $refreshToken, + 'client_id' => $this->_clientId, + 'client_secret' => $this->_clientSecret, + 'redirect_uri' => $this->_redirectUri, + 'grant_type' => 'refresh_token' + ); + $postData = array_merge($defaultPostData, $postData); + + return $this->_getAccessToken($postData, $curlOptions); + } + + /** + * Get access token. + * + * @return mixed + */ + function getAccessToken() { + return $this->_accessToken; + } + + /** + * Get API version. + * + * @return integer + */ + function getApiVersion() { + return self::$_apiVersion; + } + + /** + * Get the corresponding MIME type for a given file extension. + * + * @param string $extension + * + * @return string + * @throws Services_Soundcloud_Unsupported_Audio_Format_Exception if the format is unsupported + */ + function getAudioMimeType($extension) { + if (array_key_exists($extension, self::$_audioMimeTypes)) { + return self::$_audioMimeTypes[$extension]; + } else { + throw new Services_Soundcloud_Unsupported_Audio_Format_Exception(); + } + } + + /** + * Get development mode. + * + * @return boolean + */ + function getDevelopment() { + return $this->_development; + } + + /** + * Get HTTP response header. + * + * @param string $header Name of the header + * + * @return mixed + */ + function getHttpHeader($header) { + if (is_array($this->_lastHttpResponseHeaders) + && array_key_exists($header, $this->_lastHttpResponseHeaders) + ) { + return $this->_lastHttpResponseHeaders[$header]; + } else { + return false; + } + } + + /** + * Get redirect uri. + * + * @return mixed + */ + function getRedirectUri() { + return $this->_redirectUri; + } + + /** + * Get response format. + * + * @return string + */ + function getResponseFormat() { + return $this->_responseFormat; + } + + /** + * Set access token. + * + * @param string $accessToken + * + * @return object + */ + function setAccessToken($accessToken) { + $this->_accessToken = $accessToken; + + return $this; + } + + /** + * Set redirect uri. + * + * @param string $redirectUri + * + * @return object + */ + function setRedirectUri($redirectUri) { + $this->_redirectUri = $redirectUri; + + return $this; + } + + /** + * Set response format. + * + * @param string $format Could either be xml or json + * + * @throws Services_Soundcloud_Unsupported_Response_Format_Exception if the given response format isn't supported + * @return object + */ + function setResponseFormat($format) { + if (array_key_exists($format, self::$_responseFormats)) { + $this->_responseFormat = self::$_responseFormats[$format]; + } else { + throw new Services_Soundcloud_Unsupported_Response_Format_Exception(); + } + + return $this; + } + + /** + * Set development mode. + * + * @param boolean $development + * + * @return object + */ + function setDevelopment($development) { + $this->_development = $development; + + return $this; + } + + /** + * Send a GET HTTP request. + * + * @param string $path URI to request + * @param array $params Optional query string parameters + * @param array $curlOptions Optional cURL options + * + * @return mixed + * @see Soundcloud::_request() + */ + function get($path, $params = array(), $curlOptions = array()) { + $url = $this->_buildUrl($path, $params); + + return $this->_request($url, $curlOptions); + } + + /** + * Send a POST HTTP request. + * + * @param string $path URI to request + * @param array $postData Optional post data + * @param array $curlOptions Optional cURL options + * + * @return mixed + * @see Soundcloud::_request() + */ + function post($path, $postData = array(), $curlOptions = array()) { + $url = $this->_buildUrl($path); + $options = array(CURLOPT_POST => true, CURLOPT_POSTFIELDS => $postData); + $options += $curlOptions; + + return $this->_request($url, $options); + } + + /** + * Send a PUT HTTP request. + * + * @param string $path URI to request + * @param array $postData Optional post data + * @param array $curlOptions Optional cURL options + * + * @return mixed + * @see Soundcloud::_request() + */ + function put($path, $postData, $curlOptions = array()) { + $url = $this->_buildUrl($path); + $options = array( + CURLOPT_CUSTOMREQUEST => 'PUT', + CURLOPT_POSTFIELDS => $postData + ); + $options += $curlOptions; + + return $this->_request($url, $options); + } + + /** + * Send a DELETE HTTP request. + * + * @param string $path URI to request + * @param array $params Optional query string parameters + * @param array $curlOptions Optional cURL options + * + * @return mixed + * @see Soundcloud::_request() + */ + function delete($path, $params = array(), $curlOptions = array()) { + $url = $this->_buildUrl($path, $params); + $options = array(CURLOPT_CUSTOMREQUEST => 'DELETE'); + $options += $curlOptions; + + return $this->_request($url, $options); + } + + /** + * Download track. + * + * @param integer $trackId + * @param array Optional query string parameters + * @param array $curlOptions Optional cURL options + * + * @return mixed + * @see Soundcloud::_request() + */ + function download($trackId, $params = array(), $curlOptions = array()) { + $lastResponseFormat = array_pop( + preg_split('/\//', $this->getResponseFormat()) + ); + $defaultParams = array('oauth_token' => $this->getAccessToken()); + $defaultCurlOptions = array( + CURLOPT_FOLLOWLOCATION => true, + self::CURLOPT_OAUTH_TOKEN => false + ); + $url = $this->_buildUrl( + 'tracks/' . $trackId . '/download', + array_merge($defaultParams, $params) + ); + $options = $defaultCurlOptions + $curlOptions; + + $this->setResponseFormat('*'); + + $response = $this->_request($url, $options); + + // rollback to the previously defined response format. + $this->setResponseFormat($lastResponseFormat); + + return $response; + } + + /** + * Construct default HTTP headers including response format and authorization. + * + * @param boolean Include access token or not + * + * @return array $headers + */ + protected function _buildDefaultHeaders($includeAccessToken = true) { + $headers = array(); + + if ($this->_responseFormat) { + array_push($headers, 'Accept: ' . $this->_responseFormat); + } + + if ($includeAccessToken && $this->_accessToken) { + array_push($headers, 'Authorization: OAuth ' . $this->_accessToken); + } + + return $headers; + } + + /** + * Construct a URL. + * + * @param string $path Relative or absolute URI + * @param array $params Optional query string parameters + * @param boolean $includeVersion Include API version + * + * @return string $url + */ + protected function _buildUrl($path, $params = null, $includeVersion = true) { + if (preg_match('/^https?\:\/\//', $path)) { + $url = $path; + } else { + $url = 'https://'; + $url .= (!preg_match('/connect/', $path)) ? 'api.' : ''; + $url .= ($this->_development) + ? self::$_domains['development'] + : self::$_domains['production']; + $url .= '/'; + $url .= ($includeVersion) ? 'v' . self::$_apiVersion . '/' : ''; + $url .= $path; + } + + $url .= (count($params)) ? '?' . http_build_query($params) : ''; + + return $url; + } + + /** + * Retrieve access token. + * + * @param array $postData Post data + * @param array $curlOptions Optional cURL options + * + * @return mixed + */ + protected function _getAccessToken($postData, $curlOptions = array()) { + $options = array(CURLOPT_POST => true, CURLOPT_POSTFIELDS => $postData); + $options += $curlOptions; + $response = json_decode( + $this->_request($this->getAccessTokenUrl(), $options), + true + ); + + if (array_key_exists('access_token', $response)) { + $this->_accessToken = $response['access_token']; + + return $response; + } else { + return false; + } + } + + /** + * Get HTTP user agent. + * + * @access protected + * + * @return string + */ + protected function _getUserAgent() { + return self::$_userAgent . '/' . $this->version; + } + + /** + * Parse HTTP response headers. + * + * @param string $headers + * + * @return array + */ + protected function _parseHttpHeaders($headers) { + $headers = preg_split('/\n/', trim($headers)); + $parsedHeaders = array(); + + foreach ($headers as $header) { + if (!preg_match('/\:\s/', $header)) { + continue; + } + + list($key, $val) = preg_split('/\:\s/', $header, 2); + $key = str_replace('-', '_', strtolower($key)); + $val = trim($val); + + $parsedHeaders[$key] = $val; + } + + return $parsedHeaders; + } + + /** + * Validates HTTP response code. + * + * @access protected + * + * @return boolean + */ + protected function _validResponseCode($code) { + return (bool)preg_match('/^20[0-9]{1}$/', $code); + } + + /** + * Performs the actual HTTP request using curl. Can be overwritten by extending classes. + * + * @access protected + * + * @param string $url + * @param array $curlOptions Optional cURL options + * + * @throws Services_Soundcloud_Invalid_Http_Response_Code_Exception if the response code isn't valid + * @return mixed + */ + protected function _request($url, $curlOptions = array()) { + $ch = curl_init(); + $options = array( + CURLOPT_URL => $url, + CURLOPT_HEADER => true, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_USERAGENT => $this->_getUserAgent() + ); + $options += $curlOptions; + + if (array_key_exists(self::CURLOPT_OAUTH_TOKEN, $options)) { + $includeAccessToken = $options[self::CURLOPT_OAUTH_TOKEN]; + unset($options[self::CURLOPT_OAUTH_TOKEN]); + } else { + $includeAccessToken = true; + } + + if (array_key_exists(CURLOPT_HTTPHEADER, $options)) { + $options[CURLOPT_HTTPHEADER] = array_merge( + $this->_buildDefaultHeaders(), + $curlOptions[CURLOPT_HTTPHEADER] + ); + } else { + $options[CURLOPT_HTTPHEADER] = $this->_buildDefaultHeaders($includeAccessToken); + } + + curl_setopt_array($ch, $options); + + $data = curl_exec($ch); + $info = curl_getinfo($ch); + + curl_close($ch); + + $this->_lastHttpResponseHeaders = $this->_parseHttpHeaders( + substr($data, 0, $info['header_size']) + ); + $this->_lastHttpResponseBody = substr($data, $info['header_size']); + $this->_lastHttpResponseCode = $info['http_code']; + + if ($this->_validResponseCode($this->_lastHttpResponseCode)) { + return $this->_lastHttpResponseBody; + } else { + throw new Services_Soundcloud_Invalid_Http_Response_Code_Exception( + null, + 0, + $this->_lastHttpResponseBody, + $this->_lastHttpResponseCode + ); + } + } + +} diff --git a/library/soundcloud-api/Services/Soundcloud/Exception.php b/library/soundcloud-api/Services/Soundcloud/Exception.php new file mode 100644 index 000000000..76e3370ad --- /dev/null +++ b/library/soundcloud-api/Services/Soundcloud/Exception.php @@ -0,0 +1,146 @@ + + * @copyright 2010 Anton Lindqvist + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://github.com/mptre/php-soundcloud + */ +class Services_Soundcloud_Missing_Client_Id_Exception extends Exception { + + /** + * Default message. + * + * @access protected + * + * @var string + */ + protected $message = 'All requests must include a consumer key. Referred to as client_id in OAuth2.'; + +} + +/** + * Soundcloud invalid HTTP response code exception. + * + * @category Services + * @package Services_Soundcloud + * @author Anton Lindqvist + * @copyright 2010 Anton Lindqvist + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://github.com/mptre/php-soundcloud + */ +class Services_Soundcloud_Invalid_Http_Response_Code_Exception extends Exception { + + /** + * HTTP response body. + * + * @access protected + * + * @var string + */ + protected $httpBody; + + /** + * HTTP response code. + * + * @access protected + * + * @var integer + */ + protected $httpCode; + + /** + * Default message. + * + * @access protected + * + * @var string + */ + protected $message = 'The requested URL responded with HTTP code %d.'; + + /** + * Constructor. + * + * @param string $message + * @param string $code + * @param string $httpBody + * @param integer $httpCode + * + * @return void + */ + function __construct($message = null, $code = 0, $httpBody = null, $httpCode = 0) { + $this->httpBody = $httpBody; + $this->httpCode = $httpCode; + $message = sprintf($this->message, $httpCode); + + parent::__construct($message, $code); + } + + /** + * Get HTTP response body. + * + * @return mixed + */ + function getHttpBody() { + return $this->httpBody; + } + + /** + * Get HTTP response code. + * + * @return mixed + */ + function getHttpCode() { + return $this->httpCode; + } + +} + +/** + * Soundcloud unsupported response format exception. + * + * @category Services + * @package Services_Soundcloud + * @author Anton Lindqvist + * @copyright 2010 Anton Lindqvist + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://github.com/mptre/php-soundcloud + */ +class Services_Soundcloud_Unsupported_Response_Format_Exception extends Exception { + + /** + * Default message. + * + * @access protected + * + * @var string + */ + protected $message = 'The given response format is unsupported.'; + +} + +/** + * Soundcloud unsupported audio format exception. + * + * @category Services + * @package Services_Soundcloud + * @author Anton Lindqvist + * @copyright 2010 Anton Lindqvist + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://github.com/mptre/php-soundcloud + */ +class Services_Soundcloud_Unsupported_Audio_Format_Exception extends Exception { + + /** + * Default message. + * + * @access protected + * + * @var string + */ + protected $message = 'The given audio format is unsupported.'; + +} diff --git a/library/soundcloud-api/Services/Soundcloud/Version.php b/library/soundcloud-api/Services/Soundcloud/Version.php new file mode 100644 index 000000000..6ee964a23 --- /dev/null +++ b/library/soundcloud-api/Services/Soundcloud/Version.php @@ -0,0 +1,22 @@ + + * @copyright 2010 Anton Lindqvist + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://github.com/mptre/php-soundcloud + */ +class Services_Soundcloud_Version { + + const MAJOR = 2; + const MINOR = 1; + const PATCH = 1; + + public static function get() { + return implode('.', array(self::MAJOR, self::MINOR, self::PATCH)); + } + +} diff --git a/library/soundcloud-api/tests/Soundcloud_Test.php b/library/soundcloud-api/tests/Soundcloud_Test.php new file mode 100644 index 000000000..cfc3e9c4a --- /dev/null +++ b/library/soundcloud-api/tests/Soundcloud_Test.php @@ -0,0 +1,310 @@ +soundcloud = new Services_Soundcloud_Expose( + '1337', + '1337', + 'http://soundcloud.local/callback' + ); + } + + function tearDown() { + $this->soundcloud = null; + } + + function testVersionFormat() { + $this->assertRegExp( + '/^[0-9]+\.[0-9]+\.[0-9]+$/', + Services_Soundcloud_Version::get() + ); + } + + function testGetUserAgent() { + $this->assertRegExp( + '/^PHP\-SoundCloud\/[0-9]+\.[0-9]+\.[0-9]+$/', + $this->soundcloud->getUserAgent() + ); + } + + function testApiVersion() { + $this->assertEquals(1, $this->soundcloud->getApiVersion()); + } + + function testGetAudioMimeTypes() { + $supportedExtensions = array( + 'aac' => 'video/mp4', + 'aiff' => 'audio/x-aiff', + 'flac' => 'audio/flac', + 'mp3' => 'audio/mpeg', + 'ogg' => 'audio/ogg', + 'wav' => 'audio/x-wav' + ); + $unsupportedExtensions = array('gif', 'html', 'jpg', 'mp4', 'xml', 'xspf'); + + foreach ($supportedExtensions as $extension => $mimeType) { + $this->assertEquals( + $mimeType, + $this->soundcloud->getAudioMimeType($extension) + ); + } + + foreach ($unsupportedExtensions as $extension => $mimeType) { + $this->setExpectedException('Services_Soundcloud_Unsupported_Audio_Format_Exception'); + + $this->soundcloud->getAudioMimeType($extension); + } + } + + function testGetAuthorizeUrl() { + $this->assertEquals( + 'https://soundcloud.com/connect?client_id=1337&redirect_uri=http%3A%2F%2Fsoundcloud.local%2Fcallback&response_type=code', + $this->soundcloud->getAuthorizeUrl() + ); + } + + function testGetAuthorizeUrlWithCustomQueryParameters() { + $this->assertEquals( + 'https://soundcloud.com/connect?client_id=1337&redirect_uri=http%3A%2F%2Fsoundcloud.local%2Fcallback&response_type=code&foo=bar', + $this->soundcloud->getAuthorizeUrl(array('foo' => 'bar')) + ); + + $this->assertEquals( + 'https://soundcloud.com/connect?client_id=1337&redirect_uri=http%3A%2F%2Fsoundcloud.local%2Fcallback&response_type=code&foo=bar&bar=foo', + $this->soundcloud->getAuthorizeUrl(array('foo' => 'bar', 'bar' => 'foo')) + ); + } + + function testGetAccessTokenUrl() { + $this->assertEquals( + 'https://api.soundcloud.com/oauth2/token', + $this->soundcloud->getAccessTokenUrl() + ); + } + + function testSetAccessToken() { + $this->soundcloud->setAccessToken('1337'); + + $this->assertEquals('1337', $this->soundcloud->getAccessToken()); + } + + function testSetDevelopment() { + $this->soundcloud->setDevelopment(true); + + $this->assertTrue($this->soundcloud->getDevelopment()); + } + + function testSetRedirectUri() { + $this->soundcloud->setRedirectUri('http://soundcloud.local/callback'); + + $this->assertEquals( + 'http://soundcloud.local/callback', + $this->soundcloud->getRedirectUri() + ); + } + + function testDefaultResponseFormat() { + $this->assertEquals( + 'application/json', + $this->soundcloud->getResponseFormat() + ); + } + + function testSetResponseFormatHtml() { + $this->setExpectedException('Services_Soundcloud_Unsupported_Response_Format_Exception'); + + $this->soundcloud->setResponseFormat('html'); + } + + function testSetResponseFormatAll() { + $this->soundcloud->setResponseFormat('*'); + + $this->assertEquals( + '*/*', + $this->soundcloud->getResponseFormat() + ); + } + + function testSetResponseFormatJson() { + $this->soundcloud->setResponseFormat('json'); + + $this->assertEquals( + 'application/json', + $this->soundcloud->getResponseFormat() + ); + } + + function testSetResponseFormatXml() { + $this->soundcloud->setResponseFormat('xml'); + + $this->assertEquals( + 'application/xml', + $this->soundcloud->getResponseFormat() + ); + } + + function testResponseCodeSuccess() { + $this->assertTrue($this->soundcloud->validResponseCode(200)); + } + + function testResponseCodeRedirect() { + $this->assertFalse($this->soundcloud->validResponseCode(301)); + } + + function testResponseCodeClientError() { + $this->assertFalse($this->soundcloud->validResponseCode(400)); + } + + function testResponseCodeServerError() { + $this->assertFalse($this->soundcloud->validResponseCode(500)); + } + + function testBuildDefaultHeaders() { + $this->assertEquals( + array('Accept: application/json'), + $this->soundcloud->buildDefaultHeaders() + ); + } + + function testBuildDefaultHeadersWithAccessToken() { + $this->soundcloud->setAccessToken('1337'); + + $this->assertEquals( + array('Accept: application/json', 'Authorization: OAuth 1337'), + $this->soundcloud->buildDefaultHeaders() + ); + } + + function testBuildUrl() { + $this->assertEquals( + 'https://api.soundcloud.com/v1/me', + $this->soundcloud->buildUrl('me') + ); + } + + function testBuildUrlWithQueryParameters() { + $this->assertEquals( + 'https://api.soundcloud.com/v1/tracks?q=rofl+dubstep', + $this->soundcloud->buildUrl( + 'tracks', + array('q' => 'rofl dubstep') + ) + ); + + $this->assertEquals( + 'https://api.soundcloud.com/v1/tracks?q=rofl+dubstep&filter=public', + $this->soundcloud->buildUrl( + 'tracks', + array('q' => 'rofl dubstep', 'filter' => 'public') + ) + ); + } + + function testBuildUrlWithDevelopmentDomain() { + $this->soundcloud->setDevelopment(true); + + $this->assertEquals( + 'https://api.sandbox-soundcloud.com/v1/me', + $this->soundcloud->buildUrl('me') + ); + } + + function testBuildUrlWithoutApiVersion() { + $this->assertEquals( + 'https://api.soundcloud.com/me', + $this->soundcloud->buildUrl('me', null, false) + ); + } + + function testBuildUrlWithAbsoluteUrl() { + $this->assertEquals( + 'https://api.soundcloud.com/me', + $this->soundcloud->buildUrl('https://api.soundcloud.com/me') + ); + } + + /** + * @dataProvider dataProviderHttpHeaders + */ + function testParseHttpHeaders($rawHeaders, $expectedHeaders) { + $parsedHeaders = $this->soundcloud->parseHttpHeaders($rawHeaders); + + foreach ($parsedHeaders as $key => $val) { + $this->assertEquals($val, $expectedHeaders[$key]); + } + } + + function testSoundcloudMissingConsumerKeyException() { + $this->setExpectedException('Services_Soundcloud_Missing_Client_Id_Exception'); + + $soundcloud = new Services_Soundcloud('', ''); + } + + function testSoundcloudInvalidHttpResponseCodeException() { + $this->setExpectedException('Services_Soundcloud_Invalid_Http_Response_Code_Exception'); + + $this->soundcloud->get('me'); + } + + /** + * @dataProvider dataProviderSoundcloudInvalidHttpResponseCode + */ + function testSoundcloudInvalidHttpResponseCode($expectedHeaders) { + try { + $this->soundcloud->get('me'); + } catch (Services_Soundcloud_Invalid_Http_Response_Code_Exception $e) { + $this->assertEquals( + '{"error":"401 - Unauthorized"}', + $e->getHttpBody() + ); + + $this->assertEquals(401, $e->getHttpCode()); + + foreach ($expectedHeaders as $key => $val) { + $this->assertEquals( + $val, + $this->soundcloud->getHttpHeader($key) + ); + } + } + } + + static function dataProviderHttpHeaders() { + $rawHeaders = << 'Wed, 17 Nov 2010 15:39:52 GMT', + 'cache_control' => 'public', + 'content_type' => 'text/html; charset=utf-8', + 'content_encoding' => 'gzip', + 'server' => 'foobar', + 'content_length' => '1337' + ); + + return array(array($rawHeaders, $expectedHeaders)); + } + + static function dataProviderSoundcloudInvalidHttpResponseCode() { + $expectedHeaders = array( + 'server' => 'nginx', + 'content_type' => 'application/json; charset=utf-8', + 'connection' => 'keep-alive', + 'cache_control' => 'no-cache', + 'content_length' => '30' + ); + + return array(array($expectedHeaders)); + } + +} diff --git a/library/soundcloud-api/tests/Soundcloud_Test_Helper.php b/library/soundcloud-api/tests/Soundcloud_Test_Helper.php new file mode 100644 index 000000000..2959d0813 --- /dev/null +++ b/library/soundcloud-api/tests/Soundcloud_Test_Helper.php @@ -0,0 +1,94 @@ + + * @copyright 2010 Anton Lindqvist + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://github.com/mptre/php-soundcloud + */ +class Services_Soundcloud_Expose extends Services_Soundcloud { + + /** + * Class constructor. See parent constructor for further reference. + * + * @param string $clientId Application client id + * @param string $clientSecret Application client secret + * @param string $redirectUri Application redirect uri + * @param boolean $development Sandbox mode + * + * @return void + * @see Soundcloud + */ + function __construct($clientId, $clientSecret, $redirectUri = null, $development = false) { + parent::__construct($clientId, $clientSecret, $redirectUri, $development); + } + + /** + * Construct default http headers including response format and authorization. + * + * @return array + * @see Soundcloud::_buildDefaultHeaders() + */ + function buildDefaultHeaders() { + return $this->_buildDefaultHeaders(); + } + + /** + * Construct a url. + * + * @param string $path Relative or absolute uri + * @param array $params Optional query string parameters + * @param boolean $includeVersion Include the api version + * + * @return string + * @see Soundcloud::_buildUrl() + */ + function buildUrl($path, $params = null, $includeVersion = true) { + return $this->_buildUrl($path, $params, $includeVersion); + } + + /** + * Get http user agent. + * + * @return string + * @see Soundcloud::_getUserAgent() + */ + function getUserAgent() { + return $this->_getUserAgent(); + } + + /** + * Parse HTTP response headers. + * + * @param string $headers + * + * @return array + * @see Soundcloud::_parseHttpHeaders() + */ + function parseHttpHeaders($headers) { + return $this->_parseHttpHeaders($headers); + } + + /** + * Validates http response code. + * + * @return boolean + * @see Soundcloud::_validResponseCode() + */ + function validResponseCode($code) { + return $this->_validResponseCode($code); + } + +} diff --git a/python_apps/show-recorder/config.cfg b/python_apps/show-recorder/config.cfg new file mode 100644 index 000000000..86cf8d194 --- /dev/null +++ b/python_apps/show-recorder/config.cfg @@ -0,0 +1,8 @@ +# Hostname +base_url = 'http://campcaster.dev/' + +# URL to get the version number of the server API +show_schedule_url = 'Recorder/get-show-schedule/format/json' + +# base path to store recordered shows at +base_recorded_files = '/home/naomi/Music/' diff --git a/python_apps/show-recorder/testrecordscript.py b/python_apps/show-recorder/testrecordscript.py new file mode 100644 index 000000000..43bed928a --- /dev/null +++ b/python_apps/show-recorder/testrecordscript.py @@ -0,0 +1,118 @@ +#!/usr/local/bin/python +import urllib +import logging +import json +import time +import datetime + +from eci import * +from configobj import ConfigObj +import subprocess + +# loading config file +try: + config = ConfigObj('config.cfg') +except Exception, e: + print 'Error loading config file: ', e + sys.exit() + +shows_to_record = {} + + +def record_show(filelength, filename, filetype="mp3"): + + length = str(filelength)+".0" + filename = filename.replace(" ", "-") + filepath = "%s%s.%s" % (config["base_recorded_files"], filename, filetype) + + e = ECI() + + e("cs-add play_chainsetup") + e("c-add 1st_chain") + e("ai-add alsa") + e("ao-add "+filepath) + e("cs-set-length "+length) + e("cop-select 1") + e("cs-connect") + e("start") + + while 1: + time.sleep(1) + + if e("engine-status") != "running": + break + + e("stop") + e("cs-disconnect") + + return filepath + + +def getDateTimeObj(time): + + timeinfo = time.split(" ") + date = timeinfo[0].split("-") + time = timeinfo[1].split(":") + + return datetime.datetime(int(date[0]), int(date[1]), int(date[2]), int(time[0]), int(time[1]), int(time[2])) + +def process_shows(shows): + + for show in shows: + show_starts = getDateTimeObj(show[u'starts']) + show_end = getDateTimeObj(show[u'ends']) + time_delta = show_end - show_starts + + shows_to_record[show[u'starts']] = time_delta + + +def check_record(): + + tnow = datetime.datetime.now() + sorted_show_keys = sorted(shows_to_record.keys()) + start_time = sorted_show_keys[0] + next_show = getDateTimeObj(start_time) + + #print tnow, next_show + + #tnow = getDateTimeObj("2011-03-04 16:00:00") + #next_show = getDateTimeObj("2011-03-04 16:00:01") + + delta = next_show - tnow + + if delta <= datetime.timedelta(seconds=60): + time.sleep(delta.seconds) + + show_length = shows_to_record[start_time] + filepath = record_show(show_length.seconds, start_time) + #filepath = record_show(10, "2011-03-04 16:00:00") + + command = "%s -c %s" %("../../utils/airtime-import", filepath) + subprocess.call([command],shell=True) + + +def get_shows(): + + url = config["base_url"] + config["show_schedule_url"] + #url = url.replace("%%from%%", "2011-03-13 20:00:00") + #url = url.replace("%%to%%", "2011-04-17 21:00:00") + + response = urllib.urlopen(url) + data = response.read() + response_json = json.loads(data) + shows = response_json[u'shows'] + print shows + + if len(shows): + process_shows(shows) + check_record() + + +if __name__ == '__main__': + + while True: + get_shows() + time.sleep(30) + + + diff --git a/python_apps/show-recorder/testsoundcloud.py b/python_apps/show-recorder/testsoundcloud.py new file mode 100644 index 000000000..0ba6fd20d --- /dev/null +++ b/python_apps/show-recorder/testsoundcloud.py @@ -0,0 +1,59 @@ +import webbrowser +import scapi + +# the host to connect to. Normally, this +# would be api.soundcloud.com +API_HOST = "api.soundcloud.com" + +# This needs to be the consumer ID you got from +# http://soundcloud.com/settings/applications/new +CONSUMER = "2CLCxcSXYzx7QhhPVHN4A" +# This needs to be the consumer secret password you got from +# http://soundcloud.com/settings/applications/new +CONSUMER_SECRET = "pZ7beWmF06epXLHVUP1ufOg2oEnIt9XhE8l8xt0bBs" + +# first, we create an OAuthAuthenticator that only knows about consumer +# credentials. This is done so that we can get an request-token as +# first step. +oauth_authenticator = scapi.authentication.OAuthAuthenticator(CONSUMER, + CONSUMER_SECRET, + None, + None) + +# The connector works with the authenticator to create and sign the requests. It +# has some helper-methods that allow us to do the OAuth-dance. +connector = scapi.ApiConnector(host=API_HOST, authenticator=oauth_authenticator) + +# First step is to get a request-token, and to let the user authorize that +# via the browser. +token, secret = connector.fetch_request_token() +authorization_url = connector.get_request_token_authorization_url(token) +webbrowser.open(authorization_url) +oauth_verifier = raw_input("please enter verifier code as seen in the browser:") + +# Now we create a new authenticator with the temporary token & secret we got from +# the request-token. This will give us the access-token +oauth_authenticator = scapi.authentication.OAuthAuthenticator(CONSUMER, + CONSUMER_SECRET, + token, + secret) + +# we need a new connector with the new authenticator! +connector = scapi.ApiConnector(API_HOST, authenticator=oauth_authenticator) +token, secret = connector.fetch_access_token(oauth_verifier) + + +# now we are finally ready to go - with all four parameters OAuth requires, +# we can setup an authenticator that allows for actual API-calls. +oauth_authenticator = scapi.authentication.OAuthAuthenticator(CONSUMER, + CONSUMER_SECRET, + token, + secret) + +# we pass the connector to a Scope - a Scope is essentially a path in the REST-url-space. +# Without any path-component, it's the root from which we can then query into the +# resources. +root = scapi.Scope(scapi.ApiConnector(host=API_HOST, authenticator=oauth_authenticator)) + +# Hey, nice meeting you! Connected to SoundCloud using OAuth will allow you to access protected resources, like the current user's name. +print "Hello, %s" % root.me().username diff --git a/python_apps/soundcloud-api/AUTHORS b/python_apps/soundcloud-api/AUTHORS new file mode 100644 index 000000000..ae92c1d26 --- /dev/null +++ b/python_apps/soundcloud-api/AUTHORS @@ -0,0 +1,5 @@ +Authors +------- + +Diez B. Roggisch, deets@web.de + diff --git a/python_apps/soundcloud-api/ChangeLog b/python_apps/soundcloud-api/ChangeLog new file mode 100644 index 000000000..9b5bb4679 --- /dev/null +++ b/python_apps/soundcloud-api/ChangeLog @@ -0,0 +1,9 @@ +2009-09-10 Diez Roggisch + + * OAuth 1.0a working + * Query-Parameters for GET-requests to allow e.g. filtering + * Setting file-objects as attributes working. + * share to emails working. + * groups + * downloading/streaming private tracks + diff --git a/python_apps/soundcloud-api/LICENSE b/python_apps/soundcloud-api/LICENSE new file mode 100644 index 000000000..3b473dbfc --- /dev/null +++ b/python_apps/soundcloud-api/LICENSE @@ -0,0 +1,458 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS diff --git a/python_apps/soundcloud-api/README b/python_apps/soundcloud-api/README new file mode 100644 index 000000000..729a8faac --- /dev/null +++ b/python_apps/soundcloud-api/README @@ -0,0 +1,45 @@ +Running tests +============= + +The **SCAPI** comes with a small testsuite. It can be run automatically through either setuptools_ +or nose_. + +Configuring tests +----------------- + +Before you can run the tests, you need to configure them. You do this using the `test.ini` file in the +root of python **SCAPI** workingcopy. + +Running tests through setuptools +-------------------------------- + +You can run the whole testsuite through setuptools_ by doing :: + + host:~/SoundCloudAPI deets$ python setup.py test + +Running tests through nose +-------------------------- + +If you want a more fine-grained control over which tests to run, you can use the `nosetests`-commandline tool. + +Then to run individual tests, you can e.g. do:: + + host:~/SoundCloudAPI deets$ nosetests -s scapi.tests.scapi_tests:SCAPITests.test_setting_permissions + + +See the nose_-website for more options. + + + +.. _nose: http://somethingaboutorange.com/mrl/projects/nose/ +.. _setuptools: http://peak.telecommunity.com/DevCenter/setuptools +.. _ConfigObj: http://www.voidspace.org.uk/python/configobj.html + + +Creating the API-docs +===================== + +Do:: + epydoc -v --name="SoundCloud API" --html -o docs/api scapi --exclude="os|mimetypes|urllib2|exceptions|mimetools" + + diff --git a/python_apps/soundcloud-api/bootstrap.py b/python_apps/soundcloud-api/bootstrap.py new file mode 100644 index 000000000..c04723c5b --- /dev/null +++ b/python_apps/soundcloud-api/bootstrap.py @@ -0,0 +1,58 @@ +import webbrowser +import scapi + +# the host to connect to. Normally, this +# would be api.soundcloud.com +API_HOST = "api.sandbox-soundcloud.com" + +# This needs to be the consumer ID you got from +# http://soundcloud.com/settings/applications/new +CONSUMER = "gLnhFeUBnBCZF8a6Ngqq7w" +# This needs to be the consumer secret password you got from +# http://soundcloud.com/settings/applications/new +CONSUMER_SECRET = "nbWRdG5X9xUb63l4nIeFYm3nmeVJ2v4s1ROpvRSBvU8" + +# first, we create an OAuthAuthenticator that only knows about consumer +# credentials. This is done so that we can get an request-token as +# first step. +oauth_authenticator = scapi.authentication.OAuthAuthenticator(CONSUMER, + CONSUMER_SECRET, + None, + None) + +# The connector works with the authenticator to create and sign the requests. It +# has some helper-methods that allow us to do the OAuth-dance. +connector = scapi.ApiConnector(host=API_HOST, authenticator=oauth_authenticator) + +# First step is to get a request-token, and to let the user authorize that +# via the browser. +token, secret = connector.fetch_request_token() +authorization_url = connector.get_request_token_authorization_url(token) +webbrowser.open(authorization_url) +oauth_verifier = raw_input("please enter verifier code as seen in the browser:") + +# Now we create a new authenticator with the temporary token & secret we got from +# the request-token. This will give us the access-token +oauth_authenticator = scapi.authentication.OAuthAuthenticator(CONSUMER, + CONSUMER_SECRET, + token, + secret) + +# we need a new connector with the new authenticator! +connector = scapi.ApiConnector(API_HOST, authenticator=oauth_authenticator) +token, secret = connector.fetch_access_token(oauth_verifier) + +# now we are finally ready to go - with all four parameters OAuth requires, +# we can setup an authenticator that allows for actual API-calls. +oauth_authenticator = scapi.authentication.OAuthAuthenticator(CONSUMER, + CONSUMER_SECRET, + token, + secret) + +# we pass the connector to a Scope - a Scope is essentiall a path in the REST-url-space. +# Without any path-component, it's the root from which we can then query into the +# resources. +root = scapi.Scope(scapi.ApiConnector(host=API_HOST, authenticator=oauth_authenticator)) + +# Hey, nice meeting you! +print "Hello, %s" % root.me().username diff --git a/python_apps/soundcloud-api/docs/api/api-objects.txt b/python_apps/soundcloud-api/docs/api/api-objects.txt new file mode 100644 index 000000000..8f3e21555 --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/api-objects.txt @@ -0,0 +1,333 @@ +scapi scapi-module.html +scapi.escape scapi.util-module.html#escape +scapi.USE_PROXY scapi-module.html#USE_PROXY +scapi.REQUEST_TOKEN_URL scapi-module.html#REQUEST_TOKEN_URL +scapi.PROXY scapi-module.html#PROXY +scapi.ACCESS_TOKEN_URL scapi-module.html#ACCESS_TOKEN_URL +scapi.register_classes scapi-module.html#register_classes +scapi.logger scapi-module.html#logger +scapi.AUTHORIZATION_URL scapi-module.html#AUTHORIZATION_URL +scapi.authentication scapi.authentication-module.html +scapi.authentication.USE_DOUBLE_ESCAPE_HACK scapi.authentication-module.html#USE_DOUBLE_ESCAPE_HACK +scapi.authentication.escape scapi.util-module.html#escape +scapi.authentication.logger scapi.authentication-module.html#logger +scapi.config scapi.config-module.html +scapi.json scapi.json-module.html +scapi.json.read scapi.json-module.html#read +scapi.json.write scapi.json-module.html#write +scapi.multidict scapi.multidict-module.html +scapi.tests scapi.tests-module.html +scapi.tests.scapi_tests scapi.tests.scapi_tests-module.html +scapi.tests.scapi_tests.api_logger scapi.tests.scapi_tests-module.html#api_logger +scapi.tests.scapi_tests.logger scapi.tests.scapi_tests-module.html#logger +scapi.tests.test_connect scapi.tests.test_connect-module.html +scapi.tests.test_connect.test_me_having_stress scapi.tests.test_connect-module.html#test_me_having_stress +scapi.tests.test_connect.test_scoped_track_creation scapi.tests.test_connect-module.html#test_scoped_track_creation +scapi.tests.test_connect.test_permissions scapi.tests.test_connect-module.html#test_permissions +scapi.tests.test_connect.CONSUMER_SECRET scapi.tests.test_connect-module.html#CONSUMER_SECRET +scapi.tests.test_connect.CONSUMER scapi.tests.test_connect-module.html#CONSUMER +scapi.tests.test_connect.load_config scapi.tests.test_connect-module.html#load_config +scapi.tests.test_connect.test_upload scapi.tests.test_connect-module.html#test_upload +scapi.tests.test_connect.USER scapi.tests.test_connect-module.html#USER +scapi.tests.test_connect.test_load_config scapi.tests.test_connect-module.html#test_load_config +scapi.tests.test_connect.test_access_token_acquisition scapi.tests.test_connect-module.html#test_access_token_acquisition +scapi.tests.test_connect.test_track_creation scapi.tests.test_connect-module.html#test_track_creation +scapi.tests.test_connect.test_setting_comments scapi.tests.test_connect-module.html#test_setting_comments +scapi.tests.test_connect.test_track_update scapi.tests.test_connect-module.html#test_track_update +scapi.tests.test_connect.SECRET scapi.tests.test_connect-module.html#SECRET +scapi.tests.test_connect.test_contact_add_and_removal scapi.tests.test_connect-module.html#test_contact_add_and_removal +scapi.tests.test_connect.logger scapi.tests.test_connect-module.html#logger +scapi.tests.test_connect.test_connect scapi.tests.test_connect-module.html#test_connect +scapi.tests.test_connect.ROOT scapi.tests.test_connect-module.html#ROOT +scapi.tests.test_connect.test_contact_list scapi.tests.test_connect-module.html#test_contact_list +scapi.tests.test_connect.test_playlists scapi.tests.test_connect-module.html#test_playlists +scapi.tests.test_connect.API_HOST scapi.tests.test_connect-module.html#API_HOST +scapi.tests.test_connect._logger scapi.tests.test_connect-module.html#_logger +scapi.tests.test_connect.TOKEN scapi.tests.test_connect-module.html#TOKEN +scapi.tests.test_connect.test_events scapi.tests.test_connect-module.html#test_events +scapi.tests.test_connect.test_non_global_api scapi.tests.test_connect-module.html#test_non_global_api +scapi.tests.test_connect.test_setting_permissions scapi.tests.test_connect-module.html#test_setting_permissions +scapi.tests.test_connect.PASSWORD scapi.tests.test_connect-module.html#PASSWORD +scapi.tests.test_connect.test_setting_comments_the_way_shawn_says_its_correct scapi.tests.test_connect-module.html#test_setting_comments_the_way_shawn_says_its_correct +scapi.tests.test_connect.test_large_list scapi.tests.test_connect-module.html#test_large_list +scapi.tests.test_connect.CONNECTOR scapi.tests.test_connect-module.html#CONNECTOR +scapi.tests.test_connect.CONFIG_NAME scapi.tests.test_connect-module.html#CONFIG_NAME +scapi.tests.test_connect.RUN_INTERACTIVE_TESTS scapi.tests.test_connect-module.html#RUN_INTERACTIVE_TESTS +scapi.tests.test_connect.setup scapi.tests.test_connect-module.html#setup +scapi.tests.test_connect.test_favorites scapi.tests.test_connect-module.html#test_favorites +scapi.tests.test_connect.USE_OAUTH scapi.tests.test_connect-module.html#USE_OAUTH +scapi.tests.test_oauth scapi.tests.test_oauth-module.html +scapi.tests.test_oauth._logger scapi.tests.test_oauth-module.html#_logger +scapi.tests.test_oauth.test_oauth_connect scapi.tests.test_oauth-module.html#test_oauth_connect +scapi.tests.test_oauth.TOKEN scapi.tests.test_oauth-module.html#TOKEN +scapi.tests.test_oauth.test_base64_connect scapi.tests.test_oauth-module.html#test_base64_connect +scapi.tests.test_oauth.CONSUMER_SECRET scapi.tests.test_oauth-module.html#CONSUMER_SECRET +scapi.tests.test_oauth.SECRET scapi.tests.test_oauth-module.html#SECRET +scapi.tests.test_oauth.logger scapi.tests.test_oauth-module.html#logger +scapi.tests.test_oauth.CONSUMER scapi.tests.test_oauth-module.html#CONSUMER +scapi.util scapi.util-module.html +scapi.util.escape scapi.util-module.html#escape +exceptions.AssertionError exceptions.AssertionError-class.html +exceptions.AssertionError.__init__ exceptions.AssertionError-class.html#__init__ +exceptions.AssertionError.__new__ exceptions.AssertionError-class.html#__new__ +scapi.ApiConnector scapi.ApiConnector-class.html +scapi.ApiConnector.fetch_access_token scapi.ApiConnector-class.html#fetch_access_token +scapi.ApiConnector.LIST_LIMIT scapi.ApiConnector-class.html#LIST_LIMIT +scapi.ApiConnector.LIST_LIMIT_PARAMETER scapi.ApiConnector-class.html#LIST_LIMIT_PARAMETER +scapi.ApiConnector.fetch_request_token scapi.ApiConnector-class.html#fetch_request_token +scapi.ApiConnector.get_request_token_authorization_url scapi.ApiConnector-class.html#get_request_token_authorization_url +scapi.ApiConnector.normalize_method scapi.ApiConnector-class.html#normalize_method +scapi.ApiConnector.__init__ scapi.ApiConnector-class.html#__init__ +scapi.ApiConnector.LIST_OFFSET_PARAMETER scapi.ApiConnector-class.html#LIST_OFFSET_PARAMETER +scapi.Comment scapi.Comment-class.html +scapi.RESTBase._scope scapi.RESTBase-class.html#_scope +scapi.RESTBase.__init__ scapi.RESTBase-class.html#__init__ +scapi.Comment.KIND scapi.Comment-class.html#KIND +scapi.RESTBase.create scapi.RESTBase-class.html#create +scapi.RESTBase.get scapi.RESTBase-class.html#get +scapi.RESTBase.__getattr__ scapi.RESTBase-class.html#__getattr__ +scapi.RESTBase.ALL_DOMAIN_CLASSES scapi.RESTBase-class.html#ALL_DOMAIN_CLASSES +scapi.RESTBase.new scapi.RESTBase-class.html#new +scapi.RESTBase.ALIASES scapi.RESTBase-class.html#ALIASES +scapi.RESTBase.__ne__ scapi.RESTBase-class.html#__ne__ +scapi.RESTBase._as_arguments scapi.RESTBase-class.html#_as_arguments +scapi.RESTBase._convert_value scapi.RESTBase-class.html#_convert_value +scapi.RESTBase.__setattr__ scapi.RESTBase-class.html#__setattr__ +scapi.RESTBase._singleton scapi.RESTBase-class.html#_singleton +scapi.RESTBase.REGISTRY scapi.RESTBase-class.html#REGISTRY +scapi.RESTBase.__eq__ scapi.RESTBase-class.html#__eq__ +scapi.RESTBase.__repr__ scapi.RESTBase-class.html#__repr__ +scapi.RESTBase.__hash__ scapi.RESTBase-class.html#__hash__ +scapi.Event scapi.Event-class.html +scapi.RESTBase._scope scapi.RESTBase-class.html#_scope +scapi.RESTBase.__init__ scapi.RESTBase-class.html#__init__ +scapi.Event.KIND scapi.Event-class.html#KIND +scapi.RESTBase.create scapi.RESTBase-class.html#create +scapi.RESTBase.get scapi.RESTBase-class.html#get +scapi.RESTBase.__getattr__ scapi.RESTBase-class.html#__getattr__ +scapi.RESTBase.ALL_DOMAIN_CLASSES scapi.RESTBase-class.html#ALL_DOMAIN_CLASSES +scapi.RESTBase.new scapi.RESTBase-class.html#new +scapi.RESTBase.ALIASES scapi.RESTBase-class.html#ALIASES +scapi.RESTBase.__ne__ scapi.RESTBase-class.html#__ne__ +scapi.RESTBase._as_arguments scapi.RESTBase-class.html#_as_arguments +scapi.RESTBase._convert_value scapi.RESTBase-class.html#_convert_value +scapi.RESTBase.__setattr__ scapi.RESTBase-class.html#__setattr__ +scapi.RESTBase._singleton scapi.RESTBase-class.html#_singleton +scapi.RESTBase.REGISTRY scapi.RESTBase-class.html#REGISTRY +scapi.RESTBase.__eq__ scapi.RESTBase-class.html#__eq__ +scapi.RESTBase.__repr__ scapi.RESTBase-class.html#__repr__ +scapi.RESTBase.__hash__ scapi.RESTBase-class.html#__hash__ +scapi.Group scapi.Group-class.html +scapi.RESTBase._scope scapi.RESTBase-class.html#_scope +scapi.RESTBase.__init__ scapi.RESTBase-class.html#__init__ +scapi.Group.KIND scapi.Group-class.html#KIND +scapi.RESTBase.create scapi.RESTBase-class.html#create +scapi.RESTBase.get scapi.RESTBase-class.html#get +scapi.RESTBase.__getattr__ scapi.RESTBase-class.html#__getattr__ +scapi.RESTBase.ALL_DOMAIN_CLASSES scapi.RESTBase-class.html#ALL_DOMAIN_CLASSES +scapi.RESTBase.new scapi.RESTBase-class.html#new +scapi.RESTBase.ALIASES scapi.RESTBase-class.html#ALIASES +scapi.RESTBase.__ne__ scapi.RESTBase-class.html#__ne__ +scapi.RESTBase._as_arguments scapi.RESTBase-class.html#_as_arguments +scapi.RESTBase._convert_value scapi.RESTBase-class.html#_convert_value +scapi.RESTBase.__setattr__ scapi.RESTBase-class.html#__setattr__ +scapi.RESTBase._singleton scapi.RESTBase-class.html#_singleton +scapi.RESTBase.REGISTRY scapi.RESTBase-class.html#REGISTRY +scapi.RESTBase.__eq__ scapi.RESTBase-class.html#__eq__ +scapi.RESTBase.__repr__ scapi.RESTBase-class.html#__repr__ +scapi.RESTBase.__hash__ scapi.RESTBase-class.html#__hash__ +scapi.InvalidMethodException scapi.InvalidMethodException-class.html +scapi.InvalidMethodException.__repr__ scapi.InvalidMethodException-class.html#__repr__ +scapi.InvalidMethodException.__init__ scapi.InvalidMethodException-class.html#__init__ +scapi.NoResultFromRequest scapi.NoResultFromRequest-class.html +scapi.Playlist scapi.Playlist-class.html +scapi.RESTBase._scope scapi.RESTBase-class.html#_scope +scapi.RESTBase.__init__ scapi.RESTBase-class.html#__init__ +scapi.Playlist.KIND scapi.Playlist-class.html#KIND +scapi.RESTBase.create scapi.RESTBase-class.html#create +scapi.RESTBase.get scapi.RESTBase-class.html#get +scapi.RESTBase.__getattr__ scapi.RESTBase-class.html#__getattr__ +scapi.RESTBase.ALL_DOMAIN_CLASSES scapi.RESTBase-class.html#ALL_DOMAIN_CLASSES +scapi.RESTBase.new scapi.RESTBase-class.html#new +scapi.RESTBase.ALIASES scapi.RESTBase-class.html#ALIASES +scapi.RESTBase.__ne__ scapi.RESTBase-class.html#__ne__ +scapi.RESTBase._as_arguments scapi.RESTBase-class.html#_as_arguments +scapi.RESTBase._convert_value scapi.RESTBase-class.html#_convert_value +scapi.RESTBase.__setattr__ scapi.RESTBase-class.html#__setattr__ +scapi.RESTBase._singleton scapi.RESTBase-class.html#_singleton +scapi.RESTBase.REGISTRY scapi.RESTBase-class.html#REGISTRY +scapi.RESTBase.__eq__ scapi.RESTBase-class.html#__eq__ +scapi.RESTBase.__repr__ scapi.RESTBase-class.html#__repr__ +scapi.RESTBase.__hash__ scapi.RESTBase-class.html#__hash__ +scapi.RESTBase scapi.RESTBase-class.html +scapi.RESTBase._scope scapi.RESTBase-class.html#_scope +scapi.RESTBase.__init__ scapi.RESTBase-class.html#__init__ +scapi.RESTBase.__setattr__ scapi.RESTBase-class.html#__setattr__ +scapi.RESTBase.create scapi.RESTBase-class.html#create +scapi.RESTBase.get scapi.RESTBase-class.html#get +scapi.RESTBase.__getattr__ scapi.RESTBase-class.html#__getattr__ +scapi.RESTBase.ALL_DOMAIN_CLASSES scapi.RESTBase-class.html#ALL_DOMAIN_CLASSES +scapi.RESTBase.new scapi.RESTBase-class.html#new +scapi.RESTBase.ALIASES scapi.RESTBase-class.html#ALIASES +scapi.RESTBase.__ne__ scapi.RESTBase-class.html#__ne__ +scapi.RESTBase._as_arguments scapi.RESTBase-class.html#_as_arguments +scapi.RESTBase._convert_value scapi.RESTBase-class.html#_convert_value +scapi.RESTBase.KIND scapi.RESTBase-class.html#KIND +scapi.RESTBase._singleton scapi.RESTBase-class.html#_singleton +scapi.RESTBase.REGISTRY scapi.RESTBase-class.html#REGISTRY +scapi.RESTBase.__eq__ scapi.RESTBase-class.html#__eq__ +scapi.RESTBase.__repr__ scapi.RESTBase-class.html#__repr__ +scapi.RESTBase.__hash__ scapi.RESTBase-class.html#__hash__ +scapi.SCRedirectHandler scapi.SCRedirectHandler-class.html +scapi.SCRedirectHandler.alternate_method scapi.SCRedirectHandler-class.html#alternate_method +scapi.SCRedirectHandler.http_error_303 scapi.SCRedirectHandler-class.html#http_error_303 +scapi.SCRedirectHandler.http_error_201 scapi.SCRedirectHandler-class.html#http_error_201 +scapi.Scope scapi.Scope-class.html +scapi.Scope.oauth_sign_get_request scapi.Scope-class.html#oauth_sign_get_request +scapi.Scope._map scapi.Scope-class.html#_map +scapi.Scope.__str__ scapi.Scope-class.html#__str__ +scapi.Scope.__getattr__ scapi.Scope-class.html#__getattr__ +scapi.Scope._call scapi.Scope-class.html#_call +scapi.Scope._create_query_string scapi.Scope-class.html#_create_query_string +scapi.Scope._create_request scapi.Scope-class.html#_create_request +scapi.Scope._get_connector scapi.Scope-class.html#_get_connector +scapi.Scope.__init__ scapi.Scope-class.html#__init__ +scapi.Scope.__repr__ scapi.Scope-class.html#__repr__ +scapi.Track scapi.Track-class.html +scapi.RESTBase._scope scapi.RESTBase-class.html#_scope +scapi.RESTBase.__init__ scapi.RESTBase-class.html#__init__ +scapi.Track.KIND scapi.Track-class.html#KIND +scapi.RESTBase.create scapi.RESTBase-class.html#create +scapi.RESTBase._as_arguments scapi.RESTBase-class.html#_as_arguments +scapi.RESTBase.__getattr__ scapi.RESTBase-class.html#__getattr__ +scapi.RESTBase.ALL_DOMAIN_CLASSES scapi.RESTBase-class.html#ALL_DOMAIN_CLASSES +scapi.RESTBase.new scapi.RESTBase-class.html#new +scapi.Track.ALIASES scapi.Track-class.html#ALIASES +scapi.RESTBase.__ne__ scapi.RESTBase-class.html#__ne__ +scapi.RESTBase.get scapi.RESTBase-class.html#get +scapi.RESTBase._convert_value scapi.RESTBase-class.html#_convert_value +scapi.RESTBase.__setattr__ scapi.RESTBase-class.html#__setattr__ +scapi.RESTBase._singleton scapi.RESTBase-class.html#_singleton +scapi.RESTBase.REGISTRY scapi.RESTBase-class.html#REGISTRY +scapi.RESTBase.__eq__ scapi.RESTBase-class.html#__eq__ +scapi.RESTBase.__repr__ scapi.RESTBase-class.html#__repr__ +scapi.RESTBase.__hash__ scapi.RESTBase-class.html#__hash__ +scapi.UnknownContentType scapi.UnknownContentType-class.html +scapi.UnknownContentType.__str__ scapi.UnknownContentType-class.html#__str__ +scapi.UnknownContentType.__repr__ scapi.UnknownContentType-class.html#__repr__ +scapi.UnknownContentType.__init__ scapi.UnknownContentType-class.html#__init__ +scapi.User scapi.User-class.html +scapi.RESTBase._scope scapi.RESTBase-class.html#_scope +scapi.RESTBase.__init__ scapi.RESTBase-class.html#__init__ +scapi.User.KIND scapi.User-class.html#KIND +scapi.RESTBase.create scapi.RESTBase-class.html#create +scapi.RESTBase._as_arguments scapi.RESTBase-class.html#_as_arguments +scapi.RESTBase.__getattr__ scapi.RESTBase-class.html#__getattr__ +scapi.RESTBase.ALL_DOMAIN_CLASSES scapi.RESTBase-class.html#ALL_DOMAIN_CLASSES +scapi.RESTBase.new scapi.RESTBase-class.html#new +scapi.User.ALIASES scapi.User-class.html#ALIASES +scapi.RESTBase.__ne__ scapi.RESTBase-class.html#__ne__ +scapi.RESTBase.get scapi.RESTBase-class.html#get +scapi.RESTBase._convert_value scapi.RESTBase-class.html#_convert_value +scapi.RESTBase.__setattr__ scapi.RESTBase-class.html#__setattr__ +scapi.RESTBase._singleton scapi.RESTBase-class.html#_singleton +scapi.RESTBase.REGISTRY scapi.RESTBase-class.html#REGISTRY +scapi.RESTBase.__eq__ scapi.RESTBase-class.html#__eq__ +scapi.RESTBase.__repr__ scapi.RESTBase-class.html#__repr__ +scapi.RESTBase.__hash__ scapi.RESTBase-class.html#__hash__ +scapi.authentication.BasicAuthenticator scapi.authentication.BasicAuthenticator-class.html +scapi.authentication.BasicAuthenticator.augment_request scapi.authentication.BasicAuthenticator-class.html#augment_request +scapi.authentication.BasicAuthenticator.__init__ scapi.authentication.BasicAuthenticator-class.html#__init__ +scapi.authentication.OAuthAuthenticator scapi.authentication.OAuthAuthenticator-class.html +scapi.authentication.OAuthAuthenticator.augment_request scapi.authentication.OAuthAuthenticator-class.html#augment_request +scapi.authentication.OAuthAuthenticator.generate_nonce scapi.authentication.OAuthAuthenticator-class.html#generate_nonce +scapi.authentication.OAuthAuthenticator.OAUTH_API_VERSION scapi.authentication.OAuthAuthenticator-class.html#OAUTH_API_VERSION +scapi.authentication.OAuthAuthenticator.generate_timestamp scapi.authentication.OAuthAuthenticator-class.html#generate_timestamp +scapi.authentication.OAuthAuthenticator.AUTHORIZATION_HEADER scapi.authentication.OAuthAuthenticator-class.html#AUTHORIZATION_HEADER +scapi.authentication.OAuthAuthenticator.__init__ scapi.authentication.OAuthAuthenticator-class.html#__init__ +scapi.authentication.OAuthSignatureMethod_HMAC_SHA1 scapi.authentication.OAuthSignatureMethod_HMAC_SHA1-class.html +scapi.authentication.OAuthSignatureMethod_HMAC_SHA1.FORBIDDEN scapi.authentication.OAuthSignatureMethod_HMAC_SHA1-class.html#FORBIDDEN +scapi.authentication.OAuthSignatureMethod_HMAC_SHA1.get_name scapi.authentication.OAuthSignatureMethod_HMAC_SHA1-class.html#get_name +scapi.authentication.OAuthSignatureMethod_HMAC_SHA1.get_normalized_http_method scapi.authentication.OAuthSignatureMethod_HMAC_SHA1-class.html#get_normalized_http_method +scapi.authentication.OAuthSignatureMethod_HMAC_SHA1.get_normalized_http_url scapi.authentication.OAuthSignatureMethod_HMAC_SHA1-class.html#get_normalized_http_url +scapi.authentication.OAuthSignatureMethod_HMAC_SHA1.get_normalized_parameters scapi.authentication.OAuthSignatureMethod_HMAC_SHA1-class.html#get_normalized_parameters +scapi.authentication.OAuthSignatureMethod_HMAC_SHA1.build_signature scapi.authentication.OAuthSignatureMethod_HMAC_SHA1-class.html#build_signature +scapi.json.JsonReader scapi.json.JsonReader-class.html +scapi.json.JsonReader.escapes scapi.json.JsonReader-class.html#escapes +scapi.json.JsonReader._readObject scapi.json.JsonReader-class.html#_readObject +scapi.json.JsonReader._assertNext scapi.json.JsonReader-class.html#_assertNext +scapi.json.JsonReader._readNumber scapi.json.JsonReader-class.html#_readNumber +scapi.json.JsonReader._peek scapi.json.JsonReader-class.html#_peek +scapi.json.JsonReader._readNull scapi.json.JsonReader-class.html#_readNull +scapi.json.JsonReader._hexDigitToInt scapi.json.JsonReader-class.html#_hexDigitToInt +scapi.json.JsonReader._readTrue scapi.json.JsonReader-class.html#_readTrue +scapi.json.JsonReader._readDoubleSolidusComment scapi.json.JsonReader-class.html#_readDoubleSolidusComment +scapi.json.JsonReader.read scapi.json.JsonReader-class.html#read +scapi.json.JsonReader._readFalse scapi.json.JsonReader-class.html#_readFalse +scapi.json.JsonReader.hex_digits scapi.json.JsonReader-class.html#hex_digits +scapi.json.JsonReader._readComment scapi.json.JsonReader-class.html#_readComment +scapi.json.JsonReader._readArray scapi.json.JsonReader-class.html#_readArray +scapi.json.JsonReader._eatWhitespace scapi.json.JsonReader-class.html#_eatWhitespace +scapi.json.JsonReader._read scapi.json.JsonReader-class.html#_read +scapi.json.JsonReader._readString scapi.json.JsonReader-class.html#_readString +scapi.json.JsonReader._readCStyleComment scapi.json.JsonReader-class.html#_readCStyleComment +scapi.json.JsonReader._next scapi.json.JsonReader-class.html#_next +scapi.json.JsonWriter scapi.json.JsonWriter-class.html +scapi.json.JsonWriter.write scapi.json.JsonWriter-class.html#write +scapi.json.JsonWriter._append scapi.json.JsonWriter-class.html#_append +scapi.json.JsonWriter._write scapi.json.JsonWriter-class.html#_write +scapi.json.ReadException scapi.json.ReadException-class.html +scapi.json.WriteException scapi.json.WriteException-class.html +scapi.json._StringGenerator scapi.json._StringGenerator-class.html +scapi.json._StringGenerator.peek scapi.json._StringGenerator-class.html#peek +scapi.json._StringGenerator.all scapi.json._StringGenerator-class.html#all +scapi.json._StringGenerator.next scapi.json._StringGenerator-class.html#next +scapi.json._StringGenerator.__init__ scapi.json._StringGenerator-class.html#__init__ +scapi.tests.scapi_tests.SCAPITests scapi.tests.scapi_tests.SCAPITests-class.html +scapi.tests.scapi_tests.SCAPITests._load_config scapi.tests.scapi_tests.SCAPITests-class.html#_load_config +scapi.tests.scapi_tests.SCAPITests.test_track_creation_with_artwork scapi.tests.scapi_tests.SCAPITests-class.html#test_track_creation_with_artwork +scapi.tests.scapi_tests.SCAPITests.test_upload scapi.tests.scapi_tests.SCAPITests-class.html#test_upload +scapi.tests.scapi_tests.SCAPITests.test_scoped_track_creation scapi.tests.scapi_tests.SCAPITests-class.html#test_scoped_track_creation +scapi.tests.scapi_tests.SCAPITests.test_permissions scapi.tests.scapi_tests.SCAPITests-class.html#test_permissions +scapi.tests.scapi_tests.SCAPITests.CONSUMER_SECRET scapi.tests.scapi_tests.SCAPITests-class.html#CONSUMER_SECRET +scapi.tests.scapi_tests.SCAPITests.CONSUMER scapi.tests.scapi_tests.SCAPITests-class.html#CONSUMER +scapi.tests.scapi_tests.SCAPITests.test_me_having_stress scapi.tests.scapi_tests.SCAPITests-class.html#test_me_having_stress +scapi.tests.scapi_tests.SCAPITests.USER scapi.tests.scapi_tests.SCAPITests-class.html#USER +scapi.tests.scapi_tests.SCAPITests.CONFIGSPEC scapi.tests.scapi_tests.SCAPITests-class.html#CONFIGSPEC +scapi.tests.scapi_tests.SCAPITests.test_track_creation_with_email_sharers scapi.tests.scapi_tests.SCAPITests-class.html#test_track_creation_with_email_sharers +scapi.tests.scapi_tests.SCAPITests.test_setting_comments scapi.tests.scapi_tests.SCAPITests-class.html#test_setting_comments +scapi.tests.scapi_tests.SCAPITests.test_access_token_acquisition scapi.tests.scapi_tests.SCAPITests-class.html#test_access_token_acquisition +scapi.tests.scapi_tests.SCAPITests.test_track_creation scapi.tests.scapi_tests.SCAPITests-class.html#test_track_creation +scapi.tests.scapi_tests.SCAPITests.test_groups scapi.tests.scapi_tests.SCAPITests-class.html#test_groups +scapi.tests.scapi_tests.SCAPITests.test_track_update scapi.tests.scapi_tests.SCAPITests-class.html#test_track_update +scapi.tests.scapi_tests.SCAPITests.test_modifying_playlists scapi.tests.scapi_tests.SCAPITests-class.html#test_modifying_playlists +scapi.tests.scapi_tests.SCAPITests.SECRET scapi.tests.scapi_tests.SCAPITests-class.html#SECRET +scapi.tests.scapi_tests.SCAPITests.test_oauth_get_signing scapi.tests.scapi_tests.SCAPITests-class.html#test_oauth_get_signing +scapi.tests.scapi_tests.SCAPITests.test_contact_add_and_removal scapi.tests.scapi_tests.SCAPITests-class.html#test_contact_add_and_removal +scapi.tests.scapi_tests.SCAPITests.test_connect scapi.tests.scapi_tests.SCAPITests-class.html#test_connect +scapi.tests.scapi_tests.SCAPITests.test_large_list scapi.tests.scapi_tests.SCAPITests-class.html#test_large_list +unittest.TestCase.failureException exceptions.AssertionError-class.html +scapi.tests.scapi_tests.SCAPITests.test_contact_list scapi.tests.scapi_tests.SCAPITests-class.html#test_contact_list +scapi.tests.scapi_tests.SCAPITests.test_playlists scapi.tests.scapi_tests.SCAPITests-class.html#test_playlists +scapi.tests.scapi_tests.SCAPITests.API_HOST scapi.tests.scapi_tests.SCAPITests-class.html#API_HOST +scapi.tests.scapi_tests.SCAPITests.setUp scapi.tests.scapi_tests.SCAPITests-class.html#setUp +scapi.tests.scapi_tests.SCAPITests.test_downloadable scapi.tests.scapi_tests.SCAPITests-class.html#test_downloadable +scapi.tests.scapi_tests.SCAPITests.TOKEN scapi.tests.scapi_tests.SCAPITests-class.html#TOKEN +scapi.tests.scapi_tests.SCAPITests.test_events scapi.tests.scapi_tests.SCAPITests-class.html#test_events +scapi.tests.scapi_tests.SCAPITests.test_non_global_api scapi.tests.scapi_tests.SCAPITests-class.html#test_non_global_api +scapi.tests.scapi_tests.SCAPITests.PASSWORD scapi.tests.scapi_tests.SCAPITests-class.html#PASSWORD +scapi.tests.scapi_tests.SCAPITests.test_setting_comments_the_way_shawn_says_its_correct scapi.tests.scapi_tests.SCAPITests-class.html#test_setting_comments_the_way_shawn_says_its_correct +scapi.tests.scapi_tests.SCAPITests.CONFIG_NAME scapi.tests.scapi_tests.SCAPITests-class.html#CONFIG_NAME +scapi.tests.scapi_tests.SCAPITests.test_setting_permissions scapi.tests.scapi_tests.SCAPITests-class.html#test_setting_permissions +scapi.tests.scapi_tests.SCAPITests.RUN_INTERACTIVE_TESTS scapi.tests.scapi_tests.SCAPITests-class.html#RUN_INTERACTIVE_TESTS +scapi.tests.scapi_tests.SCAPITests.test_favorites scapi.tests.scapi_tests.SCAPITests-class.html#test_favorites +scapi.tests.scapi_tests.SCAPITests.test_track_creation_with_updated_artwork scapi.tests.scapi_tests.SCAPITests-class.html#test_track_creation_with_updated_artwork +scapi.tests.scapi_tests.SCAPITests.AUTHENTICATOR scapi.tests.scapi_tests.SCAPITests-class.html#AUTHENTICATOR +scapi.tests.scapi_tests.SCAPITests.test_streaming scapi.tests.scapi_tests.SCAPITests-class.html#test_streaming +scapi.tests.scapi_tests.SCAPITests.test_playlist_creation scapi.tests.scapi_tests.SCAPITests-class.html#test_playlist_creation +scapi.tests.scapi_tests.SCAPITests.test_track_deletion scapi.tests.scapi_tests.SCAPITests-class.html#test_track_deletion +scapi.tests.scapi_tests.SCAPITests.test_filtered_list scapi.tests.scapi_tests.SCAPITests-class.html#test_filtered_list +scapi.tests.scapi_tests.SCAPITests.root scapi.tests.scapi_tests.SCAPITests-class.html#root +scapi.util.MultiDict scapi.util.MultiDict-class.html +scapi.util.MultiDict.add scapi.util.MultiDict-class.html#add +scapi.util.MultiDict.iteritemslist scapi.util.MultiDict-class.html#iteritemslist diff --git a/python_apps/soundcloud-api/docs/api/class-tree.html b/python_apps/soundcloud-api/docs/api/class-tree.html new file mode 100644 index 000000000..b2473382f --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/class-tree.html @@ -0,0 +1,216 @@ + + + + + Class Hierarchy + + + + + + +
    $methodParamName
    + + + + + + + + + + + + + + + + + + + + + + +
      + + + + +
    [hide private]
    [frames] | no frames]
    +
    +
    + [ Module Hierarchy + | Class Hierarchy ] +

    +

    Class Hierarchy

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + diff --git a/python_apps/soundcloud-api/docs/api/crarr.png b/python_apps/soundcloud-api/docs/api/crarr.png new file mode 100644 index 000000000..26b43c524 Binary files /dev/null and b/python_apps/soundcloud-api/docs/api/crarr.png differ diff --git a/python_apps/soundcloud-api/docs/api/epydoc.css b/python_apps/soundcloud-api/docs/api/epydoc.css new file mode 100644 index 000000000..86d417068 --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/epydoc.css @@ -0,0 +1,322 @@ + + +/* Epydoc CSS Stylesheet + * + * This stylesheet can be used to customize the appearance of epydoc's + * HTML output. + * + */ + +/* Default Colors & Styles + * - Set the default foreground & background color with 'body'; and + * link colors with 'a:link' and 'a:visited'. + * - Use bold for decision list terms. + * - The heading styles defined here are used for headings *within* + * docstring descriptions. All headings used by epydoc itself use + * either class='epydoc' or class='toc' (CSS styles for both + * defined below). + */ +body { background: #ffffff; color: #000000; } +p { margin-top: 0.5em; margin-bottom: 0.5em; } +a:link { color: #0000ff; } +a:visited { color: #204080; } +dt { font-weight: bold; } +h1 { font-size: +140%; font-style: italic; + font-weight: bold; } +h2 { font-size: +125%; font-style: italic; + font-weight: bold; } +h3 { font-size: +110%; font-style: italic; + font-weight: normal; } +code { font-size: 100%; } +/* N.B.: class, not pseudoclass */ +a.link { font-family: monospace; } + +/* Page Header & Footer + * - The standard page header consists of a navigation bar (with + * pointers to standard pages such as 'home' and 'trees'); a + * breadcrumbs list, which can be used to navigate to containing + * classes or modules; options links, to show/hide private + * variables and to show/hide frames; and a page title (using + *

    ). The page title may be followed by a link to the + * corresponding source code (using 'span.codelink'). + * - The footer consists of a navigation bar, a timestamp, and a + * pointer to epydoc's homepage. + */ +h1.epydoc { margin: 0; font-size: +140%; font-weight: bold; } +h2.epydoc { font-size: +130%; font-weight: bold; } +h3.epydoc { font-size: +115%; font-weight: bold; + margin-top: 0.2em; } +td h3.epydoc { font-size: +115%; font-weight: bold; + margin-bottom: 0; } +table.navbar { background: #a0c0ff; color: #000000; + border: 2px groove #c0d0d0; } +table.navbar table { color: #000000; } +th.navbar-select { background: #70b0ff; + color: #000000; } +table.navbar a { text-decoration: none; } +table.navbar a:link { color: #0000ff; } +table.navbar a:visited { color: #204080; } +span.breadcrumbs { font-size: 85%; font-weight: bold; } +span.options { font-size: 70%; } +span.codelink { font-size: 85%; } +td.footer { font-size: 85%; } + +/* Table Headers + * - Each summary table and details section begins with a 'header' + * row. This row contains a section title (marked by + * 'span.table-header') as well as a show/hide private link + * (marked by 'span.options', defined above). + * - Summary tables that contain user-defined groups mark those + * groups using 'group header' rows. + */ +td.table-header { background: #70b0ff; color: #000000; + border: 1px solid #608090; } +td.table-header table { color: #000000; } +td.table-header table a:link { color: #0000ff; } +td.table-header table a:visited { color: #204080; } +span.table-header { font-size: 120%; font-weight: bold; } +th.group-header { background: #c0e0f8; color: #000000; + text-align: left; font-style: italic; + font-size: 115%; + border: 1px solid #608090; } + +/* Summary Tables (functions, variables, etc) + * - Each object is described by a single row of the table with + * two cells. The left cell gives the object's type, and is + * marked with 'code.summary-type'. The right cell gives the + * object's name and a summary description. + * - CSS styles for the table's header and group headers are + * defined above, under 'Table Headers' + */ +table.summary { border-collapse: collapse; + background: #e8f0f8; color: #000000; + border: 1px solid #608090; + margin-bottom: 0.5em; } +td.summary { border: 1px solid #608090; } +code.summary-type { font-size: 85%; } +table.summary a:link { color: #0000ff; } +table.summary a:visited { color: #204080; } + + +/* Details Tables (functions, variables, etc) + * - Each object is described in its own div. + * - A single-row summary table w/ table-header is used as + * a header for each details section (CSS style for table-header + * is defined above, under 'Table Headers'). + */ +table.details { border-collapse: collapse; + background: #e8f0f8; color: #000000; + border: 1px solid #608090; + margin: .2em 0 0 0; } +table.details table { color: #000000; } +table.details a:link { color: #0000ff; } +table.details a:visited { color: #204080; } + +/* Fields */ +dl.fields { margin-left: 2em; margin-top: 1em; + margin-bottom: 1em; } +dl.fields dd ul { margin-left: 0em; padding-left: 0em; } +dl.fields dd ul li ul { margin-left: 2em; padding-left: 0em; } +div.fields { margin-left: 2em; } +div.fields p { margin-bottom: 0.5em; } + +/* Index tables (identifier index, term index, etc) + * - link-index is used for indices containing lists of links + * (namely, the identifier index & term index). + * - index-where is used in link indices for the text indicating + * the container/source for each link. + * - metadata-index is used for indices containing metadata + * extracted from fields (namely, the bug index & todo index). + */ +table.link-index { border-collapse: collapse; + background: #e8f0f8; color: #000000; + border: 1px solid #608090; } +td.link-index { border-width: 0px; } +table.link-index a:link { color: #0000ff; } +table.link-index a:visited { color: #204080; } +span.index-where { font-size: 70%; } +table.metadata-index { border-collapse: collapse; + background: #e8f0f8; color: #000000; + border: 1px solid #608090; + margin: .2em 0 0 0; } +td.metadata-index { border-width: 1px; border-style: solid; } +table.metadata-index a:link { color: #0000ff; } +table.metadata-index a:visited { color: #204080; } + +/* Function signatures + * - sig* is used for the signature in the details section. + * - .summary-sig* is used for the signature in the summary + * table, and when listing property accessor functions. + * */ +.sig-name { color: #006080; } +.sig-arg { color: #008060; } +.sig-default { color: #602000; } +.summary-sig { font-family: monospace; } +.summary-sig-name { color: #006080; font-weight: bold; } +table.summary a.summary-sig-name:link + { color: #006080; font-weight: bold; } +table.summary a.summary-sig-name:visited + { color: #006080; font-weight: bold; } +.summary-sig-arg { color: #006040; } +.summary-sig-default { color: #501800; } + +/* Subclass list + */ +ul.subclass-list { display: inline; } +ul.subclass-list li { display: inline; } + +/* To render variables, classes etc. like functions */ +table.summary .summary-name { color: #006080; font-weight: bold; + font-family: monospace; } +table.summary + a.summary-name:link { color: #006080; font-weight: bold; + font-family: monospace; } +table.summary + a.summary-name:visited { color: #006080; font-weight: bold; + font-family: monospace; } + +/* Variable values + * - In the 'variable details' sections, each varaible's value is + * listed in a 'pre.variable' box. The width of this box is + * restricted to 80 chars; if the value's repr is longer than + * this it will be wrapped, using a backslash marked with + * class 'variable-linewrap'. If the value's repr is longer + * than 3 lines, the rest will be ellided; and an ellipsis + * marker ('...' marked with 'variable-ellipsis') will be used. + * - If the value is a string, its quote marks will be marked + * with 'variable-quote'. + * - If the variable is a regexp, it is syntax-highlighted using + * the re* CSS classes. + */ +pre.variable { padding: .5em; margin: 0; + background: #dce4ec; color: #000000; + border: 1px solid #708890; } +.variable-linewrap { color: #604000; font-weight: bold; } +.variable-ellipsis { color: #604000; font-weight: bold; } +.variable-quote { color: #604000; font-weight: bold; } +.variable-group { color: #008000; font-weight: bold; } +.variable-op { color: #604000; font-weight: bold; } +.variable-string { color: #006030; } +.variable-unknown { color: #a00000; font-weight: bold; } +.re { color: #000000; } +.re-char { color: #006030; } +.re-op { color: #600000; } +.re-group { color: #003060; } +.re-ref { color: #404040; } + +/* Base tree + * - Used by class pages to display the base class hierarchy. + */ +pre.base-tree { font-size: 80%; margin: 0; } + +/* Frames-based table of contents headers + * - Consists of two frames: one for selecting modules; and + * the other listing the contents of the selected module. + * - h1.toc is used for each frame's heading + * - h2.toc is used for subheadings within each frame. + */ +h1.toc { text-align: center; font-size: 105%; + margin: 0; font-weight: bold; + padding: 0; } +h2.toc { font-size: 100%; font-weight: bold; + margin: 0.5em 0 0 -0.3em; } + +/* Syntax Highlighting for Source Code + * - doctest examples are displayed in a 'pre.py-doctest' block. + * If the example is in a details table entry, then it will use + * the colors specified by the 'table pre.py-doctest' line. + * - Source code listings are displayed in a 'pre.py-src' block. + * Each line is marked with 'span.py-line' (used to draw a line + * down the left margin, separating the code from the line + * numbers). Line numbers are displayed with 'span.py-lineno'. + * The expand/collapse block toggle button is displayed with + * 'a.py-toggle' (Note: the CSS style for 'a.py-toggle' should not + * modify the font size of the text.) + * - If a source code page is opened with an anchor, then the + * corresponding code block will be highlighted. The code + * block's header is highlighted with 'py-highlight-hdr'; and + * the code block's body is highlighted with 'py-highlight'. + * - The remaining py-* classes are used to perform syntax + * highlighting (py-string for string literals, py-name for names, + * etc.) + */ +pre.py-doctest { padding: .5em; margin: 1em; + background: #e8f0f8; color: #000000; + border: 1px solid #708890; } +table pre.py-doctest { background: #dce4ec; + color: #000000; } +pre.py-src { border: 2px solid #000000; + background: #f0f0f0; color: #000000; } +.py-line { border-left: 2px solid #000000; + margin-left: .2em; padding-left: .4em; } +.py-lineno { font-style: italic; font-size: 90%; + padding-left: .5em; } +a.py-toggle { text-decoration: none; } +div.py-highlight-hdr { border-top: 2px solid #000000; + border-bottom: 2px solid #000000; + background: #d8e8e8; } +div.py-highlight { border-bottom: 2px solid #000000; + background: #d0e0e0; } +.py-prompt { color: #005050; font-weight: bold;} +.py-more { color: #005050; font-weight: bold;} +.py-string { color: #006030; } +.py-comment { color: #003060; } +.py-keyword { color: #600000; } +.py-output { color: #404040; } +.py-name { color: #000050; } +.py-name:link { color: #000050 !important; } +.py-name:visited { color: #000050 !important; } +.py-number { color: #005000; } +.py-defname { color: #000060; font-weight: bold; } +.py-def-name { color: #000060; font-weight: bold; } +.py-base-class { color: #000060; } +.py-param { color: #000060; } +.py-docstring { color: #006030; } +.py-decorator { color: #804020; } +/* Use this if you don't want links to names underlined: */ +/*a.py-name { text-decoration: none; }*/ + +/* Graphs & Diagrams + * - These CSS styles are used for graphs & diagrams generated using + * Graphviz dot. 'img.graph-without-title' is used for bare + * diagrams (to remove the border created by making the image + * clickable). + */ +img.graph-without-title { border: none; } +img.graph-with-title { border: 1px solid #000000; } +span.graph-title { font-weight: bold; } +span.graph-caption { } + +/* General-purpose classes + * - 'p.indent-wrapped-lines' defines a paragraph whose first line + * is not indented, but whose subsequent lines are. + * - The 'nomargin-top' class is used to remove the top margin (e.g. + * from lists). The 'nomargin' class is used to remove both the + * top and bottom margin (but not the left or right margin -- + * for lists, that would cause the bullets to disappear.) + */ +p.indent-wrapped-lines { padding: 0 0 0 7em; text-indent: -7em; + margin: 0; } +.nomargin-top { margin-top: 0; } +.nomargin { margin-top: 0; margin-bottom: 0; } + +/* HTML Log */ +div.log-block { padding: 0; margin: .5em 0 .5em 0; + background: #e8f0f8; color: #000000; + border: 1px solid #000000; } +div.log-error { padding: .1em .3em .1em .3em; margin: 4px; + background: #ffb0b0; color: #000000; + border: 1px solid #000000; } +div.log-warning { padding: .1em .3em .1em .3em; margin: 4px; + background: #ffffb0; color: #000000; + border: 1px solid #000000; } +div.log-info { padding: .1em .3em .1em .3em; margin: 4px; + background: #b0ffb0; color: #000000; + border: 1px solid #000000; } +h2.log-hdr { background: #70b0ff; color: #000000; + margin: 0; padding: 0em 0.5em 0em 0.5em; + border-bottom: 1px solid #000000; font-size: 110%; } +p.log { font-weight: bold; margin: .5em 0 .5em 0; } +tr.opt-changed { color: #000000; font-weight: bold; } +tr.opt-default { color: #606060; } +pre.log { margin: 0; padding: 0; padding-left: 1em; } diff --git a/python_apps/soundcloud-api/docs/api/epydoc.js b/python_apps/soundcloud-api/docs/api/epydoc.js new file mode 100644 index 000000000..e787dbcf4 --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/epydoc.js @@ -0,0 +1,293 @@ +function toggle_private() { + // Search for any private/public links on this page. Store + // their old text in "cmd," so we will know what action to + // take; and change their text to the opposite action. + var cmd = "?"; + var elts = document.getElementsByTagName("a"); + for(var i=0; i...
    "; + elt.innerHTML = s; + } +} + +function toggle(id) { + elt = document.getElementById(id+"-toggle"); + if (elt.innerHTML == "-") + collapse(id); + else + expand(id); + return false; +} + +function highlight(id) { + var elt = document.getElementById(id+"-def"); + if (elt) elt.className = "py-highlight-hdr"; + var elt = document.getElementById(id+"-expanded"); + if (elt) elt.className = "py-highlight"; + var elt = document.getElementById(id+"-collapsed"); + if (elt) elt.className = "py-highlight"; +} + +function num_lines(s) { + var n = 1; + var pos = s.indexOf("\n"); + while ( pos > 0) { + n += 1; + pos = s.indexOf("\n", pos+1); + } + return n; +} + +// Collapse all blocks that mave more than `min_lines` lines. +function collapse_all(min_lines) { + var elts = document.getElementsByTagName("div"); + for (var i=0; i 0) + if (elt.id.substring(split, elt.id.length) == "-expanded") + if (num_lines(elt.innerHTML) > min_lines) + collapse(elt.id.substring(0, split)); + } +} + +function expandto(href) { + var start = href.indexOf("#")+1; + if (start != 0 && start != href.length) { + if (href.substring(start, href.length) != "-") { + collapse_all(4); + pos = href.indexOf(".", start); + while (pos != -1) { + var id = href.substring(start, pos); + expand(id); + pos = href.indexOf(".", pos+1); + } + var id = href.substring(start, href.length); + expand(id); + highlight(id); + } + } +} + +function kill_doclink(id) { + var parent = document.getElementById(id); + parent.removeChild(parent.childNodes.item(0)); +} +function auto_kill_doclink(ev) { + if (!ev) var ev = window.event; + if (!this.contains(ev.toElement)) { + var parent = document.getElementById(this.parentID); + parent.removeChild(parent.childNodes.item(0)); + } +} + +function doclink(id, name, targets_id) { + var elt = document.getElementById(id); + + // If we already opened the box, then destroy it. + // (This case should never occur, but leave it in just in case.) + if (elt.childNodes.length > 1) { + elt.removeChild(elt.childNodes.item(0)); + } + else { + // The outer box: relative + inline positioning. + var box1 = document.createElement("div"); + box1.style.position = "relative"; + box1.style.display = "inline"; + box1.style.top = 0; + box1.style.left = 0; + + // A shadow for fun + var shadow = document.createElement("div"); + shadow.style.position = "absolute"; + shadow.style.left = "-1.3em"; + shadow.style.top = "-1.3em"; + shadow.style.background = "#404040"; + + // The inner box: absolute positioning. + var box2 = document.createElement("div"); + box2.style.position = "relative"; + box2.style.border = "1px solid #a0a0a0"; + box2.style.left = "-.2em"; + box2.style.top = "-.2em"; + box2.style.background = "white"; + box2.style.padding = ".3em .4em .3em .4em"; + box2.style.fontStyle = "normal"; + box2.onmouseout=auto_kill_doclink; + box2.parentID = id; + + // Get the targets + var targets_elt = document.getElementById(targets_id); + var targets = targets_elt.getAttribute("targets"); + var links = ""; + target_list = targets.split(","); + for (var i=0; i" + + target[0] + ""; + } + + // Put it all together. + elt.insertBefore(box1, elt.childNodes.item(0)); + //box1.appendChild(box2); + box1.appendChild(shadow); + shadow.appendChild(box2); + box2.innerHTML = + "Which "+name+" do you want to see documentation for?" + + ""; + } + return false; +} + +function get_anchor() { + var href = location.href; + var start = href.indexOf("#")+1; + if ((start != 0) && (start != href.length)) + return href.substring(start, href.length); + } +function redirect_url(dottedName) { + // Scan through each element of the "pages" list, and check + // if "name" matches with any of them. + for (var i=0; i-m" or "-c"; + // extract the portion & compare it to dottedName. + var pagename = pages[i].substring(0, pages[i].length-2); + if (pagename == dottedName.substring(0,pagename.length)) { + + // We've found a page that matches `dottedName`; + // construct its URL, using leftover `dottedName` + // content to form an anchor. + var pagetype = pages[i].charAt(pages[i].length-1); + var url = pagename + ((pagetype=="m")?"-module.html": + "-class.html"); + if (dottedName.length > pagename.length) + url += "#" + dottedName.substring(pagename.length+1, + dottedName.length); + return url; + } + } + } diff --git a/python_apps/soundcloud-api/docs/api/exceptions.AssertionError-class.html b/python_apps/soundcloud-api/docs/api/exceptions.AssertionError-class.html new file mode 100644 index 000000000..2dbca8db5 --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/exceptions.AssertionError-class.html @@ -0,0 +1,299 @@ + + + + + exceptions.AssertionError + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + exceptions :: + AssertionError :: + Class AssertionError + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class AssertionError

    +
    +   object --+            
    +            |            
    +BaseException --+        
    +                |        
    +        Exception --+    
    +                    |    
    +        StandardError --+
    +                        |
    +                       AssertionError
    +
    + +
    +

    Assertion failed.

    + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(...)
    + x.__init__(...) initializes x; see x.__class__.__doc__ for signature
    + + +
    + +
    + a new object with type S, a subtype of T + + + + + + +
    __new__(T, + S, + ...) + + +
    + +
    +

    Inherited from BaseException: + __delattr__, + __getattribute__, + __getitem__, + __getslice__, + __reduce__, + __repr__, + __setattr__, + __setstate__, + __str__ +

    +

    Inherited from object: + __hash__, + __reduce_ex__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from BaseException: + args, + message +

    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(...) +
    (Constructor) +

    +
      +
    + +

    x.__init__(...) initializes x; see x.__class__.__doc__ for + signature

    +
    +
    Overrides: + object.__init__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    __new__(T, + S, + ...) +

    +
      +
    + + +
    +
    Returns: a new object with type S, a subtype of T
    +
    Overrides: + object.__new__ +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + diff --git a/python_apps/soundcloud-api/docs/api/frames.html b/python_apps/soundcloud-api/docs/api/frames.html new file mode 100644 index 000000000..6d0191e9d --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/frames.html @@ -0,0 +1,17 @@ + + + + + SoundCloud API + + + + + + + + + diff --git a/python_apps/soundcloud-api/docs/api/help.html b/python_apps/soundcloud-api/docs/api/help.html new file mode 100644 index 000000000..ff7d397f2 --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/help.html @@ -0,0 +1,278 @@ + + + + + Help + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    API Documentation

    + +

    This document contains the API (Application Programming Interface) +documentation for SoundCloud API. Documentation for the Python +objects defined by the project is divided into separate pages for each +package, module, and class. The API documentation also includes two +pages containing information about the project as a whole: a trees +page, and an index page.

    + +

    Object Documentation

    + +

    Each Package Documentation page contains:

    +
      +
    • A description of the package.
    • +
    • A list of the modules and sub-packages contained by the + package.
    • +
    • A summary of the classes defined by the package.
    • +
    • A summary of the functions defined by the package.
    • +
    • A summary of the variables defined by the package.
    • +
    • A detailed description of each function defined by the + package.
    • +
    • A detailed description of each variable defined by the + package.
    • +
    + +

    Each Module Documentation page contains:

    +
      +
    • A description of the module.
    • +
    • A summary of the classes defined by the module.
    • +
    • A summary of the functions defined by the module.
    • +
    • A summary of the variables defined by the module.
    • +
    • A detailed description of each function defined by the + module.
    • +
    • A detailed description of each variable defined by the + module.
    • +
    + +

    Each Class Documentation page contains:

    +
      +
    • A class inheritance diagram.
    • +
    • A list of known subclasses.
    • +
    • A description of the class.
    • +
    • A summary of the methods defined by the class.
    • +
    • A summary of the instance variables defined by the class.
    • +
    • A summary of the class (static) variables defined by the + class.
    • +
    • A detailed description of each method defined by the + class.
    • +
    • A detailed description of each instance variable defined by the + class.
    • +
    • A detailed description of each class (static) variable defined + by the class.
    • +
    + +

    Project Documentation

    + +

    The Trees page contains the module and class hierarchies:

    +
      +
    • The module hierarchy lists every package and module, with + modules grouped into packages. At the top level, and within each + package, modules and sub-packages are listed alphabetically.
    • +
    • The class hierarchy lists every class, grouped by base + class. If a class has more than one base class, then it will be + listed under each base class. At the top level, and under each base + class, classes are listed alphabetically.
    • +
    + +

    The Index page contains indices of terms and + identifiers:

    +
      +
    • The term index lists every term indexed by any object's + documentation. For each term, the index provides links to each + place where the term is indexed.
    • +
    • The identifier index lists the (short) name of every package, + module, class, method, function, variable, and parameter. For each + identifier, the index provides a short description, and a link to + its documentation.
    • +
    + +

    The Table of Contents

    + +

    The table of contents occupies the two frames on the left side of +the window. The upper-left frame displays the project +contents, and the lower-left frame displays the module +contents:

    + + + + + + + + + +
    + Project
    Contents
    ...
    + API
    Documentation
    Frame


    +
    + Module
    Contents
     
    ...
      +

    + +

    The project contents frame contains a list of all packages +and modules that are defined by the project. Clicking on an entry +will display its contents in the module contents frame. Clicking on a +special entry, labeled "Everything," will display the contents of +the entire project.

    + +

    The module contents frame contains a list of every +submodule, class, type, exception, function, and variable defined by a +module or package. Clicking on an entry will display its +documentation in the API documentation frame. Clicking on the name of +the module, at the top of the frame, will display the documentation +for the module itself.

    + +

    The "frames" and "no frames" buttons below the top +navigation bar can be used to control whether the table of contents is +displayed or not.

    + +

    The Navigation Bar

    + +

    A navigation bar is located at the top and bottom of every page. +It indicates what type of page you are currently viewing, and allows +you to go to related pages. The following table describes the labels +on the navigation bar. Note that not some labels (such as +[Parent]) are not displayed on all pages.

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    LabelHighlighted when...Links to...
    [Parent](never highlighted) the parent of the current package
    [Package]viewing a packagethe package containing the current object +
    [Module]viewing a modulethe module containing the current object +
    [Class]viewing a class the class containing the current object
    [Trees]viewing the trees page the trees page
    [Index]viewing the index page the index page
    [Help]viewing the help page the help page
    + +

    The "show private" and "hide private" buttons below +the top navigation bar can be used to control whether documentation +for private objects is displayed. Private objects are usually defined +as objects whose (short) names begin with a single underscore, but do +not end with an underscore. For example, "_x", +"__pprint", and "epydoc.epytext._tokenize" +are private objects; but "re.sub", +"__init__", and "type_" are not. However, +if a module defines the "__all__" variable, then its +contents are used to decide which objects are private.

    + +

    A timestamp below the bottom navigation bar indicates when each +page was last updated.

    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + diff --git a/python_apps/soundcloud-api/docs/api/identifier-index.html b/python_apps/soundcloud-api/docs/api/identifier-index.html new file mode 100644 index 000000000..94e006634 --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/identifier-index.html @@ -0,0 +1,892 @@ + + + + + Identifier Index + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +
    +

    Identifier Index

    +
    +[ + A + B + C + D + E + F + G + H + I + J + K + L + M + N + O + P + Q + R + S + T + U + V + W + X + Y + Z + _ +] +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    A

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    B

    + + + + + + + + +

    C

    + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    E

    + + + + + + + + +

    F

    + + + + + + + + +

    G

    + + + + + + + + + + + + + + + + + +

    H

    + + + + + + + + +

    I

    + + + + + + + + +

    J

    + + + + + + + + +

    K

    + + + + + + + + + + + + + + + + + +

    L

    + + + + + + + + + + + + + + + + + +

    M

    + + + + + + + + +

    N

    + + + + + + + + + + + + +

    O

    + + + + + + + + + + + + +

    P

    + + + + + + + + + + + + +

    R

    + + + + + + + + + + + + + + + + + + + + + + +

    S

    + + + + + + + + + + + + + + + + + + + + + + +

    T

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    U

    + + + + + + + + + + + + + + + + + +

    W

    + + + + + + + + +

    _

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +

    + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + diff --git a/python_apps/soundcloud-api/docs/api/index.html b/python_apps/soundcloud-api/docs/api/index.html new file mode 100644 index 000000000..6d0191e9d --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/index.html @@ -0,0 +1,17 @@ + + + + + SoundCloud API + + + + + + + + + diff --git a/python_apps/soundcloud-api/docs/api/module-tree.html b/python_apps/soundcloud-api/docs/api/module-tree.html new file mode 100644 index 000000000..bf467d991 --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/module-tree.html @@ -0,0 +1,130 @@ + + + + + Module Hierarchy + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      + + + + +
    [hide private]
    [frames] | no frames]
    +
    +
    + [ Module Hierarchy + | Class Hierarchy ] +

    +

    Module Hierarchy

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + diff --git a/python_apps/soundcloud-api/docs/api/redirect.html b/python_apps/soundcloud-api/docs/api/redirect.html new file mode 100644 index 000000000..8ac436448 --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/redirect.html @@ -0,0 +1,38 @@ +Epydoc Redirect Page + + + + + + + + +

    Epydoc Auto-redirect page

    + +

    When javascript is enabled, this page will redirect URLs of +the form redirect.html#dotted.name to the +documentation for the object with the given fully-qualified +dotted name.

    +

     

    + + + + + diff --git a/python_apps/soundcloud-api/docs/api/scapi-module.html b/python_apps/soundcloud-api/docs/api/scapi-module.html new file mode 100644 index 000000000..e63c5dac9 --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/scapi-module.html @@ -0,0 +1,444 @@ + + + + + scapi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package scapi + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Package scapi

    source code

    + + + + + + + +
    + + + + + +
    Submodules[hide private]
    +
    +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Classes[hide private]
    +
    +   + + ApiConnector
    + The ApiConnector holds all the data necessary to authenticate + against the soundcloud-api. +
    +   + + Comment
    + A comment domain object/resource. +
    +   + + Event
    + A event domain object/resource. +
    +   + + Group
    + A group domain object/resource +
    +   + + InvalidMethodException +
    +   + + NoResultFromRequest +
    +   + + Playlist
    + A playlist/set domain object/resource +
    +   + + RESTBase
    + The baseclass for all our domain-objects/resources. +
    +   + + SCRedirectHandler
    + A urllib2-Handler to deal with the redirects the RESTful API of SC + uses. +
    +   + + Scope
    + The basic means to query and create resources. +
    +   + + Track
    + A track domain object/resource. +
    +   + + UnknownContentType +
    +   + + User
    + A user domain object/resource. +
    + + + + + + + + + +
    + + + + + +
    Functions[hide private]
    +
    +   + + + + + + +
    register_classes() + source code + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Variables[hide private]
    +
    +   + + ACCESS_TOKEN_URL = 'http://api.soundcloud.com/oauth/ac...
    + The url Soundcould offers to make users authorize a concrete request + token. +
    +   + + AUTHORIZATION_URL = 'http://api.soundcloud.com/oauth/a... +
    +   + + PROXY = ''
    + The url Soundcould offers to obtain request-tokens +
    +   + + REQUEST_TOKEN_URL = 'http://api.soundcloud.com/oauth/r...
    + The url Soundcould offers to exchange access-tokens for + request-tokens. +
    +   + + USE_PROXY = False
    + Something like http://127.0.0.1:10000/ +
    +   + + logger = logging.getLogger("scapi") +
    + + + + + + +
    + + + + + +
    Variables Details[hide private]
    +
    + +
    + +
    +

    ACCESS_TOKEN_URL

    +

    The url Soundcould offers to make users authorize a concrete request + token.

    +
    +
    +
    +
    Value:
    +
    +'http://api.soundcloud.com/oauth/access_token'
    +
    +
    +
    +
    +
    + +
    + +
    +

    AUTHORIZATION_URL

    + +
    +
    +
    +
    Value:
    +
    +'http://api.soundcloud.com/oauth/authorize'
    +
    +
    +
    +
    +
    + +
    + +
    +

    REQUEST_TOKEN_URL

    +

    The url Soundcould offers to exchange access-tokens for + request-tokens.

    +
    +
    +
    +
    Value:
    +
    +'http://api.soundcloud.com/oauth/request_token'
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + diff --git a/python_apps/soundcloud-api/docs/api/scapi-pysrc.html b/python_apps/soundcloud-api/docs/api/scapi-pysrc.html new file mode 100644 index 000000000..73d83533b --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/scapi-pysrc.html @@ -0,0 +1,1263 @@ + + + + + scapi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package scapi + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    +

    Source Code for Package scapi

    +
    +  1  ##    SouncCloudAPI implements a Python wrapper around the SoundCloud RESTful 
    +  2  ##    API 
    +  3  ## 
    +  4  ##    Copyright (C) 2008  Diez B. Roggisch 
    +  5  ##    Contact mailto:deets@soundcloud.com 
    +  6  ## 
    +  7  ##    This library is free software; you can redistribute it and/or 
    +  8  ##    modify it under the terms of the GNU Lesser General Public 
    +  9  ##    License as published by the Free Software Foundation; either 
    + 10  ##    version 2.1 of the License, or (at your option) any later version. 
    + 11  ## 
    + 12  ##    This library is distributed in the hope that it will be useful, 
    + 13  ##    but WITHOUT ANY WARRANTY; without even the implied warranty of 
    + 14  ##    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
    + 15  ##    Lesser General Public License for more details. 
    + 16  ## 
    + 17  ##    You should have received a copy of the GNU Lesser General Public 
    + 18  ##    License along with this library; if not, write to the Free Software 
    + 19  ##    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
    + 20   
    + 21  import urllib 
    + 22  import urllib2 
    + 23   
    + 24  import logging 
    + 25  import simplejson 
    + 26  import cgi 
    + 27  from scapi.MultipartPostHandler import MultipartPostHandler 
    + 28  from inspect import isclass 
    + 29  import urlparse 
    + 30  from scapi.authentication import BasicAuthenticator 
    + 31  from scapi.util import ( 
    + 32      escape, 
    + 33      MultiDict, 
    + 34      ) 
    + 35   
    + 36  logging.basicConfig() 
    + 37  logger = logging.getLogger(__name__) 
    + 38   
    + 39  USE_PROXY = False 
    + 40  """ 
    + 41  Something like http://127.0.0.1:10000/ 
    + 42  """ 
    + 43  PROXY = '' 
    + 44   
    + 45   
    + 46   
    + 47  """ 
    + 48  The url Soundcould offers to obtain request-tokens 
    + 49  """ 
    + 50  REQUEST_TOKEN_URL = 'http://api.soundcloud.com/oauth/request_token' 
    + 51  """ 
    + 52  The url Soundcould offers to exchange access-tokens for request-tokens. 
    + 53  """ 
    + 54  ACCESS_TOKEN_URL = 'http://api.soundcloud.com/oauth/access_token' 
    + 55  """ 
    + 56  The url Soundcould offers to make users authorize a concrete request token. 
    + 57  """ 
    + 58  AUTHORIZATION_URL = 'http://api.soundcloud.com/oauth/authorize' 
    + 59   
    + 60  __all__ = ['SoundCloudAPI', 'USE_PROXY', 'PROXY', 'REQUEST_TOKEN_URL', 'ACCESS_TOKEN_URL', 'AUTHORIZATION_URL'] 
    +
    61 + 62 + 63 -class NoResultFromRequest(Exception): +
    64 pass +
    65 +
    66 -class InvalidMethodException(Exception): +
    67 +
    68 - def __init__(self, message): +
    69 self._message = message + 70 Exception.__init__(self) +
    71 +
    72 - def __repr__(self): +
    73 res = Exception.__repr__(self) + 74 res += "\n" + 75 res += "-" * 10 + 76 res += "\nmessage:\n\n" + 77 res += self._message + 78 return res +
    79 +
    80 -class UnknownContentType(Exception): +
    81 - def __init__(self, msg): +
    82 Exception.__init__(self) + 83 self._msg = msg +
    84 +
    85 - def __repr__(self): +
    86 return self.__class__.__name__ + ":" + self._msg +
    87 +
    88 - def __str__(self): +
    89 return str(self) +
    90 +
    91 + 92 -class ApiConnector(object): +
    93 """ + 94 The ApiConnector holds all the data necessary to authenticate against + 95 the soundcloud-api. You can instantiate several connectors if you like, but usually one + 96 should be sufficient. + 97 """ + 98 + 99 """ +100 SoundClound imposes a maximum on the number of returned items. This value is that +101 maximum. +102 """ +103 LIST_LIMIT = 50 +104 +105 """ +106 The query-parameter that is used to request results beginning from a certain offset. +107 """ +108 LIST_OFFSET_PARAMETER = 'offset' +109 """ +110 The query-parameter that is used to request results being limited to a certain amount. +111 +112 Currently this is of no use and just for completeness sake. +113 """ +114 LIST_LIMIT_PARAMETER = 'limit' +115 +
    116 - def __init__(self, host, user=None, password=None, authenticator=None, base="", collapse_scope=True): +
    117 """ +118 Constructor for the API-Singleton. Use it once with parameters, and then the +119 subsequent calls internal to the API will work. +120 +121 @type host: str +122 @param host: the host to connect to, e.g. "api.soundcloud.com". If a port is needed, use +123 "api.soundcloud.com:1234" +124 @type user: str +125 @param user: if given, the username for basic HTTP authentication +126 @type password: str +127 @param password: if the user is given, you have to give a password as well +128 @type authenticator: OAuthAuthenticator | BasicAuthenticator +129 @param authenticator: the authenticator to use, see L{scapi.authentication} +130 """ +131 self.host = host +132 if authenticator is not None: +133 self.authenticator = authenticator +134 elif user is not None and password is not None: +135 self.authenticator = BasicAuthenticator(user, password) +136 self._base = base +137 self.collapse_scope = collapse_scope +
    138 +
    139 - def normalize_method(self, method): +
    140 """ +141 This method will take a method that has been part of a redirect of some sort +142 and see if it's valid, which means that it's located beneath our base. +143 If yes, we return it normalized without that very base. +144 """ +145 _, _, path, _, _, _ = urlparse.urlparse(method) +146 if path.startswith("/"): +147 path = path[1:] +148 # if the base is "", we return the whole path, +149 # otherwise normalize it away +150 if self._base == "": +151 return path +152 if path.startswith(self._base): +153 return path[len(self._base)-1:] +154 raise InvalidMethodException("Not a valid API method: %s" % method) +
    155 +156 +157 +
    158 - def fetch_request_token(self, url=None, oauth_callback="oob", oauth_verifier=None): +
    159 """ +160 Helper-function for a registered consumer to obtain a request token, as +161 used by oauth. +162 +163 Use it like this: +164 +165 >>> oauth_authenticator = scapi.authentication.OAuthAuthenticator(CONSUMER, +166 CONSUMER_SECRET, +167 None, +168 None) +169 +170 >>> sca = scapi.ApiConnector(host=API_HOST, authenticator=oauth_authenticator) +171 >>> token, secret = sca.fetch_request_token() +172 >>> authorization_url = sca.get_request_token_authorization_url(token) +173 +174 Please note the None passed as token & secret to the authenticator. +175 """ +176 if url is None: +177 url = REQUEST_TOKEN_URL +178 req = urllib2.Request(url) +179 self.authenticator.augment_request(req, None, oauth_callback=oauth_callback, oauth_verifier=oauth_verifier) +180 handlers = [] +181 if USE_PROXY: +182 handlers.append(urllib2.ProxyHandler({'http' : PROXY})) +183 opener = urllib2.build_opener(*handlers) +184 handle = opener.open(req, None) +185 info = handle.info() +186 content = handle.read() +187 params = cgi.parse_qs(content, keep_blank_values=False) +188 key = params['oauth_token'][0] +189 secret = params['oauth_token_secret'][0] +190 return key, secret +
    191 +192 +
    193 - def fetch_access_token(self, oauth_verifier): +
    194 """ +195 Helper-function for a registered consumer to exchange an access token for +196 a request token. +197 +198 Use it like this: +199 +200 >>> oauth_authenticator = scapi.authentication.OAuthAuthenticator(CONSUMER, +201 CONSUMER_SECRET, +202 request_token, +203 request_token_secret) +204 +205 >>> sca = scapi.ApiConnector(host=API_HOST, authenticator=oauth_authenticator) +206 >>> token, secret = sca.fetch_access_token() +207 +208 Please note the values passed as token & secret to the authenticator. +209 """ +210 return self.fetch_request_token(ACCESS_TOKEN_URL, oauth_verifier=oauth_verifier) +
    211 +212 +
    213 - def get_request_token_authorization_url(self, token): +
    214 """ +215 Simple helper function to generate the url needed +216 to ask a user for request token authorization. +217 +218 See also L{fetch_request_token}. +219 +220 Possible usage: +221 +222 >>> import webbrowser +223 >>> sca = scapi.ApiConnector() +224 >>> authorization_url = sca.get_request_token_authorization_url(token) +225 >>> webbrowser.open(authorization_url) +226 """ +227 return "%s?oauth_token=%s" % (AUTHORIZATION_URL, token) +
    228 +
    229 +230 +231 -class SCRedirectHandler(urllib2.HTTPRedirectHandler): +
    232 """ +233 A urllib2-Handler to deal with the redirects the RESTful API of SC uses. +234 """ +235 alternate_method = None +236 +
    237 - def http_error_303(self, req, fp, code, msg, hdrs): +
    238 """ +239 In case of return-code 303 (See-other), we have to store the location we got +240 because that will determine the actual type of resource returned. +241 """ +242 self.alternate_method = hdrs['location'] +243 # for oauth, we need to re-create the whole header-shizzle. This +244 # does it - it recreates a full url and signs the request +245 new_url = self.alternate_method +246 # if USE_PROXY: +247 # import pdb; pdb.set_trace() +248 # old_url = req.get_full_url() +249 # protocol, host, _, _, _, _ = urlparse.urlparse(old_url) +250 # new_url = urlparse.urlunparse((protocol, host, self.alternate_method, None, None, None)) +251 req = req.recreate_request(new_url) +252 return urllib2.HTTPRedirectHandler.http_error_303(self, req, fp, code, msg, hdrs) +
    253 +
    254 - def http_error_201(self, req, fp, code, msg, hdrs): +
    255 """ +256 We fake a 201 being a 303 so that our redirection-scheme takes place +257 for the 201 the API throws in case we created something. If the location is +258 not available though, that means that whatever we created has succeded - without +259 being a named resource. Assigning an asset to a track is an example of such +260 case. +261 """ +262 if 'location' not in hdrs: +263 raise NoResultFromRequest() +264 return self.http_error_303(req, fp, 303, msg, hdrs) +
    265 +
    266 -class Scope(object): +
    267 """ +268 The basic means to query and create resources. The Scope uses the L{ApiConnector} to +269 create the proper URIs for querying or creating resources. +270 +271 For accessing resources from the root level, you explcitly create a Scope and pass it +272 an L{ApiConnector}-instance. Then you can query it +273 or create new resources like this: +274 +275 >>> connector = scapi.ApiConnector(host='host', user='user', password='password') # initialize the API +276 >>> scope = scapi.Scope(connector) # get the root scope +277 >>> users = list(scope.users()) +278 [<scapi.User object at 0x12345>, ...] +279 +280 Please not that all resources that are lists are returned as B{generator}. So you need +281 to either iterate over them, or call list(resources) on them. +282 +283 When accessing resources that belong to another resource, like contacts of a user, you access +284 the parent's resource scope implicitly through the resource instance like this: +285 +286 >>> user = scope.users().next() +287 >>> list(user.contacts()) +288 [<scapi.Contact object at 0x12345>, ...] +289 +290 """ +
    291 - def __init__(self, connector, scope=None, parent=None): +
    292 """ +293 Create the Scope. It can have a resource as scope, and possibly a parent-scope. +294 +295 @param connector: The connector to use. +296 @type connector: ApiConnector +297 @type scope: scapi.RESTBase +298 @param scope: the resource to make this scope belong to +299 @type parent: scapi.Scope +300 @param parent: the parent scope of this scope +301 """ +302 +303 if scope is None: +304 scope = () +305 else: +306 scope = scope, +307 if parent is not None: +308 scope = parent._scope + scope +309 self._scope = scope +310 self._connector = connector +
    311 +
    312 - def _get_connector(self): +
    313 return self._connector +
    314 +315 +
    316 - def oauth_sign_get_request(self, url): +
    317 """ +318 This method will take an arbitrary url, and rewrite it +319 so that the current authenticator's oauth-headers are appended +320 as query-parameters. +321 +322 This is used in streaming and downloading, because those content +323 isn't served from the SoundCloud servers themselves. +324 +325 A usage example would look like this: +326 +327 >>> sca = scapi.Scope(connector) +328 >>> track = sca.tracks(params={ +329 "filter" : "downloadable", +330 }).next() +331 +332 +333 >>> download_url = track.download_url +334 >>> signed_url = track.oauth_sign_get_request(download_url) +335 >>> data = urllib2.urlopen(signed_url).read() +336 +337 """ +338 scheme, netloc, path, params, query, fragment = urlparse.urlparse(url) +339 +340 req = urllib2.Request(url) +341 +342 all_params = {} +343 if query: +344 all_params.update(cgi.parse_qs(query)) +345 +346 if not all_params: +347 all_params = None +348 +349 self._connector.authenticator.augment_request(req, all_params, False) +350 +351 auth_header = req.get_header("Authorization") +352 auth_header = auth_header[len("OAuth "):] +353 +354 query_params = [] +355 if query: +356 query_params.append(query) +357 +358 for part in auth_header.split(","): +359 key, value = part.split("=") +360 assert key.startswith("oauth") +361 value = value[1:-1] +362 query_params.append("%s=%s" % (key, value)) +363 +364 query = "&".join(query_params) +365 url = urlparse.urlunparse((scheme, netloc, path, params, query, fragment)) +366 return url +
    367 +368 +
    369 - def _create_request(self, url, connector, parameters, queryparams, alternate_http_method=None, use_multipart=False): +
    370 """ +371 This method returnes the urllib2.Request to perform the actual HTTP-request. +372 +373 We return a subclass that overload the get_method-method to return a custom method like "PUT". +374 Additionally, the request is enhanced with the current authenticators authorization scheme +375 headers. +376 +377 @param url: the destination url +378 @param connector: our connector-instance +379 @param parameters: the POST-parameters to use. +380 @type parameters: None|dict<str, basestring|list<basestring>> +381 @param queryparams: the queryparams to use +382 @type queryparams: None|dict<str, basestring|list<basestring>> +383 @param alternate_http_method: an alternate HTTP-method to use +384 @type alternate_http_method: str +385 @return: the fully equipped request +386 @rtype: urllib2.Request +387 """ +388 class MyRequest(urllib2.Request): +389 def get_method(self): +390 if alternate_http_method is not None: +391 return alternate_http_method +392 return urllib2.Request.get_method(self) +
    393 +394 def has_data(self): +395 return parameters is not None +
    396 +397 def augment_request(self, params, use_multipart=False): +398 connector.authenticator.augment_request(self, params, use_multipart) +399 +400 @classmethod +401 def recreate_request(cls, location): +402 return self._create_request(location, connector, None, None) +403 +404 req = MyRequest(url) +405 all_params = {} +406 if parameters is not None: +407 all_params.update(parameters) +408 if queryparams is not None: +409 all_params.update(queryparams) +410 if not all_params: +411 all_params = None +412 req.augment_request(all_params, use_multipart) +413 req.add_header("Accept", "application/json") +414 return req +415 +416 +
    417 - def _create_query_string(self, queryparams): +
    418 """ +419 Small helpermethod to create the querystring from a dict. +420 +421 @type queryparams: None|dict<str, basestring|list<basestring>> +422 @param queryparams: the queryparameters. +423 @return: either the empty string, or a "?" followed by the parameters joined by "&" +424 @rtype: str +425 """ +426 if not queryparams: +427 return "" +428 h = [] +429 for key, values in queryparams.iteritems(): +430 if isinstance(values, (int, long, float)): +431 values = str(values) +432 if isinstance(values, basestring): +433 values = [values] +434 for v in values: +435 v = v.encode("utf-8") +436 h.append("%s=%s" % (key, escape(v))) +437 return "?" + "&".join(h) +
    438 +439 +
    440 - def _call(self, method, *args, **kwargs): +
    441 """ +442 The workhorse. It's complicated, convoluted and beyond understanding of a mortal being. +443 +444 You have been warned. +445 """ +446 +447 queryparams = {} +448 __offset__ = ApiConnector.LIST_LIMIT +449 if "__offset__" in kwargs: +450 offset = kwargs.pop("__offset__") +451 queryparams['offset'] = offset +452 __offset__ = offset + ApiConnector.LIST_LIMIT +453 +454 if "params" in kwargs: +455 queryparams.update(kwargs.pop("params")) +456 +457 # create a closure to invoke this method again with a greater offset +458 _cl_method = method +459 _cl_args = tuple(args) +460 _cl_kwargs = {} +461 _cl_kwargs.update(kwargs) +462 _cl_kwargs["__offset__"] = __offset__ +463 def continue_list_fetching(): +464 return self._call(method, *_cl_args, **_cl_kwargs) +
    465 connector = self._get_connector() +466 def filelike(v): +467 if isinstance(v, file): +468 return True +469 if hasattr(v, "read"): +470 return True +471 return False +472 alternate_http_method = None +473 if "_alternate_http_method" in kwargs: +474 alternate_http_method = kwargs.pop("_alternate_http_method") +475 urlparams = kwargs if kwargs else None +476 use_multipart = False +477 if urlparams is not None: +478 fileargs = dict((key, value) for key, value in urlparams.iteritems() if filelike(value)) +479 use_multipart = bool(fileargs) +480 +481 # ensure the method has a trailing / +482 if method[-1] != "/": +483 method = method + "/" +484 if args: +485 method = "%s%s" % (method, "/".join(str(a) for a in args)) +486 +487 scope = '' +488 if self._scope: +489 scopes = self._scope +490 if connector.collapse_scope: +491 scopes = scopes[-1:] +492 scope = "/".join([sc._scope() for sc in scopes]) + "/" +493 url = "http://%(host)s/%(base)s%(scope)s%(method)s%(queryparams)s" % dict(host=connector.host, method=method, base=connector._base, scope=scope, queryparams=self._create_query_string(queryparams)) +494 +495 # we need to install SCRedirectHandler +496 # to gather possible See-Other redirects +497 # so that we can exchange our method +498 redirect_handler = SCRedirectHandler() +499 handlers = [redirect_handler] +500 if USE_PROXY: +501 handlers.append(urllib2.ProxyHandler({'http' : PROXY})) +502 req = self._create_request(url, connector, urlparams, queryparams, alternate_http_method, use_multipart) +503 +504 http_method = req.get_method() +505 if urlparams is not None: +506 logger.debug("Posting url: %s, method: %s", url, http_method) +507 else: +508 logger.debug("Fetching url: %s, method: %s", url, http_method) +509 +510 +511 if use_multipart: +512 handlers.extend([MultipartPostHandler]) +513 else: +514 if urlparams is not None: +515 urlparams = urllib.urlencode(urlparams.items(), True) +516 opener = urllib2.build_opener(*handlers) +517 try: +518 handle = opener.open(req, urlparams) +519 except NoResultFromRequest: +520 return None +521 except urllib2.HTTPError, e: +522 if http_method == "GET" and e.code == 404: +523 return None +524 raise +525 +526 info = handle.info() +527 ct = info['Content-Type'] +528 content = handle.read() +529 logger.debug("Content-type:%s", ct) +530 logger.debug("Request Content:\n%s", content) +531 if redirect_handler.alternate_method is not None: +532 method = connector.normalize_method(redirect_handler.alternate_method) +533 logger.debug("Method changed through redirect to: <%s>", method) +534 +535 try: +536 if "application/json" in ct: +537 content = content.strip() +538 if not content: +539 content = "{}" +540 try: +541 res = simplejson.loads(content) +542 except: +543 logger.error("Couldn't decode returned json") +544 logger.error(content) +545 raise +546 res = self._map(res, method, continue_list_fetching) +547 return res +548 elif len(content) <= 1: +549 # this might be the famous SeeOtherSpecialCase which means that +550 # all that matters is just the method +551 pass +552 raise UnknownContentType("%s, returned:\n%s" % (ct, content)) +553 finally: +554 handle.close() +555 +
    556 - def _map(self, res, method, continue_list_fetching): +
    557 """ +558 This method will take the JSON-result of a HTTP-call and return our domain-objects. +559 +560 It's also deep magic, don't look. +561 """ +562 pathparts = reversed(method.split("/")) +563 stack = [] +564 for part in pathparts: +565 stack.append(part) +566 if part in RESTBase.REGISTRY: +567 cls = RESTBase.REGISTRY[part] +568 # multiple objects +569 if isinstance(res, list): +570 def result_gen(): +571 count = 0 +572 for item in res: +573 yield cls(item, self, stack) +574 count += 1 +575 if count == ApiConnector.LIST_LIMIT: +576 for item in continue_list_fetching(): +577 yield item +
    578 return result_gen() +579 else: +580 return cls(res, self, stack) +581 logger.debug("don't know how to handle result") +582 logger.debug(res) +583 return res +584 +
    585 - def __getattr__(self, _name): +
    586 """ +587 Retrieve an API-method or a scoped domain-class. +588 +589 If the former, result is a callable that supports the following invocations: +590 +591 - calling (...), with possible arguments (positional/keyword), return the resulting resource or list of resources. +592 When calling, you can pass a keyword-argument B{params}. This must be a dict or L{MultiDict} and will be used to add additional query-get-parameters. +593 +594 - invoking append(resource) on it will PUT the resource, making it part of the current resource. Makes +595 sense only if it's a collection of course. +596 +597 - invoking remove(resource) on it will DELETE the resource from it's container. Also only usable on collections. +598 +599 TODO: describe the latter +600 """ +601 scope = self +602 +603 class api_call(object): +604 def __call__(selfish, *args, **kwargs): +605 return self._call(_name, *args, **kwargs) +
    606 +607 def new(self, **kwargs): +608 """ +609 Will invoke the new method on the named resource _name, with +610 self as scope. +611 """ +612 cls = RESTBase.REGISTRY[_name] +613 return cls.new(scope, **kwargs) +614 +615 def append(selfish, resource): +616 """ +617 If the current scope is +618 """ +619 self._call(_name, str(resource.id), _alternate_http_method="PUT") +620 +621 def remove(selfish, resource): +622 self._call(_name, str(resource.id), _alternate_http_method="DELETE") +623 +624 if _name in RESTBase.ALL_DOMAIN_CLASSES: +625 cls = RESTBase.ALL_DOMAIN_CLASSES[_name] +626 +627 class ScopeBinder(object): +628 def new(self, *args, **data): +629 +630 d = MultiDict() +631 name = cls._singleton() +632 +633 def unfold_value(key, value): +634 if isinstance(value, (basestring, file)): +635 d.add(key, value) +636 elif isinstance(value, dict): +637 for sub_key, sub_value in value.iteritems(): +638 unfold_value("%s[%s]" % (key, sub_key), sub_value) +639 else: +640 # assume iteration else +641 for sub_value in value: +642 unfold_value(key + "[]", sub_value) +643 +644 +645 for key, value in data.iteritems(): +646 unfold_value("%s[%s]" % (name, key), value) +647 +648 return scope._call(cls.KIND, **d) +649 +650 def create(self, **data): +651 return cls.create(scope, **data) +652 +653 def get(self, id): +654 return cls.get(scope, id) +655 +656 +657 return ScopeBinder() +658 return api_call() +659 +
    660 - def __repr__(self): +
    661 return str(self) +
    662 +
    663 - def __str__(self): +
    664 scopes = self._scope +665 base = "" +666 if len(scopes) > 1: +667 base = str(scopes[-2]) +668 return base + "/" + str(scopes[-1]) +
    669 +
    670 +671 # maybe someday I'll make that work. +672 # class RESTBaseMeta(type): +673 # def __new__(self, name, bases, d): +674 # clazz = type(name, bases, d) +675 # if 'KIND' in d: +676 # kind = d['KIND'] +677 # RESTBase.REGISTRY[kind] = clazz +678 # return clazz +679 +680 -class RESTBase(object): +
    681 """ +682 The baseclass for all our domain-objects/resources. +683 +684 +685 """ +686 REGISTRY = {} +687 +688 ALL_DOMAIN_CLASSES = {} +689 +690 ALIASES = [] +691 +692 KIND = None +693 +
    694 - def __init__(self, data, scope, path_stack=None): +
    695 self.__data = data +696 self.__scope = scope +697 # try and see if we can/must create an id out of our path +698 logger.debug("path_stack: %r", path_stack) +699 if path_stack: +700 try: +701 id = int(path_stack[0]) +702 self.__data['id'] = id +703 except ValueError: +704 pass +
    705 +
    706 - def __getattr__(self, name): +
    707 if name in self.__data: +708 obj = self.__data[name] +709 if name in RESTBase.REGISTRY: +710 if isinstance(obj, dict): +711 obj = RESTBase.REGISTRY[name](obj, self.__scope) +712 elif isinstance(obj, list): +713 obj = [RESTBase.REGISTRY[name](o, self.__scope) for o in obj] +714 else: +715 logger.warning("Found %s in our registry, but don't know what to do with"\ +716 "the object.") +717 return obj +718 scope = Scope(self.__scope._get_connector(), scope=self, parent=self.__scope) +719 return getattr(scope, name) +
    720 +
    721 - def __setattr__(self, name, value): +
    722 """ +723 This method is used to set a property, a resource or a list of resources as property of the resource the +724 method is invoked on. +725 +726 For example, to set a comment on a track, do +727 +728 >>> sca = scapi.Scope(connector) +729 >>> track = scapi.Track.new(title='bar', sharing="private") +730 >>> comment = scapi.Comment.create(body="This is the body of my comment", timestamp=10) +731 >>> track.comments = comment +732 +733 To set a list of users as permissions, do +734 +735 >>> sca = scapi.Scope(connector) +736 >>> me = sca.me() +737 >>> track = scapi.Track.new(title='bar', sharing="private") +738 >>> users = sca.users() +739 >>> users_to_set = [user for user in users[:10] if user != me] +740 >>> track.permissions = users_to_set +741 +742 And finally, to simply change the title of a track, do +743 +744 >>> sca = scapi.Scope(connector) +745 >>> track = sca.Track.get(track_id) +746 >>> track.title = "new_title" +747 +748 @param name: the property name +749 @type name: str +750 @param value: the property, resource or resources to set +751 @type value: RESTBase | list<RESTBase> | basestring | long | int | float +752 @return: None +753 """ +754 +755 # update "private" data, such as __data +756 if "_RESTBase__" in name: +757 self.__dict__[name] = value +758 else: +759 if isinstance(value, list) and len(value): +760 # the parametername is something like +761 # permissions[user_id][] +762 # so we try to infer that. +763 parameter_name = "%s[%s_id][]" % (name, value[0]._singleton()) +764 values = [o.id for o in value] +765 kwargs = {"_alternate_http_method" : "PUT", +766 parameter_name : values} +767 self.__scope._call(self.KIND, self.id, name, **kwargs) +768 elif isinstance(value, RESTBase): +769 # we got a single instance, so make that an argument +770 self.__scope._call(self.KIND, self.id, name, **value._as_arguments()) +771 else: +772 # we have a simple property +773 parameter_name = "%s[%s]" % (self._singleton(), name) +774 kwargs = {"_alternate_http_method" : "PUT", +775 parameter_name : self._convert_value(value)} +776 self.__scope._call(self.KIND, self.id, **kwargs) +
    777 +
    778 - def _as_arguments(self): +
    779 """ +780 Converts a resource to a argument-string the way Rails expects it. +781 """ +782 res = {} +783 for key, value in self.__data.items(): +784 value = self._convert_value(value) +785 res["%s[%s]" % (self._singleton(), key)] = value +786 return res +
    787 +
    788 - def _convert_value(self, value): +
    789 if isinstance(value, unicode): +790 value = value.encode("utf-8") +791 elif isinstance(value, file): +792 pass +793 else: +794 value = str(value) +795 return value +
    796 +797 @classmethod +
    798 - def create(cls, scope, **data): +
    799 """ +800 This is a convenience-method for creating an object that will be passed +801 as parameter - e.g. a comment. A usage would look like this: +802 +803 >>> sca = scapi.Scope(connector) +804 >>> track = sca.Track.new(title='bar', sharing="private") +805 >>> comment = sca.Comment.create(body="This is the body of my comment", timestamp=10) +806 >>> track.comments = comment +807 +808 """ +809 return cls(data, scope) +
    810 +811 @classmethod +
    812 - def new(cls, scope, **data): +
    813 """ +814 Create a new resource inside a given Scope. The actual values are in data. +815 +816 So for creating new resources, you have two options: +817 +818 - create an instance directly using the class: +819 +820 >>> scope = scapi.Scope(connector) +821 >>> scope.User.new(...) +822 <scapi.User object at 0x1234> +823 +824 - create a instance in a certain scope: +825 +826 >>> scope = scapi.Scope(connector) +827 >>> user = scapi.User("1") +828 >>> track = user.tracks.new() +829 <scapi.Track object at 0x1234> +830 +831 @param scope: if not empty, a one-element tuple containing the Scope +832 @type scope: tuple<Scope>[1] +833 @param data: the data +834 @type data: dict +835 @return: new instance of the resource +836 """ +837 return getattr(scope, cls.__name__).new(**data) +
    838 +839 @classmethod +
    840 - def get(cls, scope, id): +
    841 """ +842 Fetch a resource by id. +843 +844 Simply pass a known id as argument. For example +845 +846 >>> sca = scapi.Scope(connector) +847 >>> track = sca.Track.get(id) +848 +849 """ +850 return getattr(scope, cls.KIND)(id) +
    851 +852 +
    853 - def _scope(self): +
    854 """ +855 Return the scope this resource lives in, which is the KIND and id +856 +857 @return: "<KIND>/<id>" +858 """ +859 return "%s/%s" % (self.KIND, str(self.id)) +
    860 +861 @classmethod +
    862 - def _singleton(cls): +
    863 """ +864 This method will take a resource name like "users" and +865 return the single-case, in the example "user". +866 +867 Currently, it's not very sophisticated, only strips a trailing s. +868 """ +869 name = cls.KIND +870 if name[-1] == 's': +871 return name[:-1] +872 raise ValueError("Can't make %s to a singleton" % name) +
    873 +
    874 - def __repr__(self): +
    875 res = [] +876 res.append("\n\n******\n%s:" % self.__class__.__name__) +877 res.append("") +878 for key, v in self.__data.iteritems(): +879 key = str(key) +880 if isinstance(v, unicode): +881 v = v.encode('utf-8') +882 else: +883 v = str(v) +884 res.append("%s=%s" % (key, v)) +885 return "\n".join(res) +
    886 +
    887 - def __hash__(self): +
    888 return hash("%s%i" % (self.KIND, self.id)) +
    889 +
    890 - def __eq__(self, other): +
    891 """ +892 Test for equality. +893 +894 Resources are considered equal if the have the same kind and id. +895 """ +896 if not isinstance(other, RESTBase): +897 return False +898 res = self.KIND == other.KIND and self.id == other.id +899 return res +
    900 +
    901 - def __ne__(self, other): +
    902 return not self == other +
    903 +
    904 -class User(RESTBase): +
    905 """ +906 A user domain object/resource. +907 """ +908 KIND = 'users' +909 ALIASES = ['me', 'permissions', 'contacts', 'user'] +
    910 +
    911 -class Track(RESTBase): +
    912 """ +913 A track domain object/resource. +914 """ +915 KIND = 'tracks' +916 ALIASES = ['favorites'] +
    917 +
    918 -class Comment(RESTBase): +
    919 """ +920 A comment domain object/resource. +921 """ +922 KIND = 'comments' +
    923 +
    924 -class Event(RESTBase): +
    925 """ +926 A event domain object/resource. +927 """ +928 KIND = 'events' +
    929 +
    930 -class Playlist(RESTBase): +
    931 """ +932 A playlist/set domain object/resource +933 """ +934 KIND = 'playlists' +
    935 +
    936 -class Group(RESTBase): +
    937 """ +938 A group domain object/resource +939 """ +940 KIND = 'groups' +
    941 +
    942 +943 +944 # this registers all the RESTBase subclasses. +945 # One day using a metaclass will make this a tad +946 # less ugly. +947 -def register_classes(): +
    948 g = {} +949 g.update(globals()) +950 for name, cls in [(k, v) for k, v in g.iteritems() if isclass(v) and issubclass(v, RESTBase) and not v == RESTBase]: +951 RESTBase.REGISTRY[cls.KIND] = cls +952 RESTBase.ALL_DOMAIN_CLASSES[cls.__name__] = cls +953 for alias in cls.ALIASES: +954 RESTBase.REGISTRY[alias] = cls +955 __all__.append(name) +
    956 register_classes() +957 +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + diff --git a/python_apps/soundcloud-api/docs/api/scapi.ApiConnector-class.html b/python_apps/soundcloud-api/docs/api/scapi.ApiConnector-class.html new file mode 100644 index 000000000..7e4a213ac --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/scapi.ApiConnector-class.html @@ -0,0 +1,544 @@ + + + + + scapi.ApiConnector + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package scapi :: + Class ApiConnector + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class ApiConnector

    source code

    +
    +object --+
    +         |
    +        ApiConnector
    +
    + +
    +

    The ApiConnector holds all the data necessary to authenticate against + the soundcloud-api. You can instantiate several connectors if you like, + but usually one should be sufficient.

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + host, + user=None, + password=None, + authenticator=None, + base='', + collapse_scope=True)
    + Constructor for the API-Singleton.
    + source code + +
    + +
    +   + + + + + + +
    normalize_method(self, + method)
    + This method will take a method that has been part of a redirect of + some sort and see if it's valid, which means that it's located + beneath our base.
    + source code + +
    + +
    +   + + + + + + +
    fetch_request_token(self, + url=None, + oauth_callback='oob', + oauth_verifier=None)
    + Helper-function for a registered consumer to obtain a request token, + as used by oauth.
    + source code + +
    + +
    +   + + + + + + +
    fetch_access_token(self, + oauth_verifier)
    + Helper-function for a registered consumer to exchange an access token + for a request token.
    + source code + +
    + +
    +   + + + + + + +
    get_request_token_authorization_url(self, + token)
    + Simple helper function to generate the url needed to ask a user for + request token authorization.
    + source code + +
    + +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + LIST_LIMIT = 50
    + The query-parameter that is used to request results beginning from a + certain offset. +
    +   + + LIST_OFFSET_PARAMETER = 'offset'
    + The query-parameter that is used to request results being limited to + a certain amount. +
    +   + + LIST_LIMIT_PARAMETER = 'limit' +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + host, + user=None, + password=None, + authenticator=None, + base='', + collapse_scope=True) +
    (Constructor) +

    +
    source code  +
    + +

    Constructor for the API-Singleton. Use it once with parameters, and + then the subsequent calls internal to the API will work.

    +
    +
    Parameters:
    +
      +
    • host (str) - the host to connect to, e.g. "api.soundcloud.com". If a + port is needed, use "api.soundcloud.com:1234"
    • +
    • user (str) - if given, the username for basic HTTP authentication
    • +
    • password (str) - if the user is given, you have to give a password as well
    • +
    • authenticator (OAuthAuthenticator | BasicAuthenticator) - the authenticator to use, see scapi.authentication
    • +
    +
    Overrides: + object.__init__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    normalize_method(self, + method) +

    +
    source code  +
    + +

    This method will take a method that has been part of a redirect of + some sort and see if it's valid, which means that it's located beneath + our base. If yes, we return it normalized without that very base.

    +
    +
    +
    +
    + +
    + +
    + + +
    +

    fetch_request_token(self, + url=None, + oauth_callback='oob', + oauth_verifier=None) +

    +
    source code  +
    + +

    Helper-function for a registered consumer to obtain a request token, + as used by oauth.

    +

    Use it like this:

    +
    +>>> oauth_authenticator = scapi.authentication.OAuthAuthenticator(CONSUMER, 
    +                                                          CONSUMER_SECRET,
    +                                                          None, 
    +                                                          None)
    +
    +>>> sca = scapi.ApiConnector(host=API_HOST, authenticator=oauth_authenticator)
    +>>> token, secret = sca.fetch_request_token()
    +>>> authorization_url = sca.get_request_token_authorization_url(token)
    +

    Please note the None passed as token & secret to the + authenticator.

    +
    +
    +
    +
    + +
    + +
    + + +
    +

    fetch_access_token(self, + oauth_verifier) +

    +
    source code  +
    + +

    Helper-function for a registered consumer to exchange an access token + for a request token.

    +

    Use it like this:

    +
    +>>> oauth_authenticator = scapi.authentication.OAuthAuthenticator(CONSUMER, 
    +                                                          CONSUMER_SECRET,
    +                                                          request_token, 
    +                                                          request_token_secret)
    +
    +>>> sca = scapi.ApiConnector(host=API_HOST, authenticator=oauth_authenticator)
    +>>> token, secret = sca.fetch_access_token()
    +

    Please note the values passed as token & secret to the + authenticator.

    +
    +
    +
    +
    + +
    + +
    + + +
    +

    get_request_token_authorization_url(self, + token) +

    +
    source code  +
    + +

    Simple helper function to generate the url needed to ask a user for + request token authorization.

    +

    See also fetch_request_token.

    +

    Possible usage:

    +
    +>>> import webbrowser
    +>>> sca = scapi.ApiConnector()
    +>>> authorization_url = sca.get_request_token_authorization_url(token)
    +>>> webbrowser.open(authorization_url)
    +
    +
    +
    +
    +
    + + + + + + +
    + + + + + +
    Class Variable Details[hide private]
    +
    + +
    + +
    +

    LIST_OFFSET_PARAMETER

    +

    The query-parameter that is used to request results being limited to a + certain amount.

    +

    Currently this is of no use and just for completeness sake.

    +
    +
    +
    +
    Value:
    +
    +'offset'
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + diff --git a/python_apps/soundcloud-api/docs/api/scapi.Asset-class.html b/python_apps/soundcloud-api/docs/api/scapi.Asset-class.html new file mode 100644 index 000000000..f64bc0d2b --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/scapi.Asset-class.html @@ -0,0 +1,258 @@ + + + + + scapi.Asset + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package scapi :: + Class Asset + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class Asset

    source code

    +
    +object --+    
    +         |    
    +  RESTBase --+
    +             |
    +            Asset
    +
    + +
    +An asset domain object/resource.

    + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from RESTBase: + __eq__, + __getattr__, + __hash__, + __init__, + __ne__, + __repr__, + __setattr__ +

    +

    Inherited from RESTBase (private): + _as_arguments, + _convert_value, + _scope +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __new__, + __reduce__, + __reduce_ex__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Class Methods[hide private]
    +
    +

    Inherited from RESTBase: + create, + get, + new +

    +

    Inherited from RESTBase (private): + _singleton +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + KIND = 'assets' +
    +

    Inherited from RESTBase: + ALIASES, + ALL_DOMAIN_CLASSES, + REGISTRY +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + diff --git a/python_apps/soundcloud-api/docs/api/scapi.Comment-class.html b/python_apps/soundcloud-api/docs/api/scapi.Comment-class.html new file mode 100644 index 000000000..4f2c4c950 --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/scapi.Comment-class.html @@ -0,0 +1,258 @@ + + + + + scapi.Comment + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package scapi :: + Class Comment + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class Comment

    source code

    +
    +object --+    
    +         |    
    +  RESTBase --+
    +             |
    +            Comment
    +
    + +
    +

    A comment domain object/resource.

    + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from RESTBase: + __eq__, + __getattr__, + __hash__, + __init__, + __ne__, + __repr__, + __setattr__ +

    +

    Inherited from RESTBase (private): + _as_arguments, + _convert_value, + _scope +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __new__, + __reduce__, + __reduce_ex__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Class Methods[hide private]
    +
    +

    Inherited from RESTBase: + create, + get, + new +

    +

    Inherited from RESTBase (private): + _singleton +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + KIND = 'comments' +
    +

    Inherited from RESTBase: + ALIASES, + ALL_DOMAIN_CLASSES, + REGISTRY +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + diff --git a/python_apps/soundcloud-api/docs/api/scapi.Event-class.html b/python_apps/soundcloud-api/docs/api/scapi.Event-class.html new file mode 100644 index 000000000..40095b067 --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/scapi.Event-class.html @@ -0,0 +1,258 @@ + + + + + scapi.Event + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package scapi :: + Class Event + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class Event

    source code

    +
    +object --+    
    +         |    
    +  RESTBase --+
    +             |
    +            Event
    +
    + +
    +

    A event domain object/resource.

    + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from RESTBase: + __eq__, + __getattr__, + __hash__, + __init__, + __ne__, + __repr__, + __setattr__ +

    +

    Inherited from RESTBase (private): + _as_arguments, + _convert_value, + _scope +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __new__, + __reduce__, + __reduce_ex__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Class Methods[hide private]
    +
    +

    Inherited from RESTBase: + create, + get, + new +

    +

    Inherited from RESTBase (private): + _singleton +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + KIND = 'events' +
    +

    Inherited from RESTBase: + ALIASES, + ALL_DOMAIN_CLASSES, + REGISTRY +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + diff --git a/python_apps/soundcloud-api/docs/api/scapi.Group-class.html b/python_apps/soundcloud-api/docs/api/scapi.Group-class.html new file mode 100644 index 000000000..eb30ab842 --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/scapi.Group-class.html @@ -0,0 +1,258 @@ + + + + + scapi.Group + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package scapi :: + Class Group + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class Group

    source code

    +
    +object --+    
    +         |    
    +  RESTBase --+
    +             |
    +            Group
    +
    + +
    +

    A group domain object/resource

    + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from RESTBase: + __eq__, + __getattr__, + __hash__, + __init__, + __ne__, + __repr__, + __setattr__ +

    +

    Inherited from RESTBase (private): + _as_arguments, + _convert_value, + _scope +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __new__, + __reduce__, + __reduce_ex__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Class Methods[hide private]
    +
    +

    Inherited from RESTBase: + create, + get, + new +

    +

    Inherited from RESTBase (private): + _singleton +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + KIND = 'groups' +
    +

    Inherited from RESTBase: + ALIASES, + ALL_DOMAIN_CLASSES, + REGISTRY +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + diff --git a/python_apps/soundcloud-api/docs/api/scapi.InvalidMethodException-class.html b/python_apps/soundcloud-api/docs/api/scapi.InvalidMethodException-class.html new file mode 100644 index 000000000..476502352 --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/scapi.InvalidMethodException-class.html @@ -0,0 +1,297 @@ + + + + + scapi.InvalidMethodException + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package scapi :: + Class InvalidMethodException + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class InvalidMethodException

    source code

    +
    +              object --+        
    +                       |        
    +exceptions.BaseException --+    
    +                           |    
    +        exceptions.Exception --+
    +                               |
    +                              InvalidMethodException
    +
    + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + message)
    + x.__init__(...) initializes x; see x.__class__.__doc__ for signature
    + source code + +
    + +
    +   + + + + + + +
    __repr__(self)
    + repr(x)
    + source code + +
    + +
    +

    Inherited from exceptions.Exception: + __new__ +

    +

    Inherited from exceptions.BaseException: + __delattr__, + __getattribute__, + __getitem__, + __getslice__, + __reduce__, + __setattr__, + __setstate__, + __str__ +

    +

    Inherited from object: + __hash__, + __reduce_ex__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from exceptions.BaseException: + args, + message +

    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + message) +
    (Constructor) +

    +
    source code  +
    + +

    x.__init__(...) initializes x; see x.__class__.__doc__ for + signature

    +
    +
    Overrides: + object.__init__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    __repr__(self) +
    (Representation operator) +

    +
    source code  +
    + +

    repr(x)

    +
    +
    Overrides: + object.__repr__ +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + diff --git a/python_apps/soundcloud-api/docs/api/scapi.NoResultFromRequest-class.html b/python_apps/soundcloud-api/docs/api/scapi.NoResultFromRequest-class.html new file mode 100644 index 000000000..5bcb71794 --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/scapi.NoResultFromRequest-class.html @@ -0,0 +1,195 @@ + + + + + scapi.NoResultFromRequest + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package scapi :: + Class NoResultFromRequest + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class NoResultFromRequest

    source code

    +
    +              object --+        
    +                       |        
    +exceptions.BaseException --+    
    +                           |    
    +        exceptions.Exception --+
    +                               |
    +                              NoResultFromRequest
    +
    + +
    + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from exceptions.Exception: + __init__, + __new__ +

    +

    Inherited from exceptions.BaseException: + __delattr__, + __getattribute__, + __getitem__, + __getslice__, + __reduce__, + __repr__, + __setattr__, + __setstate__, + __str__ +

    +

    Inherited from object: + __hash__, + __reduce_ex__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from exceptions.BaseException: + args, + message +

    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + diff --git a/python_apps/soundcloud-api/docs/api/scapi.Playlist-class.html b/python_apps/soundcloud-api/docs/api/scapi.Playlist-class.html new file mode 100644 index 000000000..557f05831 --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/scapi.Playlist-class.html @@ -0,0 +1,258 @@ + + + + + scapi.Playlist + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package scapi :: + Class Playlist + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class Playlist

    source code

    +
    +object --+    
    +         |    
    +  RESTBase --+
    +             |
    +            Playlist
    +
    + +
    +

    A playlist/set domain object/resource

    + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from RESTBase: + __eq__, + __getattr__, + __hash__, + __init__, + __ne__, + __repr__, + __setattr__ +

    +

    Inherited from RESTBase (private): + _as_arguments, + _convert_value, + _scope +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __new__, + __reduce__, + __reduce_ex__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Class Methods[hide private]
    +
    +

    Inherited from RESTBase: + create, + get, + new +

    +

    Inherited from RESTBase (private): + _singleton +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + KIND = 'playlists' +
    +

    Inherited from RESTBase: + ALIASES, + ALL_DOMAIN_CLASSES, + REGISTRY +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + diff --git a/python_apps/soundcloud-api/docs/api/scapi.RESTBase-class.html b/python_apps/soundcloud-api/docs/api/scapi.RESTBase-class.html new file mode 100644 index 000000000..855d8fd1e --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/scapi.RESTBase-class.html @@ -0,0 +1,895 @@ + + + + + scapi.RESTBase + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package scapi :: + Class RESTBase + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class RESTBase

    source code

    +
    +object --+
    +         |
    +        RESTBase
    +
    + +
    Known Subclasses:
    +
    + +
    + +
    +

    The baseclass for all our domain-objects/resources.

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + data, + scope, + path_stack=None)
    + x.__init__(...) initializes x; see x.__class__.__doc__ for signature
    + source code + +
    + +
    +   + + + + + + +
    __getattr__(self, + name) + source code + +
    + +
    +   + + + + + + +
    __setattr__(self, + name, + value)
    + This method is used to set a property, a resource or a list of + resources as property of the resource the method is invoked on.
    + source code + +
    + +
    +   + + + + + + +
    _as_arguments(self)
    + Converts a resource to a argument-string the way Rails expects it.
    + source code + +
    + +
    +   + + + + + + +
    _convert_value(self, + value) + source code + +
    + +
    +   + + + + + + +
    _scope(self)
    + Return the scope this resource lives in, which is the KIND and id
    + source code + +
    + +
    +   + + + + + + +
    __repr__(self)
    + repr(x)
    + source code + +
    + +
    +   + + + + + + +
    __hash__(self)
    + hash(x)
    + source code + +
    + +
    +   + + + + + + +
    __eq__(self, + other)
    + Test for equality.
    + source code + +
    + +
    +   + + + + + + +
    __ne__(self, + other) + source code + +
    + +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __new__, + __reduce__, + __reduce_ex__, + __str__ +

    +
    + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Class Methods[hide private]
    +
    +   + + + + + + +
    create(cls, + scope, + **data)
    + This is a convenience-method for creating an object that will be + passed as parameter - e.g.
    + source code + +
    + +
    +   + + + + + + +
    new(cls, + scope, + **data)
    + Create a new resource inside a given Scope.
    + source code + +
    + +
    +   + + + + + + +
    get(cls, + scope, + id)
    + Fetch a resource by id.
    + source code + +
    + +
    +   + + + + + + +
    _singleton(cls)
    + This method will take a resource name like "users" and + return the single-case, in the example "user".
    + source code + +
    + +
    + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + REGISTRY = {'comments': <class 'scapi.Comment'>, 'contacts': <... +
    +   + + ALL_DOMAIN_CLASSES = {'Comment': <class 'scapi.Comment'>, 'Eve... +
    +   + + ALIASES = [] +
    +   + + KIND = None +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + data, + scope, + path_stack=None) +
    (Constructor) +

    +
    source code  +
    + +

    x.__init__(...) initializes x; see x.__class__.__doc__ for + signature

    +
    +
    Overrides: + object.__init__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    __setattr__(self, + name, + value) +

    +
    source code  +
    + +

    This method is used to set a property, a resource or a list of + resources as property of the resource the method is invoked on.

    +

    For example, to set a comment on a track, do

    +
    +>>> sca = scapi.Scope(connector)
    +>>> track = scapi.Track.new(title='bar', sharing="private")
    +>>> comment = scapi.Comment.create(body="This is the body of my comment", timestamp=10)    
    +>>> track.comments = comment
    +

    To set a list of users as permissions, do

    +
    +>>> sca = scapi.Scope(connector)
    +>>> me = sca.me()
    +>>> track = scapi.Track.new(title='bar', sharing="private")
    +>>> users = sca.users()
    +>>> users_to_set = [user  for user in users[:10] if user != me]
    +>>> track.permissions = users_to_set
    +

    And finally, to simply change the title of a track, do

    +
    +>>> sca = scapi.Scope(connector)
    +>>> track = sca.Track.get(track_id)
    +>>> track.title = "new_title"
    +
    +
    Parameters:
    +
      +
    • name (str) - the property name
    • +
    • value (RESTBase | list<RESTBase> | basestring | long | int | float) - the property, resource or resources to set
    • +
    +
    Returns:
    +
    None
    +
    Overrides: + object.__setattr__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    create(cls, + scope, + **data) +
    Class Method +

    +
    source code  +
    + +

    This is a convenience-method for creating an object that will be + passed as parameter - e.g. a comment. A usage would look like this:

    +
    +>>> sca = scapi.Scope(connector)
    +>>> track = sca.Track.new(title='bar', sharing="private")
    +>>> comment = sca.Comment.create(body="This is the body of my comment", timestamp=10)    
    +>>> track.comments = comment
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    new(cls, + scope, + **data) +
    Class Method +

    +
    source code  +
    + +

    Create a new resource inside a given Scope. The actual values are in + data.

    +

    So for creating new resources, you have two options:

    +
      +
    • + create an instance directly using the class: +
      +>>> scope = scapi.Scope(connector)
      +>>> scope.User.new(...)
      +<scapi.User object at 0x1234>
      +
    • +
    • + create a instance in a certain scope: +
      +>>> scope = scapi.Scope(connector)
      +>>> user = scapi.User("1")
      +>>> track = user.tracks.new()
      +<scapi.Track object at 0x1234>
      +
    • +
    +
    +
    Parameters:
    +
      +
    • scope (tuple<Scope>[1]) - if not empty, a one-element tuple containing the Scope
    • +
    • data (dict) - the data
    • +
    +
    Returns:
    +
    new instance of the resource
    +
    +
    +
    + +
    + +
    + + +
    +

    get(cls, + scope, + id) +
    Class Method +

    +
    source code  +
    + +

    Fetch a resource by id.

    +

    Simply pass a known id as argument. For example

    +
    +>>> sca = scapi.Scope(connector)
    +>>> track = sca.Track.get(id)
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    _scope(self) +

    +
    source code  +
    + +

    Return the scope this resource lives in, which is the KIND and id

    +
    +
    Returns:
    +
    "<KIND>/<id>"
    +
    +
    +
    + +
    + +
    + + +
    +

    _singleton(cls) +
    Class Method +

    +
    source code  +
    + +

    This method will take a resource name like "users" and + return the single-case, in the example "user".

    +

    Currently, it's not very sophisticated, only strips a trailing s.

    +
    +
    +
    +
    + +
    + +
    + + +
    +

    __repr__(self) +
    (Representation operator) +

    +
    source code  +
    + +

    repr(x)

    +
    +
    Overrides: + object.__repr__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    __hash__(self) +
    (Hashing function) +

    +
    source code  +
    + +

    hash(x)

    +
    +
    Overrides: + object.__hash__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    __eq__(self, + other) +
    (Equality operator) +

    +
    source code  +
    + +

    Test for equality.

    +

    Resources are considered equal if the have the same kind and id.

    +
    +
    +
    +
    +
    + + + + + + +
    + + + + + +
    Class Variable Details[hide private]
    +
    + +
    + +
    +

    REGISTRY

    + +
    +
    +
    +
    Value:
    +
    +{'comments': <class 'scapi.Comment'>,
    + 'contacts': <class 'scapi.User'>,
    + 'events': <class 'scapi.Event'>,
    + 'favorites': <class 'scapi.Track'>,
    + 'groups': <class 'scapi.Group'>,
    + 'me': <class 'scapi.User'>,
    + 'permissions': <class 'scapi.User'>,
    + 'playlists': <class 'scapi.Playlist'>,
    +...
    +
    +
    +
    +
    +
    + +
    + +
    +

    ALL_DOMAIN_CLASSES

    + +
    +
    +
    +
    Value:
    +
    +{'Comment': <class 'scapi.Comment'>,
    + 'Event': <class 'scapi.Event'>,
    + 'Group': <class 'scapi.Group'>,
    + 'Playlist': <class 'scapi.Playlist'>,
    + 'Track': <class 'scapi.Track'>,
    + 'User': <class 'scapi.User'>}
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + diff --git a/python_apps/soundcloud-api/docs/api/scapi.SCRedirectHandler-class.html b/python_apps/soundcloud-api/docs/api/scapi.SCRedirectHandler-class.html new file mode 100644 index 000000000..7fa8dedee --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/scapi.SCRedirectHandler-class.html @@ -0,0 +1,319 @@ + + + + + scapi.SCRedirectHandler + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package scapi :: + Class SCRedirectHandler + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class SCRedirectHandler

    source code

    +
    +    urllib2.BaseHandler --+    
    +                          |    
    +urllib2.HTTPRedirectHandler --+
    +                              |
    +                             SCRedirectHandler
    +
    + +
    +

    A urllib2-Handler to deal with the redirects the RESTful API of SC + uses.

    + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    http_error_303(self, + req, + fp, + code, + msg, + hdrs)
    + In case of return-code 303 (See-other), we have to store the location + we got because that will determine the actual type of resource + returned.
    + source code + +
    + +
    +   + + + + + + +
    http_error_201(self, + req, + fp, + code, + msg, + hdrs)
    + We fake a 201 being a 303 so that our redirection-scheme takes place + for the 201 the API throws in case we created something.
    + source code + +
    + +
    +

    Inherited from urllib2.HTTPRedirectHandler: + http_error_301, + http_error_302, + http_error_307, + redirect_request +

    +

    Inherited from urllib2.BaseHandler: + __lt__, + add_parent, + close +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + alternate_method = None +
    +

    Inherited from urllib2.HTTPRedirectHandler: + inf_msg, + max_redirections, + max_repeats +

    +

    Inherited from urllib2.BaseHandler: + handler_order +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    http_error_303(self, + req, + fp, + code, + msg, + hdrs) +

    +
    source code  +
    + +

    In case of return-code 303 (See-other), we have to store the location + we got because that will determine the actual type of resource + returned.

    +
    +
    Overrides: + urllib2.HTTPRedirectHandler.http_error_302 +
    +
    +
    +
    + +
    + +
    + + +
    +

    http_error_201(self, + req, + fp, + code, + msg, + hdrs) +

    +
    source code  +
    + +

    We fake a 201 being a 303 so that our redirection-scheme takes place + for the 201 the API throws in case we created something. If the location + is not available though, that means that whatever we created has succeded + - without being a named resource. Assigning an asset to a track is an + example of such case.

    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + diff --git a/python_apps/soundcloud-api/docs/api/scapi.Scope-class.html b/python_apps/soundcloud-api/docs/api/scapi.Scope-class.html new file mode 100644 index 000000000..0b008e17f --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/scapi.Scope-class.html @@ -0,0 +1,682 @@ + + + + + scapi.Scope + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package scapi :: + Class Scope + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class Scope

    source code

    +
    +object --+
    +         |
    +        Scope
    +
    + +
    +

    The basic means to query and create resources. The Scope uses the ApiConnector to create the proper URIs for + querying or creating resources.

    +

    For accessing resources from the root level, you explcitly create a + Scope and pass it an ApiConnector-instance. Then you can query + it or create new resources like this:

    +
    +>>> connector = scapi.ApiConnector(host='host', user='user', password='password') # initialize the API
    +>>> scope = scapi.Scope(connector) # get the root scope
    +>>> users = list(scope.users())
    +[<scapi.User object at 0x12345>, ...]
    +

    Please not that all resources that are lists are returned as + generator. So you need to either iterate over them, or call + list(resources) on them.

    +

    When accessing resources that belong to another resource, like + contacts of a user, you access the parent's resource scope implicitly + through the resource instance like this:

    +
    +>>> user = scope.users().next()
    +>>> list(user.contacts())
    +[<scapi.Contact object at 0x12345>, ...]
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + connector, + scope=None, + parent=None)
    + Create the Scope.
    + source code + +
    + +
    +   + + + + + + +
    _get_connector(self) + source code + +
    + +
    +   + + + + + + +
    oauth_sign_get_request(self, + url)
    + This method will take an arbitrary url, and rewrite it so that the + current authenticator's oauth-headers are appended as + query-parameters.
    + source code + +
    + +
    + urllib2.Request + + + + + + +
    _create_request(self, + url, + connector, + parameters, + queryparams, + alternate_http_method=None, + use_multipart=False)
    + This method returnes the urllib2.Request to perform the actual + HTTP-request.
    + source code + +
    + +
    + str + + + + + + +
    _create_query_string(self, + queryparams)
    + Small helpermethod to create the querystring from a dict.
    + source code + +
    + +
    +   + + + + + + +
    _call(self, + method, + *args, + **kwargs)
    + The workhorse.
    + source code + +
    + +
    +   + + + + + + +
    _map(self, + res, + method, + continue_list_fetching)
    + This method will take the JSON-result of a HTTP-call and return our + domain-objects.
    + source code + +
    + +
    +   + + + + + + +
    __getattr__(self, + _name)
    + Retrieve an API-method or a scoped domain-class.
    + source code + +
    + +
    +   + + + + + + +
    __repr__(self)
    + repr(x)
    + source code + +
    + +
    +   + + + + + + +
    __str__(self)
    + str(x)
    + source code + +
    + +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __setattr__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + connector, + scope=None, + parent=None) +
    (Constructor) +

    +
    source code  +
    + +

    Create the Scope. It can have a resource as scope, and possibly a + parent-scope.

    +
    +
    Parameters:
    +
      +
    • connector (ApiConnector) - The connector to use.
    • +
    • scope (scapi.RESTBase) - the resource to make this scope belong to
    • +
    • parent (scapi.Scope) - the parent scope of this scope
    • +
    +
    Overrides: + object.__init__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    oauth_sign_get_request(self, + url) +

    +
    source code  +
    + +

    This method will take an arbitrary url, and rewrite it so that the + current authenticator's oauth-headers are appended as + query-parameters.

    +

    This is used in streaming and downloading, because those content isn't + served from the SoundCloud servers themselves.

    +

    A usage example would look like this:

    +
    +>>> sca = scapi.Scope(connector)
    +>>> track = sca.tracks(params={
    +      "filter" : "downloadable",
    +      }).next()
    +
    +>>> download_url = track.download_url
    +>>> signed_url = track.oauth_sign_get_request(download_url)
    +>>> data = urllib2.urlopen(signed_url).read()
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    _create_request(self, + url, + connector, + parameters, + queryparams, + alternate_http_method=None, + use_multipart=False) +

    +
    source code  +
    + +

    This method returnes the urllib2.Request to perform the actual + HTTP-request.

    +

    We return a subclass that overload the get_method-method to return a + custom method like "PUT". Additionally, the request is enhanced + with the current authenticators authorization scheme headers.

    +
    +
    Parameters:
    +
      +
    • url - the destination url
    • +
    • connector - our connector-instance
    • +
    • parameters (None|dict<str, basestring|list<basestring>>) - the POST-parameters to use.
    • +
    • queryparams (None|dict<str, basestring|list<basestring>>) - the queryparams to use
    • +
    • alternate_http_method (str) - an alternate HTTP-method to use
    • +
    +
    Returns: urllib2.Request
    +
    the fully equipped request
    +
    +
    +
    + +
    + +
    + + +
    +

    _create_query_string(self, + queryparams) +

    +
    source code  +
    + +

    Small helpermethod to create the querystring from a dict.

    +
    +
    Parameters:
    +
      +
    • queryparams (None|dict<str, basestring|list<basestring>>) - the queryparameters.
    • +
    +
    Returns: str
    +
    either the empty string, or a "?" followed by the + parameters joined by "&"
    +
    +
    +
    + +
    + +
    + + +
    +

    _call(self, + method, + *args, + **kwargs) +

    +
    source code  +
    + +

    The workhorse. It's complicated, convoluted and beyond understanding + of a mortal being.

    +

    You have been warned.

    +
    +
    +
    +
    + +
    + +
    + + +
    +

    _map(self, + res, + method, + continue_list_fetching) +

    +
    source code  +
    + +

    This method will take the JSON-result of a HTTP-call and return our + domain-objects.

    +

    It's also deep magic, don't look.

    +
    +
    +
    +
    + +
    + +
    + + +
    +

    __getattr__(self, + _name) +
    (Qualification operator) +

    +
    source code  +
    + +

    Retrieve an API-method or a scoped domain-class.

    +

    If the former, result is a callable that supports the following + invocations:

    +
      +
    • + calling (...), with possible arguments (positional/keyword), return + the resulting resource or list of resources. When calling, you can + pass a keyword-argument params. This must be a dict or MultiDict and + will be used to add additional query-get-parameters. +
    • +
    • + invoking append(resource) on it will PUT the resource, making it part + of the current resource. Makes sense only if it's a collection of + course. +
    • +
    • + invoking remove(resource) on it will DELETE the resource from it's + container. Also only usable on collections. +

      TODO: describe the latter

      +
    • +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    __repr__(self) +
    (Representation operator) +

    +
    source code  +
    + +

    repr(x)

    +
    +
    Overrides: + object.__repr__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    __str__(self) +
    (Informal representation operator) +

    +
    source code  +
    + +

    str(x)

    +
    +
    Overrides: + object.__str__ +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + diff --git a/python_apps/soundcloud-api/docs/api/scapi.Track-class.html b/python_apps/soundcloud-api/docs/api/scapi.Track-class.html new file mode 100644 index 000000000..e8257566a --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/scapi.Track-class.html @@ -0,0 +1,264 @@ + + + + + scapi.Track + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package scapi :: + Class Track + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class Track

    source code

    +
    +object --+    
    +         |    
    +  RESTBase --+
    +             |
    +            Track
    +
    + +
    +

    A track domain object/resource.

    + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from RESTBase: + __eq__, + __getattr__, + __hash__, + __init__, + __ne__, + __repr__, + __setattr__ +

    +

    Inherited from RESTBase (private): + _as_arguments, + _convert_value, + _scope +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __new__, + __reduce__, + __reduce_ex__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Class Methods[hide private]
    +
    +

    Inherited from RESTBase: + create, + get, + new +

    +

    Inherited from RESTBase (private): + _singleton +

    +
    + + + + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + KIND = 'tracks' +
    +   + + ALIASES = ['favorites'] +
    +

    Inherited from RESTBase: + ALL_DOMAIN_CLASSES, + REGISTRY +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + diff --git a/python_apps/soundcloud-api/docs/api/scapi.UnknownContentType-class.html b/python_apps/soundcloud-api/docs/api/scapi.UnknownContentType-class.html new file mode 100644 index 000000000..94f9ee872 --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/scapi.UnknownContentType-class.html @@ -0,0 +1,337 @@ + + + + + scapi.UnknownContentType + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package scapi :: + Class UnknownContentType + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class UnknownContentType

    source code

    +
    +              object --+        
    +                       |        
    +exceptions.BaseException --+    
    +                           |    
    +        exceptions.Exception --+
    +                               |
    +                              UnknownContentType
    +
    + +
    + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + msg)
    + x.__init__(...) initializes x; see x.__class__.__doc__ for signature
    + source code + +
    + +
    +   + + + + + + +
    __repr__(self)
    + repr(x)
    + source code + +
    + +
    +   + + + + + + +
    __str__(self)
    + str(x)
    + source code + +
    + +
    +

    Inherited from exceptions.Exception: + __new__ +

    +

    Inherited from exceptions.BaseException: + __delattr__, + __getattribute__, + __getitem__, + __getslice__, + __reduce__, + __setattr__, + __setstate__ +

    +

    Inherited from object: + __hash__, + __reduce_ex__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from exceptions.BaseException: + args, + message +

    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + msg) +
    (Constructor) +

    +
    source code  +
    + +

    x.__init__(...) initializes x; see x.__class__.__doc__ for + signature

    +
    +
    Overrides: + object.__init__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    __repr__(self) +
    (Representation operator) +

    +
    source code  +
    + +

    repr(x)

    +
    +
    Overrides: + object.__repr__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    __str__(self) +
    (Informal representation operator) +

    +
    source code  +
    + +

    str(x)

    +
    +
    Overrides: + object.__str__ +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + diff --git a/python_apps/soundcloud-api/docs/api/scapi.User-class.html b/python_apps/soundcloud-api/docs/api/scapi.User-class.html new file mode 100644 index 000000000..19b1053a9 --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/scapi.User-class.html @@ -0,0 +1,264 @@ + + + + + scapi.User + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package scapi :: + Class User + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class User

    source code

    +
    +object --+    
    +         |    
    +  RESTBase --+
    +             |
    +            User
    +
    + +
    +

    A user domain object/resource.

    + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from RESTBase: + __eq__, + __getattr__, + __hash__, + __init__, + __ne__, + __repr__, + __setattr__ +

    +

    Inherited from RESTBase (private): + _as_arguments, + _convert_value, + _scope +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __new__, + __reduce__, + __reduce_ex__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Class Methods[hide private]
    +
    +

    Inherited from RESTBase: + create, + get, + new +

    +

    Inherited from RESTBase (private): + _singleton +

    +
    + + + + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + KIND = 'users' +
    +   + + ALIASES = ['me', 'permissions', 'contacts', 'user'] +
    +

    Inherited from RESTBase: + ALL_DOMAIN_CLASSES, + REGISTRY +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + diff --git a/python_apps/soundcloud-api/docs/api/scapi.authentication-module.html b/python_apps/soundcloud-api/docs/api/scapi.authentication-module.html new file mode 100644 index 000000000..2f6f5fb8e --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/scapi.authentication-module.html @@ -0,0 +1,228 @@ + + + + + scapi.authentication + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package scapi :: + Module authentication + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Module authentication

    source code

    + + + + + + + + + + + + + + + +
    + + + + + +
    Classes[hide private]
    +
    +   + + OAuthSignatureMethod_HMAC_SHA1 +
    +   + + OAuthAuthenticator +
    +   + + BasicAuthenticator +
    + + + + + + + + + + + + +
    + + + + + +
    Variables[hide private]
    +
    +   + + USE_DOUBLE_ESCAPE_HACK = True
    + There seems to be an uncertainty on the way parameters are to be + escaped. +
    +   + + logger = logging.getLogger(__name__) +
    + + + + + + +
    + + + + + +
    Variables Details[hide private]
    +
    + +
    + +
    +

    USE_DOUBLE_ESCAPE_HACK

    +

    There seems to be an uncertainty on the way parameters are to be + escaped. For now, this variable switches between two escaping + mechanisms.

    +

    If True, the passed parameters - GET or POST - are escaped + *twice*.

    +
    +
    +
    +
    Value:
    +
    +True
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + diff --git a/python_apps/soundcloud-api/docs/api/scapi.authentication-pysrc.html b/python_apps/soundcloud-api/docs/api/scapi.authentication-pysrc.html new file mode 100644 index 000000000..65492b3c9 --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/scapi.authentication-pysrc.html @@ -0,0 +1,348 @@ + + + + + scapi.authentication + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package scapi :: + Module authentication + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    +

    Source Code for Module scapi.authentication

    +
    +  1  ##    SouncCloudAPI implements a Python wrapper around the SoundCloud RESTful 
    +  2  ##    API 
    +  3  ## 
    +  4  ##    Copyright (C) 2008  Diez B. Roggisch 
    +  5  ##    Contact mailto:deets@soundcloud.com 
    +  6  ## 
    +  7  ##    This library is free software; you can redistribute it and/or 
    +  8  ##    modify it under the terms of the GNU Lesser General Public 
    +  9  ##    License as published by the Free Software Foundation; either 
    + 10  ##    version 2.1 of the License, or (at your option) any later version. 
    + 11  ## 
    + 12  ##    This library is distributed in the hope that it will be useful, 
    + 13  ##    but WITHOUT ANY WARRANTY; without even the implied warranty of 
    + 14  ##    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
    + 15  ##    Lesser General Public License for more details. 
    + 16  ## 
    + 17  ##    You should have received a copy of the GNU Lesser General Public 
    + 18  ##    License along with this library; if not, write to the Free Software 
    + 19  ##    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
    + 20   
    + 21  import base64 
    + 22  import time, random 
    + 23  import urlparse 
    + 24  import hmac 
    + 25  import hashlib 
    + 26  from scapi.util import escape 
    + 27  import logging 
    + 28   
    + 29   
    + 30  USE_DOUBLE_ESCAPE_HACK = True 
    + 31  """ 
    + 32  There seems to be an uncertainty on the way 
    + 33  parameters are to be escaped. For now, this 
    + 34  variable switches between two escaping mechanisms. 
    + 35   
    + 36  If True, the passed parameters - GET or POST - are 
    + 37  escaped *twice*. 
    + 38  """ 
    + 39   
    + 40  logger = logging.getLogger(__name__) 
    + 41   
    +
    42 -class OAuthSignatureMethod_HMAC_SHA1(object): +
    43 + 44 FORBIDDEN = ['realm', 'oauth_signature'] + 45 +
    46 - def get_name(self): +
    47 return 'HMAC-SHA1' +
    48 +
    49 - def build_signature(self, request, parameters, consumer_secret, token_secret, oauth_parameters): +
    50 if logger.level == logging.DEBUG: + 51 logger.debug("request: %r", request) + 52 logger.debug("parameters: %r", parameters) + 53 logger.debug("consumer_secret: %r", consumer_secret) + 54 logger.debug("token_secret: %r", token_secret) + 55 logger.debug("oauth_parameters: %r", oauth_parameters) + 56 + 57 + 58 temp = {} + 59 temp.update(oauth_parameters) + 60 for p in self.FORBIDDEN: + 61 if p in temp: + 62 del temp[p] + 63 if parameters is not None: + 64 temp.update(parameters) + 65 sig = ( + 66 escape(self.get_normalized_http_method(request)), + 67 escape(self.get_normalized_http_url(request)), + 68 self.get_normalized_parameters(temp), # these are escaped in the method already + 69 ) + 70 + 71 key = '%s&' % consumer_secret + 72 if token_secret is not None: + 73 key += token_secret + 74 raw = '&'.join(sig) + 75 logger.debug("raw basestring: %s", raw) + 76 logger.debug("key: %s", key) + 77 # hmac object + 78 hashed = hmac.new(key, raw, hashlib.sha1) + 79 # calculate the digest base 64 + 80 signature = escape(base64.b64encode(hashed.digest())) + 81 return signature +
    82 + 83 +
    84 - def get_normalized_http_method(self, request): +
    85 return request.get_method().upper() +
    86 + 87 + 88 # parses the url and rebuilds it to be scheme://host/path +
    89 - def get_normalized_http_url(self, request): +
    90 url = request.get_full_url() + 91 parts = urlparse.urlparse(url) + 92 url_string = '%s://%s%s' % (parts.scheme, parts.netloc, parts.path) + 93 return url_string +
    94 + 95 +
    96 - def get_normalized_parameters(self, params): +
    97 if params is None: + 98 params = {} + 99 try: +100 # exclude the signature if it exists +101 del params['oauth_signature'] +102 except: +103 pass +104 key_values = [] +105 +106 for key, values in params.iteritems(): +107 if isinstance(values, file): +108 continue +109 if isinstance(values, (int, long, float)): +110 values = str(values) +111 if isinstance(values, (list, tuple)): +112 values = [str(v) for v in values] +113 if isinstance(values, basestring): +114 values = [values] +115 if USE_DOUBLE_ESCAPE_HACK and not key.startswith("ouath"): +116 key = escape(key) +117 for v in values: +118 v = v.encode("utf-8") +119 key = key.encode("utf-8") +120 if USE_DOUBLE_ESCAPE_HACK and not key.startswith("oauth"): +121 # this is a dirty hack to make the +122 # thing work with the current server-side +123 # implementation. Or is it by spec? +124 v = escape(v) +125 key_values.append(escape("%s=%s" % (key, v))) +126 # sort lexicographically, first after key, then after value +127 key_values.sort() +128 # combine key value pairs in string +129 return escape('&').join(key_values) +
    130 +131 +
    132 -class OAuthAuthenticator(object): +
    133 OAUTH_API_VERSION = '1.0' +134 AUTHORIZATION_HEADER = "Authorization" +135 +
    136 - def __init__(self, consumer, consumer_secret, token, secret, signature_method=OAuthSignatureMethod_HMAC_SHA1()): +
    137 self._consumer, self._token, self._secret = consumer, token, secret +138 self._consumer_secret = consumer_secret +139 self._signature_method = signature_method +140 random.seed() +
    141 +142 +
    143 - def augment_request(self, req, parameters, use_multipart=False, oauth_callback=None, oauth_verifier=None): +
    144 oauth_parameters = { +145 'oauth_consumer_key': self._consumer, +146 'oauth_timestamp': self.generate_timestamp(), +147 'oauth_nonce': self.generate_nonce(), +148 'oauth_version': self.OAUTH_API_VERSION, +149 'oauth_signature_method' : self._signature_method.get_name(), +150 #'realm' : "http://soundcloud.com", +151 } +152 if self._token is not None: +153 oauth_parameters['oauth_token'] = self._token +154 +155 if oauth_callback is not None: +156 oauth_parameters['oauth_callback'] = oauth_callback +157 +158 if oauth_verifier is not None: +159 oauth_parameters['oauth_verifier'] = oauth_verifier +160 +161 # in case we upload large files, we don't +162 # sign the request over the parameters +163 if use_multipart: +164 parameters = None +165 +166 oauth_parameters['oauth_signature'] = self._signature_method.build_signature(req, +167 parameters, +168 self._consumer_secret, +169 self._secret, +170 oauth_parameters) +171 def to_header(d): +172 return ",".join('%s="%s"' % (key, value) for key, value in sorted(oauth_parameters.items())) +
    173 +174 req.add_header(self.AUTHORIZATION_HEADER, "OAuth %s" % to_header(oauth_parameters)) +
    175 +
    176 - def generate_timestamp(self): +
    177 return int(time.time())# * 1000.0) +
    178 +
    179 - def generate_nonce(self, length=8): +
    180 return ''.join(str(random.randint(0, 9)) for i in range(length)) +
    181 +182 +
    183 -class BasicAuthenticator(object): +
    184 +
    185 - def __init__(self, user, password, consumer, consumer_secret): +
    186 self._base64string = base64.encodestring("%s:%s" % (user, password))[:-1] +187 self._x_auth_header = 'OAuth oauth_consumer_key="%s" oauth_consumer_secret="%s"' % (consumer, consumer_secret) +
    188 +
    189 - def augment_request(self, req, parameters): +
    190 req.add_header("Authorization", "Basic %s" % self._base64string) +191 req.add_header("X-Authorization", self._x_auth_header) +
    192 +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + diff --git a/python_apps/soundcloud-api/docs/api/scapi.authentication.BasicAuthenticator-class.html b/python_apps/soundcloud-api/docs/api/scapi.authentication.BasicAuthenticator-class.html new file mode 100644 index 000000000..437ef8444 --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/scapi.authentication.BasicAuthenticator-class.html @@ -0,0 +1,267 @@ + + + + + scapi.authentication.BasicAuthenticator + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package scapi :: + Module authentication :: + Class BasicAuthenticator + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class BasicAuthenticator

    source code

    +
    +object --+
    +         |
    +        BasicAuthenticator
    +
    + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + user, + password, + consumer, + consumer_secret)
    + x.__init__(...) initializes x; see x.__class__.__doc__ for signature
    + source code + +
    + +
    +   + + + + + + +
    augment_request(self, + req, + parameters) + source code + +
    + +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + user, + password, + consumer, + consumer_secret) +
    (Constructor) +

    +
    source code  +
    + +

    x.__init__(...) initializes x; see x.__class__.__doc__ for + signature

    +
    +
    Overrides: + object.__init__ +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + diff --git a/python_apps/soundcloud-api/docs/api/scapi.authentication.OAuthAuthenticator-class.html b/python_apps/soundcloud-api/docs/api/scapi.authentication.OAuthAuthenticator-class.html new file mode 100644 index 000000000..de4541aa2 --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/scapi.authentication.OAuthAuthenticator-class.html @@ -0,0 +1,337 @@ + + + + + scapi.authentication.OAuthAuthenticator + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package scapi :: + Module authentication :: + Class OAuthAuthenticator + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class OAuthAuthenticator

    source code

    +
    +object --+
    +         |
    +        OAuthAuthenticator
    +
    + +
    + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + consumer, + consumer_secret, + token, + secret, + signature_method=OAuthSignatureMethod_HMAC_SHA1())
    + x.__init__(...) initializes x; see x.__class__.__doc__ for signature
    + source code + +
    + +
    +   + + + + + + +
    augment_request(self, + req, + parameters, + use_multipart=False, + oauth_callback=None, + oauth_verifier=None) + source code + +
    + +
    +   + + + + + + +
    generate_timestamp(self) + source code + +
    + +
    +   + + + + + + +
    generate_nonce(self, + length=8) + source code + +
    + +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + OAUTH_API_VERSION = '1.0' +
    +   + + AUTHORIZATION_HEADER = 'Authorization' +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + consumer, + consumer_secret, + token, + secret, + signature_method=OAuthSignatureMethod_HMAC_SHA1()) +
    (Constructor) +

    +
    source code  +
    + +

    x.__init__(...) initializes x; see x.__class__.__doc__ for + signature

    +
    +
    Overrides: + object.__init__ +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + diff --git a/python_apps/soundcloud-api/docs/api/scapi.authentication.OAuthSignatureMethod_HMAC_SHA1-class.html b/python_apps/soundcloud-api/docs/api/scapi.authentication.OAuthSignatureMethod_HMAC_SHA1-class.html new file mode 100644 index 000000000..82e3a5ee0 --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/scapi.authentication.OAuthSignatureMethod_HMAC_SHA1-class.html @@ -0,0 +1,294 @@ + + + + + scapi.authentication.OAuthSignatureMethod_HMAC_SHA1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package scapi :: + Module authentication :: + Class OAuthSignatureMethod_HMAC_SHA1 + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class OAuthSignatureMethod_HMAC_SHA1

    source code

    +
    +object --+
    +         |
    +        OAuthSignatureMethod_HMAC_SHA1
    +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    get_name(self) + source code + +
    + +
    +   + + + + + + +
    build_signature(self, + request, + parameters, + consumer_secret, + token_secret, + oauth_parameters) + source code + +
    + +
    +   + + + + + + +
    get_normalized_http_method(self, + request) + source code + +
    + +
    +   + + + + + + +
    get_normalized_http_url(self, + request) + source code + +
    + +
    +   + + + + + + +
    get_normalized_parameters(self, + params) + source code + +
    + +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __init__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + FORBIDDEN = ['realm', 'oauth_signature'] +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + diff --git a/python_apps/soundcloud-api/docs/api/scapi.config-module.html b/python_apps/soundcloud-api/docs/api/scapi.config-module.html new file mode 100644 index 000000000..41aa49291 --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/scapi.config-module.html @@ -0,0 +1,114 @@ + + + + + scapi.config + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package scapi :: + Module config + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Module config

    source code

    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + diff --git a/python_apps/soundcloud-api/docs/api/scapi.config-pysrc.html b/python_apps/soundcloud-api/docs/api/scapi.config-pysrc.html new file mode 100644 index 000000000..27eedf60c --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/scapi.config-pysrc.html @@ -0,0 +1,122 @@ + + + + + scapi.config + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package scapi :: + Module config + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    +

    Source Code for Module scapi.config

    +
    +1   
    +2   
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + diff --git a/python_apps/soundcloud-api/docs/api/scapi.json-module.html b/python_apps/soundcloud-api/docs/api/scapi.json-module.html new file mode 100644 index 000000000..9ac8f0d48 --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/scapi.json-module.html @@ -0,0 +1,218 @@ + + + + + scapi.json + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package scapi :: + Module json + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Module json

    source code

    + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Classes[hide private]
    +
    +   + + _StringGenerator +
    +   + + WriteException +
    +   + + ReadException +
    +   + + JsonReader +
    +   + + JsonWriter +
    + + + + + + + + + + + + +
    + + + + + +
    Functions[hide private]
    +
    +   + + + + + + +
    write(obj, + escaped_forward_slash=False) + source code + +
    + +
    +   + + + + + + +
    read(s) + source code + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + diff --git a/python_apps/soundcloud-api/docs/api/scapi.json-pysrc.html b/python_apps/soundcloud-api/docs/api/scapi.json-pysrc.html new file mode 100644 index 000000000..cde009caf --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/scapi.json-pysrc.html @@ -0,0 +1,433 @@ + + + + + scapi.json + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package scapi :: + Module json + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    +

    Source Code for Module scapi.json

    +
    +  1  import string 
    +  2  import types 
    +  3   
    +  4  ##    json.py implements a JSON (http://json.org) reader and writer. 
    +  5  ##    Copyright (C) 2005  Patrick D. Logan 
    +  6  ##    Contact mailto:patrickdlogan@stardecisions.com 
    +  7  ## 
    +  8  ##    This library is free software; you can redistribute it and/or 
    +  9  ##    modify it under the terms of the GNU Lesser General Public 
    + 10  ##    License as published by the Free Software Foundation; either 
    + 11  ##    version 2.1 of the License, or (at your option) any later version. 
    + 12  ## 
    + 13  ##    This library is distributed in the hope that it will be useful, 
    + 14  ##    but WITHOUT ANY WARRANTY; without even the implied warranty of 
    + 15  ##    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
    + 16  ##    Lesser General Public License for more details. 
    + 17  ## 
    + 18  ##    You should have received a copy of the GNU Lesser General Public 
    + 19  ##    License along with this library; if not, write to the Free Software 
    + 20  ##    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
    + 21   
    + 22   
    +
    23 -class _StringGenerator(object): +
    24 - def __init__(self, string): +
    25 self.string = string + 26 self.index = -1 +
    27 - def peek(self): +
    28 i = self.index + 1 + 29 if i < len(self.string): + 30 return self.string[i] + 31 else: + 32 return None +
    33 - def next(self): +
    34 self.index += 1 + 35 if self.index < len(self.string): + 36 return self.string[self.index] + 37 else: + 38 raise StopIteration +
    39 - def all(self): +
    40 return self.string +
    41 +
    42 -class WriteException(Exception): +
    43 pass +
    44 +
    45 -class ReadException(Exception): +
    46 pass +
    47 +
    48 -class JsonReader(object): +
    49 hex_digits = {'A': 10,'B': 11,'C': 12,'D': 13,'E': 14,'F':15} + 50 escapes = {'t':'\t','n':'\n','f':'\f','r':'\r','b':'\b'} + 51 +
    52 - def read(self, s): +
    53 self._generator = _StringGenerator(s) + 54 result = self._read() + 55 return result +
    56 +
    57 - def _read(self): +
    58 self._eatWhitespace() + 59 peek = self._peek() + 60 if peek is None: + 61 raise ReadException, "Nothing to read: '%s'" % self._generator.all() + 62 if peek == '{': + 63 return self._readObject() + 64 elif peek == '[': + 65 return self._readArray() + 66 elif peek == '"': + 67 return self._readString() + 68 elif peek == '-' or peek.isdigit(): + 69 return self._readNumber() + 70 elif peek == 't': + 71 return self._readTrue() + 72 elif peek == 'f': + 73 return self._readFalse() + 74 elif peek == 'n': + 75 return self._readNull() + 76 elif peek == '/': + 77 self._readComment() + 78 return self._read() + 79 else: + 80 raise ReadException, "Input is not valid JSON: '%s'" % self._generator.all() +
    81 +
    82 - def _readTrue(self): +
    83 self._assertNext('t', "true") + 84 self._assertNext('r', "true") + 85 self._assertNext('u', "true") + 86 self._assertNext('e', "true") + 87 return True +
    88 +
    89 - def _readFalse(self): +
    90 self._assertNext('f', "false") + 91 self._assertNext('a', "false") + 92 self._assertNext('l', "false") + 93 self._assertNext('s', "false") + 94 self._assertNext('e', "false") + 95 return False +
    96 +
    97 - def _readNull(self): +
    98 self._assertNext('n', "null") + 99 self._assertNext('u', "null") +100 self._assertNext('l', "null") +101 self._assertNext('l', "null") +102 return None +
    103 +
    104 - def _assertNext(self, ch, target): +
    105 if self._next() != ch: +106 raise ReadException, "Trying to read %s: '%s'" % (target, self._generator.all()) +
    107 +
    108 - def _readNumber(self): +
    109 isfloat = False +110 result = self._next() +111 peek = self._peek() +112 while peek is not None and (peek.isdigit() or peek == "."): +113 isfloat = isfloat or peek == "." +114 result = result + self._next() +115 peek = self._peek() +116 try: +117 if isfloat: +118 return float(result) +119 else: +120 return int(result) +121 except ValueError: +122 raise ReadException, "Not a valid JSON number: '%s'" % result +
    123 +
    124 - def _readString(self): +
    125 result = "" +126 assert self._next() == '"' +127 try: +128 while self._peek() != '"': +129 ch = self._next() +130 if ch == "\\": +131 ch = self._next() +132 if ch in 'brnft': +133 ch = self.escapes[ch] +134 elif ch == "u": +135 ch4096 = self._next() +136 ch256 = self._next() +137 ch16 = self._next() +138 ch1 = self._next() +139 n = 4096 * self._hexDigitToInt(ch4096) +140 n += 256 * self._hexDigitToInt(ch256) +141 n += 16 * self._hexDigitToInt(ch16) +142 n += self._hexDigitToInt(ch1) +143 ch = unichr(n) +144 elif ch not in '"/\\': +145 raise ReadException, "Not a valid escaped JSON character: '%s' in %s" % (ch, self._generator.all()) +146 result = result + ch +147 except StopIteration: +148 raise ReadException, "Not a valid JSON string: '%s'" % self._generator.all() +149 assert self._next() == '"' +150 return result +
    151 +
    152 - def _hexDigitToInt(self, ch): +
    153 try: +154 result = self.hex_digits[ch.upper()] +155 except KeyError: +156 try: +157 result = int(ch) +158 except ValueError: +159 raise ReadException, "The character %s is not a hex digit." % ch +160 return result +
    161 +
    162 - def _readComment(self): +
    163 assert self._next() == "/" +164 second = self._next() +165 if second == "/": +166 self._readDoubleSolidusComment() +167 elif second == '*': +168 self._readCStyleComment() +169 else: +170 raise ReadException, "Not a valid JSON comment: %s" % self._generator.all() +
    171 +
    172 - def _readCStyleComment(self): +
    173 try: +174 done = False +175 while not done: +176 ch = self._next() +177 done = (ch == "*" and self._peek() == "/") +178 if not done and ch == "/" and self._peek() == "*": +179 raise ReadException, "Not a valid JSON comment: %s, '/*' cannot be embedded in the comment." % self._generator.all() +180 self._next() +181 except StopIteration: +182 raise ReadException, "Not a valid JSON comment: %s, expected */" % self._generator.all() +
    183 +
    184 - def _readDoubleSolidusComment(self): +
    185 try: +186 ch = self._next() +187 while ch != "\r" and ch != "\n": +188 ch = self._next() +189 except StopIteration: +190 pass +
    191 +
    192 - def _readArray(self): +
    193 result = [] +194 assert self._next() == '[' +195 done = self._peek() == ']' +196 while not done: +197 item = self._read() +198 result.append(item) +199 self._eatWhitespace() +200 done = self._peek() == ']' +201 if not done: +202 ch = self._next() +203 if ch != ",": +204 raise ReadException, "Not a valid JSON array: '%s' due to: '%s'" % (self._generator.all(), ch) +205 assert ']' == self._next() +206 return result +
    207 +
    208 - def _readObject(self): +
    209 result = {} +210 assert self._next() == '{' +211 done = self._peek() == '}' +212 while not done: +213 key = self._read() +214 if type(key) is not types.StringType: +215 raise ReadException, "Not a valid JSON object key (should be a string): %s" % key +216 self._eatWhitespace() +217 ch = self._next() +218 if ch != ":": +219 raise ReadException, "Not a valid JSON object: '%s' due to: '%s'" % (self._generator.all(), ch) +220 self._eatWhitespace() +221 val = self._read() +222 result[key] = val +223 self._eatWhitespace() +224 done = self._peek() == '}' +225 if not done: +226 ch = self._next() +227 if ch != ",": +228 raise ReadException, "Not a valid JSON array: '%s' due to: '%s'" % (self._generator.all(), ch) +229 assert self._next() == "}" +230 return result +
    231 +
    232 - def _eatWhitespace(self): +
    233 p = self._peek() +234 while p is not None and p in string.whitespace or p == '/': +235 if p == '/': +236 self._readComment() +237 else: +238 self._next() +239 p = self._peek() +
    240 +
    241 - def _peek(self): +
    242 return self._generator.peek() +
    243 +
    244 - def _next(self): +
    245 return self._generator.next() +
    246 +
    247 -class JsonWriter(object): +
    248 +
    249 - def _append(self, s): +
    250 self._results.append(s) +
    251 +
    252 - def write(self, obj, escaped_forward_slash=False): +
    253 self._escaped_forward_slash = escaped_forward_slash +254 self._results = [] +255 self._write(obj) +256 return "".join(self._results) +
    257 +
    258 - def _write(self, obj): +
    259 ty = type(obj) +260 if ty is types.DictType: +261 n = len(obj) +262 self._append("{") +263 for k, v in obj.items(): +264 self._write(k) +265 self._append(":") +266 self._write(v) +267 n = n - 1 +268 if n > 0: +269 self._append(",") +270 self._append("}") +271 elif ty is types.ListType or ty is types.TupleType: +272 n = len(obj) +273 self._append("[") +274 for item in obj: +275 self._write(item) +276 n = n - 1 +277 if n > 0: +278 self._append(",") +279 self._append("]") +280 elif ty is types.StringType or ty is types.UnicodeType: +281 self._append('"') +282 obj = obj.replace('\\', r'\\') +283 if self._escaped_forward_slash: +284 obj = obj.replace('/', r'\/') +285 obj = obj.replace('"', r'\"') +286 obj = obj.replace('\b', r'\b') +287 obj = obj.replace('\f', r'\f') +288 obj = obj.replace('\n', r'\n') +289 obj = obj.replace('\r', r'\r') +290 obj = obj.replace('\t', r'\t') +291 self._append(obj) +292 self._append('"') +293 elif ty is types.IntType or ty is types.LongType: +294 self._append(str(obj)) +295 elif ty is types.FloatType: +296 self._append("%f" % obj) +297 elif obj is True: +298 self._append("true") +299 elif obj is False: +300 self._append("false") +301 elif obj is None: +302 self._append("null") +303 else: +304 raise WriteException, "Cannot write in JSON: %s" % repr(obj) +
    305 +
    306 -def write(obj, escaped_forward_slash=False): +
    307 return JsonWriter().write(obj, escaped_forward_slash) +
    308 +
    309 -def read(s): +
    310 return JsonReader().read(s) +
    311 +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + diff --git a/python_apps/soundcloud-api/docs/api/scapi.json.JsonReader-class.html b/python_apps/soundcloud-api/docs/api/scapi.json.JsonReader-class.html new file mode 100644 index 000000000..bad1801ae --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/scapi.json.JsonReader-class.html @@ -0,0 +1,544 @@ + + + + + scapi.json.JsonReader + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package scapi :: + Module json :: + Class JsonReader + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class JsonReader

    source code

    +
    +object --+
    +         |
    +        JsonReader
    +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    read(self, + s) + source code + +
    + +
    +   + + + + + + +
    _read(self) + source code + +
    + +
    +   + + + + + + +
    _readTrue(self) + source code + +
    + +
    +   + + + + + + +
    _readFalse(self) + source code + +
    + +
    +   + + + + + + +
    _readNull(self) + source code + +
    + +
    +   + + + + + + +
    _assertNext(self, + ch, + target) + source code + +
    + +
    +   + + + + + + +
    _readNumber(self) + source code + +
    + +
    +   + + + + + + +
    _readString(self) + source code + +
    + +
    +   + + + + + + +
    _hexDigitToInt(self, + ch) + source code + +
    + +
    +   + + + + + + +
    _readComment(self) + source code + +
    + +
    +   + + + + + + +
    _readCStyleComment(self) + source code + +
    + +
    +   + + + + + + +
    _readDoubleSolidusComment(self) + source code + +
    + +
    +   + + + + + + +
    _readArray(self) + source code + +
    + +
    +   + + + + + + +
    _readObject(self) + source code + +
    + +
    +   + + + + + + +
    _eatWhitespace(self) + source code + +
    + +
    +   + + + + + + +
    _peek(self) + source code + +
    + +
    +   + + + + + + +
    _next(self) + source code + +
    + +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __init__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + hex_digits = {'A': 10, 'B': 11, 'C': 12, 'D': 13, 'E': 14, 'F'... +
    +   + + escapes = {'b': '\x08', 'f': '\x0c', 'n': '\n', 'r': '\r', 't'... +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Class Variable Details[hide private]
    +
    + +
    + +
    +

    hex_digits

    + +
    +
    +
    +
    Value:
    +
    +{'A': 10, 'B': 11, 'C': 12, 'D': 13, 'E': 14, 'F': 15}
    +
    +
    +
    +
    +
    + +
    + +
    +

    escapes

    + +
    +
    +
    +
    Value:
    +
    +{'b': '\x08', 'f': '\x0c', 'n': '\n', 'r': '\r', 't': '\t'}
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + diff --git a/python_apps/soundcloud-api/docs/api/scapi.json.JsonWriter-class.html b/python_apps/soundcloud-api/docs/api/scapi.json.JsonWriter-class.html new file mode 100644 index 000000000..c376a942f --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/scapi.json.JsonWriter-class.html @@ -0,0 +1,233 @@ + + + + + scapi.json.JsonWriter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package scapi :: + Module json :: + Class JsonWriter + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class JsonWriter

    source code

    +
    +object --+
    +         |
    +        JsonWriter
    +
    + +
    + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    _append(self, + s) + source code + +
    + +
    +   + + + + + + +
    write(self, + obj, + escaped_forward_slash=False) + source code + +
    + +
    +   + + + + + + +
    _write(self, + obj) + source code + +
    + +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __init__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + diff --git a/python_apps/soundcloud-api/docs/api/scapi.json.ReadException-class.html b/python_apps/soundcloud-api/docs/api/scapi.json.ReadException-class.html new file mode 100644 index 000000000..acbf8e4c3 --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/scapi.json.ReadException-class.html @@ -0,0 +1,196 @@ + + + + + scapi.json.ReadException + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package scapi :: + Module json :: + Class ReadException + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class ReadException

    source code

    +
    +              object --+        
    +                       |        
    +exceptions.BaseException --+    
    +                           |    
    +        exceptions.Exception --+
    +                               |
    +                              ReadException
    +
    + +
    + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from exceptions.Exception: + __init__, + __new__ +

    +

    Inherited from exceptions.BaseException: + __delattr__, + __getattribute__, + __getitem__, + __getslice__, + __reduce__, + __repr__, + __setattr__, + __setstate__, + __str__ +

    +

    Inherited from object: + __hash__, + __reduce_ex__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from exceptions.BaseException: + args, + message +

    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + diff --git a/python_apps/soundcloud-api/docs/api/scapi.json.WriteException-class.html b/python_apps/soundcloud-api/docs/api/scapi.json.WriteException-class.html new file mode 100644 index 000000000..b97e08ff6 --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/scapi.json.WriteException-class.html @@ -0,0 +1,196 @@ + + + + + scapi.json.WriteException + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package scapi :: + Module json :: + Class WriteException + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class WriteException

    source code

    +
    +              object --+        
    +                       |        
    +exceptions.BaseException --+    
    +                           |    
    +        exceptions.Exception --+
    +                               |
    +                              WriteException
    +
    + +
    + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from exceptions.Exception: + __init__, + __new__ +

    +

    Inherited from exceptions.BaseException: + __delattr__, + __getattribute__, + __getitem__, + __getslice__, + __reduce__, + __repr__, + __setattr__, + __setstate__, + __str__ +

    +

    Inherited from object: + __hash__, + __reduce_ex__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from exceptions.BaseException: + args, + message +

    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + diff --git a/python_apps/soundcloud-api/docs/api/scapi.json._StringGenerator-class.html b/python_apps/soundcloud-api/docs/api/scapi.json._StringGenerator-class.html new file mode 100644 index 000000000..ead736d6f --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/scapi.json._StringGenerator-class.html @@ -0,0 +1,291 @@ + + + + + scapi.json._StringGenerator + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package scapi :: + Module json :: + Class _StringGenerator + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class _StringGenerator

    source code

    +
    +object --+
    +         |
    +        _StringGenerator
    +
    + +
    + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + string)
    + x.__init__(...) initializes x; see x.__class__.__doc__ for signature
    + source code + +
    + +
    +   + + + + + + +
    peek(self) + source code + +
    + +
    +   + + + + + + +
    next(self) + source code + +
    + +
    +   + + + + + + +
    all(self) + source code + +
    + +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + string) +
    (Constructor) +

    +
    source code  +
    + +

    x.__init__(...) initializes x; see x.__class__.__doc__ for + signature

    +
    +
    Overrides: + object.__init__ +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + diff --git a/python_apps/soundcloud-api/docs/api/scapi.tests-module.html b/python_apps/soundcloud-api/docs/api/scapi.tests-module.html new file mode 100644 index 000000000..41a0acbfc --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/scapi.tests-module.html @@ -0,0 +1,140 @@ + + + + + scapi.tests + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package scapi :: + Package tests + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Package tests

    source code

    + + + + + + + +
    + + + + + +
    Submodules[hide private]
    +
    +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + diff --git a/python_apps/soundcloud-api/docs/api/scapi.tests-pysrc.html b/python_apps/soundcloud-api/docs/api/scapi.tests-pysrc.html new file mode 100644 index 000000000..a967c5152 --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/scapi.tests-pysrc.html @@ -0,0 +1,122 @@ + + + + + scapi.tests + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package scapi :: + Package tests + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    +

    Source Code for Package scapi.tests

    +
    +1   
    +2   
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + diff --git a/python_apps/soundcloud-api/docs/api/scapi.tests.scapi_tests-module.html b/python_apps/soundcloud-api/docs/api/scapi.tests.scapi_tests-module.html new file mode 100644 index 000000000..cff686d84 --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/scapi.tests.scapi_tests-module.html @@ -0,0 +1,172 @@ + + + + + scapi.tests.scapi_tests + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package scapi :: + Package tests :: + Module scapi_tests + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Module scapi_tests

    source code

    + + + + + + + + + +
    + + + + + +
    Classes[hide private]
    +
    +   + + SCAPITests +
    + + + + + + + + + + + + +
    + + + + + +
    Variables[hide private]
    +
    +   + + logger = logging.getLogger("scapi.tests") +
    +   + + api_logger = logging.getLogger("scapi") +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + diff --git a/python_apps/soundcloud-api/docs/api/scapi.tests.scapi_tests-pysrc.html b/python_apps/soundcloud-api/docs/api/scapi.tests.scapi_tests-pysrc.html new file mode 100644 index 000000000..bca835c59 --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/scapi.tests.scapi_tests-pysrc.html @@ -0,0 +1,760 @@ + + + + + scapi.tests.scapi_tests + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package scapi :: + Package tests :: + Module scapi_tests + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    +

    Source Code for Module scapi.tests.scapi_tests

    +
    +  1  from __future__ import with_statement 
    +  2   
    +  3  import os 
    +  4  import urllib2 
    +  5  import itertools 
    +  6  from textwrap import dedent 
    +  7  import pkg_resources 
    +  8  import logging 
    +  9  import webbrowser 
    + 10  from unittest import TestCase 
    + 11   
    + 12  from configobj import ConfigObj 
    + 13  from validate import Validator 
    + 14   
    + 15   
    + 16  import scapi 
    + 17  import scapi.authentication 
    + 18   
    + 19  logger = logging.getLogger("scapi.tests") 
    + 20   
    + 21  api_logger = logging.getLogger("scapi") 
    +
    22 + 23 + 24 -class SCAPITests(TestCase): +
    25 + 26 CONFIG_NAME = "test.ini" + 27 TOKEN = None + 28 SECRET = None + 29 CONSUMER = None + 30 CONSUMER_SECRET = None + 31 API_HOST = None + 32 USER = None + 33 PASSWORD = None + 34 AUTHENTICATOR = None + 35 RUN_INTERACTIVE_TESTS = False + 36 + 37 +
    38 - def setUp(self): +
    39 self._load_config() + 40 assert pkg_resources.resource_exists("scapi.tests.test_connect", "knaster.mp3") + 41 self.data = pkg_resources.resource_stream("scapi.tests.test_connect", "knaster.mp3") + 42 self.artwork_data = pkg_resources.resource_stream("scapi.tests.test_connect", "spam.jpg") +
    43 + 44 CONFIGSPEC=dedent(""" + 45 [api] + 46 token=string + 47 secret=string + 48 consumer=string + 49 consumer_secret=string + 50 api_host=string + 51 user=string + 52 password=string + 53 authenticator=option('oauth', 'base', default='oauth') + 54 + 55 [proxy] + 56 use_proxy=boolean(default=false) + 57 proxy=string(default=http://127.0.0.1:10000/) + 58 + 59 [logging] + 60 test_logger=string(default=ERROR) + 61 api_logger=string(default=ERROR) + 62 + 63 [test] + 64 run_interactive_tests=boolean(default=false) + 65 """) + 66 + 67 +
    68 - def _load_config(self): +
    69 """ + 70 Loads the configuration by looking from + 71 + 72 - the environment variable SCAPI_CONFIG + 73 - the installation location upwards until it finds test.ini + 74 - the current working directory upwards until it finds test.ini + 75 + 76 Raises an error if there is no config found + 77 """ + 78 config_name = self.CONFIG_NAME + 79 + 80 name = None + 81 + 82 if "SCAPI_CONFIG" in os.environ: + 83 if os.path.exists(os.environ["SCAPI_CONFIG"]): + 84 name = os.environ["SCAPI_CONFIG"] + 85 + 86 def search_for_config(current): + 87 while current: + 88 name = os.path.join(current, config_name) + 89 if os.path.exists(name): + 90 return name + 91 new_current = os.path.dirname(current) + 92 if new_current == current: + 93 return + 94 current = new_current +
    95 + 96 if name is None: + 97 name = search_for_config(os.path.dirname(__file__)) + 98 if name is None: + 99 name = search_for_config(os.getcwd()) +100 +101 if not name: +102 raise Exception("No test configuration file found!") +103 +104 parser = ConfigObj(name, configspec=self.CONFIGSPEC.split("\n")) +105 val = Validator() +106 if not parser.validate(val): +107 raise Exception("Config file validation error") +108 +109 api = parser['api'] +110 self.TOKEN = api.get('token') +111 self.SECRET = api.get('secret') +112 self.CONSUMER = api.get('consumer') +113 self.CONSUMER_SECRET = api.get('consumer_secret') +114 self.API_HOST = api.get('api_host') +115 self.USER = api.get('user', None) +116 self.PASSWORD = api.get('password', None) +117 self.AUTHENTICATOR = api.get("authenticator") +118 +119 # reset the hard-coded values in the api +120 if self.API_HOST: +121 scapi.AUTHORIZATION_URL = "http://%s/oauth/authorize" % self.API_HOST +122 scapi.REQUEST_TOKEN_URL = 'http://%s/oauth/request_token' % self.API_HOST +123 scapi.ACCESS_TOKEN_URL = 'http://%s/oauth/access_token' % self.API_HOST +124 +125 if "proxy" in parser and parser["proxy"]["use_proxy"]: +126 scapi.USE_PROXY = True +127 scapi.PROXY = parser["proxy"]["proxy"] +128 +129 if "logging" in parser: +130 logger.setLevel(getattr(logging, parser["logging"]["test_logger"])) +131 api_logger.setLevel(getattr(logging, parser["logging"]["api_logger"])) +132 +133 self.RUN_INTERACTIVE_TESTS = parser["test"]["run_interactive_tests"] +
    134 +135 +136 @property +
    137 - def root(self): +
    138 """ +139 Return the properly configured root-scope. +140 """ +141 if self.AUTHENTICATOR == "oauth": +142 authenticator = scapi.authentication.OAuthAuthenticator(self.CONSUMER, +143 self.CONSUMER_SECRET, +144 self.TOKEN, +145 self.SECRET) +146 elif self.AUTHENTICATOR == "base": +147 authenticator = scapi.authentication.BasicAuthenticator(self.USER, self.PASSWORD, self.CONSUMER, self.CONSUMER_SECRET) +148 else: +149 raise Exception("Unknown authenticator setting: %s", self.AUTHENTICATOR) +150 +151 connector = scapi.ApiConnector(host=self.API_HOST, +152 authenticator=authenticator) +153 +154 logger.debug("RootScope: %s authenticator: %s", self.API_HOST, self.AUTHENTICATOR) +155 return scapi.Scope(connector) +
    156 +157 +
    158 - def test_connect(self): +
    159 """ +160 test_connect +161 +162 Tries to connect & performs some read-only operations. +163 """ +164 sca = self.root +165 # quite_a_few_users = list(itertools.islice(sca.users(), 0, 127)) +166 +167 # logger.debug(quite_a_few_users) +168 # assert isinstance(quite_a_few_users, list) and isinstance(quite_a_few_users[0], scapi.User) +169 user = sca.me() +170 logger.debug(user) +171 assert isinstance(user, scapi.User) +172 contacts = list(user.contacts()) +173 assert isinstance(contacts, list) +174 if contacts: +175 assert isinstance(contacts[0], scapi.User) +176 logger.debug(contacts) +177 tracks = list(user.tracks()) +178 assert isinstance(tracks, list) +179 if tracks: +180 assert isinstance(tracks[0], scapi.Track) +181 logger.debug(tracks) +
    182 +183 +
    185 """ +186 This test is commented out because it needs user-interaction. +187 """ +188 if not self.RUN_INTERACTIVE_TESTS: +189 return +190 oauth_authenticator = scapi.authentication.OAuthAuthenticator(self.CONSUMER, +191 self.CONSUMER_SECRET, +192 None, +193 None) +194 +195 sca = scapi.ApiConnector(host=self.API_HOST, authenticator=oauth_authenticator) +196 token, secret = sca.fetch_request_token() +197 authorization_url = sca.get_request_token_authorization_url(token) +198 webbrowser.open(authorization_url) +199 oauth_verifier = raw_input("please enter verifier code as seen in the browser:") +200 +201 oauth_authenticator = scapi.authentication.OAuthAuthenticator(self.CONSUMER, +202 self.CONSUMER_SECRET, +203 token, +204 secret) +205 +206 sca = scapi.ApiConnector(self.API_HOST, authenticator=oauth_authenticator) +207 token, secret = sca.fetch_access_token(oauth_verifier) +208 logger.info("Access token: '%s'", token) +209 logger.info("Access token secret: '%s'", secret) +210 # force oauth-authentication with the new parameters, and +211 # then invoke some simple test +212 self.AUTHENTICATOR = "oauth" +213 self.TOKEN = token +214 self.SECRET = secret +215 self.test_connect() +
    216 +217 +
    218 - def test_track_creation(self): +
    219 sca = self.root +220 track = sca.Track.new(title='bar', asset_data=self.data) +221 assert isinstance(track, scapi.Track) +
    222 +223 +
    224 - def test_track_update(self): +
    225 sca = self.root +226 track = sca.Track.new(title='bar', asset_data=self.data) +227 assert isinstance(track, scapi.Track) +228 track.title='baz' +229 track = sca.Track.get(track.id) +230 assert track.title == "baz" +
    231 +232 +
    233 - def test_scoped_track_creation(self): +
    234 sca = self.root +235 user = sca.me() +236 track = user.tracks.new(title="bar", asset_data=self.data) +237 assert isinstance(track, scapi.Track) +
    238 +239 +
    240 - def test_upload(self): +
    241 sca = self.root +242 sca = self.root +243 track = sca.Track.new(title='bar', asset_data=self.data) +244 assert isinstance(track, scapi.Track) +
    245 +246 +
    247 - def test_contact_list(self): +
    248 sca = self.root +249 user = sca.me() +250 contacts = list(user.contacts()) +251 assert isinstance(contacts, list) +252 if contacts: +253 assert isinstance(contacts[0], scapi.User) +
    254 +255 +
    256 - def test_permissions(self): +
    257 sca = self.root +258 user = sca.me() +259 tracks = itertools.islice(user.tracks(), 1) +260 for track in tracks: +261 permissions = list(track.permissions()) +262 logger.debug(permissions) +263 assert isinstance(permissions, list) +264 if permissions: +265 assert isinstance(permissions[0], scapi.User) +
    266 +267 +
    268 - def test_setting_permissions(self): +
    269 sca = self.root +270 me = sca.me() +271 track = sca.Track.new(title='bar', sharing="private", asset_data=self.data) +272 assert track.sharing == "private" +273 users = itertools.islice(sca.users(), 10) +274 users_to_set = [user for user in users if user != me] +275 assert users_to_set, "Didn't find any suitable users" +276 track.permissions = users_to_set +277 assert set(track.permissions()) == set(users_to_set) +
    278 +279 +
    280 - def test_setting_comments(self): +
    281 sca = self.root +282 user = sca.me() +283 track = sca.Track.new(title='bar', sharing="private", asset_data=self.data) +284 comment = sca.Comment.create(body="This is the body of my comment", timestamp=10) +285 track.comments = comment +286 assert track.comments().next().body == comment.body +
    287 +288 +
    290 sca = self.root +291 track = sca.Track.new(title='bar', sharing="private", asset_data=self.data) +292 cbody = "This is the body of my comment" +293 track.comments.new(body=cbody, timestamp=10) +294 assert list(track.comments())[0].body == cbody +
    295 +296 +
    298 sca = self.root +299 me = sca.me() +300 for user in sca.users(): +301 if user != me: +302 user_to_set = user +303 break +304 +305 contacts = list(me.contacts()) +306 if user_to_set in contacts: +307 me.contacts.remove(user_to_set) +308 +309 me.contacts.append(user_to_set) +310 +311 contacts = list(me.contacts() ) +312 assert user_to_set.id in [c.id for c in contacts] +313 +314 me.contacts.remove(user_to_set) +315 +316 contacts = list(me.contacts() ) +317 assert user_to_set not in contacts +
    318 +319 +
    320 - def test_favorites(self): +
    321 sca = self.root +322 me = sca.me() +323 +324 favorites = list(me.favorites()) +325 assert favorites == [] or isinstance(favorites[0], scapi.Track) +326 +327 track = None +328 for user in sca.users(): +329 if user == me: +330 continue +331 for track in user.tracks(): +332 break +333 if track is not None: +334 break +335 +336 me.favorites.append(track) +337 +338 favorites = list(me.favorites()) +339 assert track in favorites +340 +341 me.favorites.remove(track) +342 +343 favorites = list(me.favorites()) +344 assert track not in favorites +
    345 +346 +
    347 - def test_large_list(self): +
    348 sca = self.root +349 +350 tracks = list(sca.tracks()) +351 if len(tracks) < scapi.ApiConnector.LIST_LIMIT: +352 for i in xrange(scapi.ApiConnector.LIST_LIMIT): +353 sca.Track.new(title='test_track_%i' % i, asset_data=self.data) +354 all_tracks = sca.tracks() +355 assert not isinstance(all_tracks, list) +356 all_tracks = list(all_tracks) +357 assert len(all_tracks) > scapi.ApiConnector.LIST_LIMIT +
    358 +359 +360 +
    361 - def test_filtered_list(self): +
    362 sca = self.root +363 +364 tracks = list(sca.tracks(params={ +365 "bpm[from]" : "180", +366 })) +367 if len(tracks) < scapi.ApiConnector.LIST_LIMIT: +368 for i in xrange(scapi.ApiConnector.LIST_LIMIT): +369 sca.Track.new(title='test_track_%i' % i, asset_data=self.data) +370 all_tracks = sca.tracks() +371 assert not isinstance(all_tracks, list) +372 all_tracks = list(all_tracks) +373 assert len(all_tracks) > scapi.ApiConnector.LIST_LIMIT +
    374 +375 +
    376 - def test_events(self): +
    377 events = list(self.root.events()) +378 assert isinstance(events, list) +379 assert isinstance(events[0], scapi.Event) +
    380 +381 +
    382 - def test_me_having_stress(self): +
    383 sca = self.root +384 for _ in xrange(20): +385 self.setUp() +386 sca.me() +
    387 +388 +
    389 - def test_non_global_api(self): +
    390 root = self.root +391 me = root.me() +392 assert isinstance(me, scapi.User) +393 +394 # now get something *from* that user +395 list(me.favorites()) +
    396 +397 +
    398 - def test_playlists(self): +
    399 sca = self.root +400 playlists = list(itertools.islice(sca.playlists(), 0, 127)) +401 for playlist in playlists: +402 tracks = playlist.tracks +403 if not isinstance(tracks, list): +404 tracks = [tracks] +405 for trackdata in tracks: +406 print trackdata +407 #user = trackdata.user +408 #print user +409 #print user.tracks() +410 print playlist.user +411 break +
    412 +413 +414 +415 +
    416 - def test_playlist_creation(self): +
    417 sca = self.root +418 sca.Playlist.new(title="I'm so happy, happy, happy, happy!") +
    419 +420 +421 +
    422 - def test_groups(self): +
    423 sca = self.root +424 groups = list(itertools.islice(sca.groups(), 0, 127)) +425 for group in groups: +426 users = group.users() +427 for user in users: +428 pass +
    429 +430 +
    432 sca = self.root +433 emails = [dict(address="deets@web.de"), dict(address="hannes@soundcloud.com")] +434 track = sca.Track.new(title='bar', asset_data=self.data, +435 shared_to=dict(emails=emails) +436 ) +437 assert isinstance(track, scapi.Track) +
    438 +439 +440 +
    442 sca = self.root +443 track = sca.Track.new(title='bar', +444 asset_data=self.data, +445 artwork_data=self.artwork_data, +446 ) +447 assert isinstance(track, scapi.Track) +448 +449 track.title = "foobarbaz" +
    450 +451 +452 +
    453 - def test_oauth_get_signing(self): +
    454 sca = self.root +455 +456 url = "http://api.soundcloud.dev/oauth/test_request" +457 params = dict(foo="bar", +458 baz="padamm", +459 ) +460 url += sca._create_query_string(params) +461 signed_url = sca.oauth_sign_get_request(url) +462 +463 +464 res = urllib2.urlopen(signed_url).read() +465 assert "oauth_nonce" in res +
    466 +467 +
    468 - def test_streaming(self): +
    469 sca = self.root +470 +471 track = sca.tracks(params={ +472 "filter" : "streamable", +473 }).next() +474 +475 +476 assert isinstance(track, scapi.Track) +477 +478 stream_url = track.stream_url +479 +480 signed_url = track.oauth_sign_get_request(stream_url) +
    481 +482 +
    483 - def test_downloadable(self): +
    484 sca = self.root +485 +486 track = sca.tracks(params={ +487 "filter" : "downloadable", +488 }).next() +489 +490 +491 assert isinstance(track, scapi.Track) +492 +493 download_url = track.download_url +494 +495 signed_url = track.oauth_sign_get_request(download_url) +496 +497 data = urllib2.urlopen(signed_url).read() +498 assert data +
    499 +500 +501 +
    502 - def test_modifying_playlists(self): +
    503 sca = self.root +504 +505 me = sca.me() +506 my_tracks = list(me.tracks()) +507 +508 assert my_tracks +509 +510 playlist = me.playlists().next() +511 playlist = sca.Playlist.get(playlist.id) +512 +513 assert isinstance(playlist, scapi.Playlist) +514 +515 pl_tracks = playlist.tracks +516 +517 playlist.title = "foobarbaz" +
    518 +519 +520 +
    521 - def test_track_deletion(self): +
    522 sca = self.root +523 track = sca.Track.new(title='bar', asset_data=self.data, +524 ) +525 +526 sca.tracks.remove(track) +
    527 +528 +529 +
    531 sca = self.root +532 track = sca.Track.new(title='bar', +533 asset_data=self.data, +534 ) +535 assert isinstance(track, scapi.Track) +536 +537 track.artwork_data = self.artwork_data +
    538 +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + diff --git a/python_apps/soundcloud-api/docs/api/scapi.tests.scapi_tests.SCAPITests-class.html b/python_apps/soundcloud-api/docs/api/scapi.tests.scapi_tests.SCAPITests-class.html new file mode 100644 index 000000000..0c5a5a449 --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/scapi.tests.scapi_tests.SCAPITests-class.html @@ -0,0 +1,1025 @@ + + + + + scapi.tests.scapi_tests.SCAPITests + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package scapi :: + Package tests :: + Module scapi_tests :: + Class SCAPITests + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class SCAPITests

    source code

    +
    +       object --+    
    +                |    
    +unittest.TestCase --+
    +                    |
    +                   SCAPITests
    +
    + +
    + + + + + + + + + +
    + + + + + +
    Nested Classes[hide private]
    +
    +

    Inherited from unittest.TestCase: + failureException +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    setUp(self)
    + Hook method for setting up the test fixture before exercising it.
    + source code + +
    + +
    +   + + + + + + +
    _load_config(self)
    + Loads the configuration by looking from
    + source code + +
    + +
    +   + + + + + + +
    test_connect(self)
    + test_connect
    + source code + +
    + +
    +   + + + + + + +
    test_access_token_acquisition(self)
    + This test is commented out because it needs user-interaction.
    + source code + +
    + +
    +   + + + + + + +
    test_track_creation(self) + source code + +
    + +
    +   + + + + + + +
    test_track_update(self) + source code + +
    + +
    +   + + + + + + +
    test_scoped_track_creation(self) + source code + +
    + +
    +   + + + + + + +
    test_upload(self) + source code + +
    + +
    +   + + + + + + +
    test_contact_list(self) + source code + +
    + +
    +   + + + + + + +
    test_permissions(self) + source code + +
    + +
    +   + + + + + + +
    test_setting_permissions(self) + source code + +
    + +
    +   + + + + + + +
    test_setting_comments(self) + source code + +
    + +
    +   + + + + + + +
    test_setting_comments_the_way_shawn_says_its_correct(self) + source code + +
    + +
    +   + + + + + + +
    test_contact_add_and_removal(self) + source code + +
    + +
    +   + + + + + + +
    test_favorites(self) + source code + +
    + +
    +   + + + + + + +
    test_large_list(self) + source code + +
    + +
    +   + + + + + + +
    test_filtered_list(self) + source code + +
    + +
    +   + + + + + + +
    test_events(self) + source code + +
    + +
    +   + + + + + + +
    test_me_having_stress(self) + source code + +
    + +
    +   + + + + + + +
    test_non_global_api(self) + source code + +
    + +
    +   + + + + + + +
    test_playlists(self) + source code + +
    + +
    +   + + + + + + +
    test_playlist_creation(self) + source code + +
    + +
    +   + + + + + + +
    test_groups(self) + source code + +
    + +
    +   + + + + + + +
    test_track_creation_with_email_sharers(self) + source code + +
    + +
    +   + + + + + + +
    test_track_creation_with_artwork(self) + source code + +
    + +
    +   + + + + + + +
    test_oauth_get_signing(self) + source code + +
    + +
    +   + + + + + + +
    test_streaming(self) + source code + +
    + +
    +   + + + + + + +
    test_downloadable(self) + source code + +
    + +
    +   + + + + + + +
    test_modifying_playlists(self) + source code + +
    + +
    +   + + + + + + +
    test_track_deletion(self) + source code + +
    + +
    +   + + + + + + +
    test_track_creation_with_updated_artwork(self) + source code + +
    + +
    +

    Inherited from unittest.TestCase: + __call__, + __init__, + __repr__, + __str__, + assertAlmostEqual, + assertAlmostEquals, + assertEqual, + assertEquals, + assertFalse, + assertNotAlmostEqual, + assertNotAlmostEquals, + assertNotEqual, + assertNotEquals, + assertRaises, + assertTrue, + assert_, + countTestCases, + debug, + defaultTestResult, + fail, + failIf, + failIfAlmostEqual, + failIfEqual, + failUnless, + failUnlessAlmostEqual, + failUnlessEqual, + failUnlessRaises, + id, + run, + shortDescription, + tearDown +

    +

    Inherited from unittest.TestCase (private): + _exc_info +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __setattr__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + CONFIG_NAME = 'test.ini' +
    +   + + TOKEN = None +
    +   + + SECRET = None +
    +   + + CONSUMER = None +
    +   + + CONSUMER_SECRET = None +
    +   + + API_HOST = None +
    +   + + USER = None +
    +   + + PASSWORD = None +
    +   + + AUTHENTICATOR = None +
    +   + + RUN_INTERACTIVE_TESTS = False +
    +   + + CONFIGSPEC = '\n[api]\ntoken=string\nsecret=string\nconsumer=s... +
    + + + + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +   + + root
    + Return the properly configured root-scope. +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    setUp(self) +

    +
    source code  +
    + +

    Hook method for setting up the test fixture before exercising it.

    +
    +
    Overrides: + unittest.TestCase.setUp +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    _load_config(self) +

    +
    source code  +
    + +

    Loads the configuration by looking from

    +
      +
    • + the environment variable SCAPI_CONFIG +
    • +
    • + the installation location upwards until it finds test.ini +
    • +
    • + the current working directory upwards until it finds test.ini +
    • +
    +

    Raises an error if there is no config found

    +
    +
    +
    +
    + +
    + +
    + + +
    +

    test_connect(self) +

    +
    source code  +
    + +

    test_connect

    +

    Tries to connect & performs some read-only operations.

    +
    +
    +
    +
    +
    + + + + + + +
    + + + + + +
    Class Variable Details[hide private]
    +
    + +
    + +
    +

    CONFIGSPEC

    + +
    +
    +
    +
    Value:
    +
    +'''
    +[api]
    +token=string
    +secret=string
    +consumer=string
    +consumer_secret=string
    +api_host=string
    +user=string
    +...
    +
    +
    +
    +
    +
    +
    + + + + + + +
    + + + + + +
    Property Details[hide private]
    +
    + +
    + +
    +

    root

    +

    Return the properly configured root-scope.

    +
    +
    Get Method:
    +
    unreachable.root(self) + - Return the properly configured root-scope. +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + diff --git a/python_apps/soundcloud-api/docs/api/scapi.tests.test_connect-module.html b/python_apps/soundcloud-api/docs/api/scapi.tests.test_connect-module.html new file mode 100644 index 000000000..e4780ff79 --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/scapi.tests.test_connect-module.html @@ -0,0 +1,586 @@ + + + + + scapi.tests.test_connect + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package scapi :: + Package tests :: + Module test_connect + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Module test_connect

    source code

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Functions[hide private]
    +
    +   + + + + + + +
    setup() + source code + +
    + +
    +   + + + + + + +
    load_config(config_name=None) + source code + +
    + +
    +   + + + + + + +
    test_load_config() + source code + +
    + +
    +   + + + + + + +
    test_connect() + source code + +
    + +
    +   + + + + + + +
    test_access_token_acquisition()
    + This test is commented out because it needs user-interaction.
    + source code + +
    + +
    +   + + + + + + +
    test_track_creation() + source code + +
    + +
    +   + + + + + + +
    test_track_update() + source code + +
    + +
    +   + + + + + + +
    test_scoped_track_creation() + source code + +
    + +
    +   + + + + + + +
    test_upload() + source code + +
    + +
    +   + + + + + + +
    test_contact_list() + source code + +
    + +
    +   + + + + + + +
    test_permissions() + source code + +
    + +
    +   + + + + + + +
    test_setting_permissions() + source code + +
    + +
    +   + + + + + + +
    test_setting_comments() + source code + +
    + +
    +   + + + + + + +
    test_setting_comments_the_way_shawn_says_its_correct() + source code + +
    + +
    +   + + + + + + +
    test_contact_add_and_removal() + source code + +
    + +
    +   + + + + + + +
    test_favorites() + source code + +
    + +
    +   + + + + + + +
    test_large_list() + source code + +
    + +
    +   + + + + + + +
    test_events() + source code + +
    + +
    +   + + + + + + +
    test_me_having_stress() + source code + +
    + +
    +   + + + + + + +
    test_non_global_api() + source code + +
    + +
    +   + + + + + + +
    test_playlists() + source code + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Variables[hide private]
    +
    +   + + logger = logging.getLogger(__name__) +
    +   + + _logger = logging.getLogger("scapi") +
    +   + + RUN_INTERACTIVE_TESTS = False +
    +   + + USE_OAUTH = True +
    +   + + TOKEN = 'FjNE9aRTg8kpxuOjzwsX8Q' +
    +   + + SECRET = 'NP5PGoyKcQv64E0aZgV4CRNzHfPwR4QghrWoqEgEE' +
    +   + + CONSUMER = 'EEi2URUfM97pAAxHTogDpQ' +
    +   + + CONSUMER_SECRET = 'NFYd8T3i4jVKGZ9TMy9LHaBQB3Sh8V5sxBiMeMZBow' +
    +   + + API_HOST = 'api.soundcloud.dev:3000' +
    +   + + USER = '' +
    +   + + PASSWORD = '' +
    +   + + CONFIG_NAME = 'soundcloud.cfg' +
    +   + + CONNECTOR = None +
    +   + + ROOT = None +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + diff --git a/python_apps/soundcloud-api/docs/api/scapi.tests.test_connect-pysrc.html b/python_apps/soundcloud-api/docs/api/scapi.tests.test_connect-pysrc.html new file mode 100644 index 000000000..3c0a41c84 --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/scapi.tests.test_connect-pysrc.html @@ -0,0 +1,627 @@ + + + + + scapi.tests.test_connect + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package scapi :: + Package tests :: + Module test_connect + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    +

    Source Code for Module scapi.tests.test_connect

    +
    +  1  from __future__ import with_statement 
    +  2  import os 
    +  3  import tempfile 
    +  4  import itertools 
    +  5  from ConfigParser import SafeConfigParser 
    +  6  import pkg_resources 
    +  7  import scapi 
    +  8  import scapi.authentication 
    +  9  import logging 
    + 10  import webbrowser 
    + 11   
    + 12  logger = logging.getLogger(__name__) 
    + 13  logger.setLevel(logging.DEBUG) 
    + 14  _logger = logging.getLogger("scapi") 
    + 15  #_logger.setLevel(logging.DEBUG) 
    + 16   
    + 17  RUN_INTERACTIVE_TESTS = False 
    + 18  USE_OAUTH = True 
    + 19   
    + 20  TOKEN  = "FjNE9aRTg8kpxuOjzwsX8Q" 
    + 21  SECRET = "NP5PGoyKcQv64E0aZgV4CRNzHfPwR4QghrWoqEgEE" 
    + 22  CONSUMER = "EEi2URUfM97pAAxHTogDpQ" 
    + 23  CONSUMER_SECRET = "NFYd8T3i4jVKGZ9TMy9LHaBQB3Sh8V5sxBiMeMZBow" 
    + 24  API_HOST = "api.soundcloud.dev:3000" 
    + 25  USER = "" 
    + 26  PASSWORD = "" 
    + 27   
    + 28  CONFIG_NAME = "soundcloud.cfg" 
    + 29   
    + 30  CONNECTOR = None 
    + 31  ROOT = None 
    +
    32 -def setup(): +
    33 global CONNECTOR, ROOT + 34 # load_config() + 35 #scapi.ApiConnector(host='192.168.2.101:3000', user='tiga', password='test') + 36 #scapi.ApiConnector(host='sandbox-api.soundcloud.com:3030', user='tiga', password='test') + 37 scapi.USE_PROXY = False + 38 scapi.PROXY = 'http://127.0.0.1:10000/' + 39 + 40 if USE_OAUTH: + 41 authenticator = scapi.authentication.OAuthAuthenticator(CONSUMER, + 42 CONSUMER_SECRET, + 43 TOKEN, + 44 SECRET) + 45 else: + 46 authenticator = scapi.authentication.BasicAuthenticator(USER, PASSWORD, CONSUMER, CONSUMER_SECRET) + 47 + 48 logger.debug("API_HOST: %s", API_HOST) + 49 CONNECTOR = scapi.ApiConnector(host=API_HOST, + 50 authenticator=authenticator) + 51 ROOT = scapi.Scope(CONNECTOR) +
    52 +
    53 -def load_config(config_name=None): +
    54 global TOKEN, SECRET, CONSUMER_SECRET, CONSUMER, API_HOST, USER, PASSWORD + 55 if config_name is None: + 56 config_name = CONFIG_NAME + 57 parser = SafeConfigParser() + 58 current = os.getcwd() + 59 while current: + 60 name = os.path.join(current, config_name) + 61 if os.path.exists(name): + 62 parser.read([name]) + 63 TOKEN = parser.get('global', 'accesstoken') + 64 SECRET = parser.get('global', 'accesstoken_secret') + 65 CONSUMER = parser.get('global', 'consumer') + 66 CONSUMER_SECRET = parser.get('global', 'consumer_secret') + 67 API_HOST = parser.get('global', 'host') + 68 USER = parser.get('global', 'user') + 69 PASSWORD = parser.get('global', 'password') + 70 logger.debug("token: %s", TOKEN) + 71 logger.debug("secret: %s", SECRET) + 72 logger.debug("consumer: %s", CONSUMER) + 73 logger.debug("consumer_secret: %s", CONSUMER_SECRET) + 74 logger.debug("user: %s", USER) + 75 logger.debug("password: %s", PASSWORD) + 76 logger.debug("host: %s", API_HOST) + 77 break + 78 new_current = os.path.dirname(current) + 79 if new_current == current: + 80 break + 81 current = new_current +
    82 + 83 +
    84 -def test_load_config(): +
    85 base = tempfile.mkdtemp() + 86 oldcwd = os.getcwd() + 87 cdir = os.path.join(base, "foo") + 88 os.mkdir(cdir) + 89 os.chdir(cdir) + 90 test_config = """ + 91 [global] + 92 host=host + 93 consumer=consumer + 94 consumer_secret=consumer_secret + 95 accesstoken=accesstoken + 96 accesstoken_secret=accesstoken_secret + 97 user=user + 98 password=password + 99 """ +100 with open(os.path.join(base, CONFIG_NAME), "w") as cf: +101 cf.write(test_config) +102 load_config() +103 assert TOKEN == "accesstoken" and SECRET == "accesstoken_secret" and API_HOST == 'host' +104 assert CONSUMER == "consumer" and CONSUMER_SECRET == "consumer_secret" +105 assert USER == "user" and PASSWORD == "password" +106 os.chdir(oldcwd) +107 load_config() +
    108 +109 +
    110 -def test_connect(): +
    111 sca = ROOT +112 quite_a_few_users = list(itertools.islice(sca.users(), 0, 127)) +113 +114 logger.debug(quite_a_few_users) +115 assert isinstance(quite_a_few_users, list) and isinstance(quite_a_few_users[0], scapi.User) +116 user = sca.me() +117 logger.debug(user) +118 assert isinstance(user, scapi.User) +119 contacts = list(user.contacts()) +120 assert isinstance(contacts, list) +121 assert isinstance(contacts[0], scapi.User) +122 logger.debug(contacts) +123 tracks = list(user.tracks()) +124 assert isinstance(tracks, list) +125 assert isinstance(tracks[0], scapi.Track) +126 logger.debug(tracks) +
    127 +128 +
    130 """ +131 This test is commented out because it needs user-interaction. +132 """ +133 if not RUN_INTERACTIVE_TESTS: +134 return +135 oauth_authenticator = scapi.authentication.OAuthAuthenticator(CONSUMER, +136 CONSUMER_SECRET, +137 None, +138 None) +139 +140 sca = scapi.ApiConnector(host=API_HOST, authenticator=oauth_authenticator) +141 token, secret = sca.fetch_request_token() +142 authorization_url = sca.get_request_token_authorization_url(token) +143 webbrowser.open(authorization_url) +144 raw_input("please press return") +145 oauth_authenticator = scapi.authentication.OAuthAuthenticator(CONSUMER, +146 CONSUMER_SECRET, +147 token, +148 secret) +149 +150 sca = scapi.ApiConnector(API_HOST, authenticator=oauth_authenticator) +151 token, secret = sca.fetch_access_token() +152 logger.info("Access token: '%s'", token) +153 logger.info("Access token secret: '%s'", secret) +154 oauth_authenticator = scapi.authentication.OAuthAuthenticator(CONSUMER, +155 CONSUMER_SECRET, +156 token, +157 secret) +158 +159 sca = scapi.ApiConnector(API_HOST, authenticator=oauth_authenticator) +160 test_track_creation() +
    161 +
    163 sca = ROOT +164 track = sca.Track.new(title='bar') +165 assert isinstance(track, scapi.Track) +
    166 +
    167 -def test_track_update(): +
    168 sca = ROOT +169 track = sca.Track.new(title='bar') +170 assert isinstance(track, scapi.Track) +171 track.title='baz' +172 track = sca.Track.get(track.id) +173 assert track.title == "baz" +
    174 +
    176 sca = ROOT +177 user = sca.me() +178 track = user.tracks.new(title="bar") +179 assert isinstance(track, scapi.Track) +
    180 +
    181 -def test_upload(): +
    182 assert pkg_resources.resource_exists("scapi.tests.test_connect", "knaster.mp3") +183 data = pkg_resources.resource_stream("scapi.tests.test_connect", "knaster.mp3") +184 sca = ROOT +185 user = sca.me() +186 logger.debug(user) +187 asset = sca.assets.new(filedata=data) +188 assert isinstance(asset, scapi.Asset) +189 logger.debug(asset) +190 tracks = list(user.tracks()) +191 track = tracks[0] +192 track.assets.append(asset) +
    193 +
    194 -def test_contact_list(): +
    195 sca = ROOT +196 user = sca.me() +197 contacts = list(user.contacts()) +198 assert isinstance(contacts, list) +199 assert isinstance(contacts[0], scapi.User) +
    200 +
    201 -def test_permissions(): +
    202 sca = ROOT +203 user = sca.me() +204 tracks = itertools.islice(user.tracks(), 1) +205 for track in tracks: +206 permissions = list(track.permissions()) +207 logger.debug(permissions) +208 assert isinstance(permissions, list) +209 if permissions: +210 assert isinstance(permissions[0], scapi.User) +
    211 +
    213 sca = ROOT +214 me = sca.me() +215 track = sca.Track.new(title='bar', sharing="private") +216 assert track.sharing == "private" +217 users = itertools.islice(sca.users(), 10) +218 users_to_set = [user for user in users if user != me] +219 assert users_to_set, "Didn't find any suitable users" +220 track.permissions = users_to_set +221 assert set(track.permissions()) == set(users_to_set) +
    222 +
    224 sca = ROOT +225 user = sca.me() +226 track = sca.Track.new(title='bar', sharing="private") +227 comment = sca.Comment.create(body="This is the body of my comment", timestamp=10) +228 track.comments = comment +229 assert track.comments().next().body == comment.body +
    230 +231 +
    233 sca = ROOT +234 track = sca.Track.new(title='bar', sharing="private") +235 cbody = "This is the body of my comment" +236 track.comments.new(body=cbody, timestamp=10) +237 assert list(track.comments())[0].body == cbody +
    238 +
    240 sca = ROOT +241 me = sca.me() +242 for user in sca.users(): +243 if user != me: +244 user_to_set = user +245 break +246 +247 contacts = list(me.contacts()) +248 if user_to_set in contacts: +249 me.contacts.remove(user_to_set) +250 +251 me.contacts.append(user_to_set) +252 +253 contacts = list(me.contacts() ) +254 assert user_to_set.id in [c.id for c in contacts] +255 +256 me.contacts.remove(user_to_set) +257 +258 contacts = list(me.contacts() ) +259 assert user_to_set not in contacts +
    260 +261 +
    262 -def test_favorites(): +
    263 sca = ROOT +264 me = sca.me() +265 +266 favorites = list(me.favorites()) +267 assert favorites == [] or isinstance(favorites[0], scapi.Track) +268 +269 track = None +270 for user in sca.users(): +271 if user == me: +272 continue +273 for track in user.tracks(): +274 break +275 if track is not None: +276 break +277 +278 me.favorites.append(track) +279 +280 favorites = list(me.favorites()) +281 assert track in favorites +282 +283 me.favorites.remove(track) +284 +285 favorites = list(me.favorites()) +286 assert track not in favorites +
    287 +
    288 -def test_large_list(): +
    289 sca = ROOT +290 tracks = list(sca.tracks()) +291 if len(tracks) < scapi.ApiConnector.LIST_LIMIT: +292 for i in xrange(scapi.ApiConnector.LIST_LIMIT): +293 scapi.Track.new(title='test_track_%i' % i) +294 all_tracks = sca.tracks() +295 assert not isinstance(all_tracks, list) +296 all_tracks = list(all_tracks) +297 assert len(all_tracks) > scapi.ApiConnector.LIST_LIMIT +
    298 +299 +
    300 -def test_events(): +
    301 events = list(ROOT.events()) +302 assert isinstance(events, list) +303 assert isinstance(events[0], scapi.Event) +
    304 +
    306 sca = ROOT +307 for _ in xrange(20): +308 setup() +309 sca.me() +
    310 +
    312 root = scapi.Scope(CONNECTOR) +313 me = root.me() +314 assert isinstance(me, scapi.User) +315 +316 # now get something *from* that user +317 favorites = list(me.favorites()) +318 assert favorites +
    319 +
    320 -def test_playlists(): +
    321 sca = ROOT +322 playlists = list(itertools.islice(sca.playlists(), 0, 127)) +323 found = False +324 for playlist in playlists: +325 tracks = playlist.tracks +326 if not isinstance(tracks, list): +327 tracks = [tracks] +328 for trackdata in tracks: +329 print trackdata +330 user = trackdata.user +331 print user +332 print user.tracks() +333 print playlist.user +334 break +
    335 +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + diff --git a/python_apps/soundcloud-api/docs/api/scapi.tests.test_oauth-module.html b/python_apps/soundcloud-api/docs/api/scapi.tests.test_oauth-module.html new file mode 100644 index 000000000..6bf493f9d --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/scapi.tests.test_oauth-module.html @@ -0,0 +1,225 @@ + + + + + scapi.tests.test_oauth + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package scapi :: + Package tests :: + Module test_oauth + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Module test_oauth

    source code

    + + + + + + + + + + + + +
    + + + + + +
    Functions[hide private]
    +
    +   + + + + + + +
    test_base64_connect() + source code + +
    + +
    +   + + + + + + +
    test_oauth_connect() + source code + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Variables[hide private]
    +
    +   + + logger = logging.getLogger(__name__) +
    +   + + _logger = logging.getLogger("scapi") +
    +   + + TOKEN = 'QcciYu1FSwDSGKAG2mNw' +
    +   + + SECRET = 'gJ2ok6ULUsYQB3rsBmpHCRHoFCAPOgK8ZjoIyxzris' +
    +   + + CONSUMER = 'Cy2eLPrIMp4vOxjz9icdQ' +
    +   + + CONSUMER_SECRET = 'KsBa272x6M2to00Vo5FdvZXt9kakcX7CDIPJoGwTro' +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + diff --git a/python_apps/soundcloud-api/docs/api/scapi.tests.test_oauth-pysrc.html b/python_apps/soundcloud-api/docs/api/scapi.tests.test_oauth-pysrc.html new file mode 100644 index 000000000..6ecf3c628 --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/scapi.tests.test_oauth-pysrc.html @@ -0,0 +1,182 @@ + + + + + scapi.tests.test_oauth + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package scapi :: + Package tests :: + Module test_oauth + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    +

    Source Code for Module scapi.tests.test_oauth

    +
    + 1  import pkg_resources 
    + 2  import scapi 
    + 3  import scapi.authentication 
    + 4  import urllib 
    + 5  import logging 
    + 6   
    + 7  logger = logging.getLogger(__name__) 
    + 8  logger.setLevel(logging.DEBUG) 
    + 9  _logger = logging.getLogger("scapi") 
    +10  _logger.setLevel(logging.DEBUG) 
    +11   
    +12  TOKEN  = "QcciYu1FSwDSGKAG2mNw" 
    +13  SECRET = "gJ2ok6ULUsYQB3rsBmpHCRHoFCAPOgK8ZjoIyxzris" 
    +14  CONSUMER = "Cy2eLPrIMp4vOxjz9icdQ" 
    +15  CONSUMER_SECRET = "KsBa272x6M2to00Vo5FdvZXt9kakcX7CDIPJoGwTro" 
    +16   
    +
    18 scapi.USE_PROXY = True +19 scapi.PROXY = 'http://127.0.0.1:10000/' +20 scapi.SoundCloudAPI(host='192.168.2.31:3000', authenticator=scapi.authentication.BasicAuthenticator('tiga', 'test')) +21 sca = scapi.Scope() +22 assert isinstance(sca.me(), scapi.User) +
    23 +24 +
    26 scapi.USE_PROXY = True +27 scapi.PROXY = 'http://127.0.0.1:10000/' +28 scapi.SoundCloudAPI(host='192.168.2.31:3000', +29 authenticator=scapi.authentication.OAuthAuthenticator(CONSUMER, +30 CONSUMER_SECRET, +31 TOKEN, SECRET)) +32 +33 sca = scapi.Scope() +34 assert isinstance(sca.me(), scapi.User) +
    35 +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + diff --git a/python_apps/soundcloud-api/docs/api/scapi.util-module.html b/python_apps/soundcloud-api/docs/api/scapi.util-module.html new file mode 100644 index 000000000..2603fb0b1 --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/scapi.util-module.html @@ -0,0 +1,173 @@ + + + + + scapi.util + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package scapi :: + Module util + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Module util

    source code

    + + + + + + + + + +
    + + + + + +
    Classes[hide private]
    +
    +   + + MultiDict +
    + + + + + + + + + +
    + + + + + +
    Functions[hide private]
    +
    +   + + + + + + +
    escape(s) + source code + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + diff --git a/python_apps/soundcloud-api/docs/api/scapi.util-pysrc.html b/python_apps/soundcloud-api/docs/api/scapi.util-pysrc.html new file mode 100644 index 000000000..1a018ed72 --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/scapi.util-pysrc.html @@ -0,0 +1,171 @@ + + + + + scapi.util + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package scapi :: + Module util + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    +

    Source Code for Module scapi.util

    +
    + 1  ##    SouncCloudAPI implements a Python wrapper around the SoundCloud RESTful 
    + 2  ##    API 
    + 3  ## 
    + 4  ##    Copyright (C) 2008  Diez B. Roggisch 
    + 5  ##    Contact mailto:deets@soundcloud.com 
    + 6  ## 
    + 7  ##    This library is free software; you can redistribute it and/or 
    + 8  ##    modify it under the terms of the GNU Lesser General Public 
    + 9  ##    License as published by the Free Software Foundation; either 
    +10  ##    version 2.1 of the License, or (at your option) any later version. 
    +11  ## 
    +12  ##    This library is distributed in the hope that it will be useful, 
    +13  ##    but WITHOUT ANY WARRANTY; without even the implied warranty of 
    +14  ##    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
    +15  ##    Lesser General Public License for more details. 
    +16  ## 
    +17  ##    You should have received a copy of the GNU Lesser General Public 
    +18  ##    License along with this library; if not, write to the Free Software 
    +19  ##    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
    +20   
    +21  import urllib 
    +22   
    +
    23 -def escape(s): +
    24 # escape '/' too +25 return urllib.quote(s, safe='') +
    26 +27 +28 +29 +30 +31 +
    32 -class MultiDict(dict): +
    33 +34 +
    35 - def add(self, key, new_value): +
    36 if key in self: +37 value = self[key] +38 if not isinstance(value, list): +39 value = [value] +40 self[key] = value +41 value.append(new_value) +42 else: +43 self[key] = new_value +
    44 +45 +
    46 - def iteritemslist(self): +
    47 for key, value in self.iteritems(): +48 if not isinstance(value, list): +49 value = [value] +50 yield key, value +
    51 +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + diff --git a/python_apps/soundcloud-api/docs/api/scapi.util.MultiDict-class.html b/python_apps/soundcloud-api/docs/api/scapi.util.MultiDict-class.html new file mode 100644 index 000000000..fe7b460e9 --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/scapi.util.MultiDict-class.html @@ -0,0 +1,247 @@ + + + + + scapi.util.MultiDict + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package scapi :: + Module util :: + Class MultiDict + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class MultiDict

    source code

    +
    +object --+    
    +         |    
    +      dict --+
    +             |
    +            MultiDict
    +
    + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    add(self, + key, + new_value) + source code + +
    + +
    +   + + + + + + +
    iteritemslist(self) + source code + +
    + +
    +

    Inherited from dict: + __cmp__, + __contains__, + __delitem__, + __eq__, + __ge__, + __getattribute__, + __getitem__, + __gt__, + __hash__, + __init__, + __iter__, + __le__, + __len__, + __lt__, + __ne__, + __new__, + __repr__, + __setitem__, + clear, + copy, + fromkeys, + get, + has_key, + items, + iteritems, + iterkeys, + itervalues, + keys, + pop, + popitem, + setdefault, + update, + values +

    +

    Inherited from object: + __delattr__, + __reduce__, + __reduce_ex__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + diff --git a/python_apps/soundcloud-api/docs/api/toc-everything.html b/python_apps/soundcloud-api/docs/api/toc-everything.html new file mode 100644 index 000000000..3ced23f23 --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/toc-everything.html @@ -0,0 +1,151 @@ + + + + + Everything + + + + + +

    Everything

    +
    +

    All Classes

    + exceptions.AssertionError
    + scapi.Comment
    scapi.Event
    scapi.Group
    + + scapi.Playlist
    + + + scapi.Track
    + scapi.User
    + + + scapi.json.JsonReader
    scapi.json.JsonWriter
    scapi.json.ReadException
    scapi.json.WriteException
    + scapi.tests.scapi_tests.SCAPITests
    +

    All Functions

    + scapi.json.read
    scapi.json.write
    + scapi.tests.test_connect.load_config
    scapi.tests.test_connect.setup
    scapi.tests.test_connect.test_access_token_acquisition
    scapi.tests.test_connect.test_connect
    scapi.tests.test_connect.test_contact_add_and_removal
    scapi.tests.test_connect.test_contact_list
    scapi.tests.test_connect.test_events
    scapi.tests.test_connect.test_favorites
    scapi.tests.test_connect.test_large_list
    scapi.tests.test_connect.test_load_config
    scapi.tests.test_connect.test_me_having_stress
    scapi.tests.test_connect.test_non_global_api
    scapi.tests.test_connect.test_permissions
    scapi.tests.test_connect.test_playlists
    scapi.tests.test_connect.test_scoped_track_creation
    scapi.tests.test_connect.test_setting_comments
    scapi.tests.test_connect.test_setting_comments_the_way_shawn_says_its_correct
    scapi.tests.test_connect.test_setting_permissions
    scapi.tests.test_connect.test_track_creation
    scapi.tests.test_connect.test_track_update
    scapi.tests.test_connect.test_upload
    scapi.tests.test_oauth.test_base64_connect
    scapi.tests.test_oauth.test_oauth_connect
    +

    All Variables

    + scapi.ACCESS_TOKEN_URL
    scapi.AUTHORIZATION_URL
    scapi.PROXY
    scapi.REQUEST_TOKEN_URL
    scapi.USE_PROXY
    + + + scapi.tests.scapi_tests.api_logger
    scapi.tests.scapi_tests.logger
    scapi.tests.test_connect.API_HOST
    scapi.tests.test_connect.CONFIG_NAME
    scapi.tests.test_connect.CONNECTOR
    scapi.tests.test_connect.CONSUMER
    scapi.tests.test_connect.CONSUMER_SECRET
    scapi.tests.test_connect.PASSWORD
    scapi.tests.test_connect.ROOT
    scapi.tests.test_connect.RUN_INTERACTIVE_TESTS
    scapi.tests.test_connect.SECRET
    scapi.tests.test_connect.TOKEN
    scapi.tests.test_connect.USER
    scapi.tests.test_connect.USE_OAUTH
    + scapi.tests.test_connect.logger
    scapi.tests.test_oauth.CONSUMER
    scapi.tests.test_oauth.CONSUMER_SECRET
    scapi.tests.test_oauth.SECRET
    scapi.tests.test_oauth.TOKEN
    + scapi.tests.test_oauth.logger

    +[hide private] + + + + diff --git a/python_apps/soundcloud-api/docs/api/toc-scapi-module.html b/python_apps/soundcloud-api/docs/api/toc-scapi-module.html new file mode 100644 index 000000000..b82804b3f --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/toc-scapi-module.html @@ -0,0 +1,70 @@ + + + + + scapi + + + + + +

    Module scapi

    +
    +

    Classes

    + + Comment
    Event
    Group
    + + Playlist
    + +
    + Scope
    + Track
    + User

    Functions

    + +

    Variables

    + ACCESS_TOKEN_URL
    AUTHORIZATION_URL
    PROXY
    REQUEST_TOKEN_URL
    USE_PROXY
    + logger
    +
    +[hide private] + + + + diff --git a/python_apps/soundcloud-api/docs/api/toc-scapi.authentication-module.html b/python_apps/soundcloud-api/docs/api/toc-scapi.authentication-module.html new file mode 100644 index 000000000..e86f597b8 --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/toc-scapi.authentication-module.html @@ -0,0 +1,46 @@ + + + + + authentication + + + + + +

    Module authentication

    +
    +

    Classes

    + + + +

    Variables

    + +
    + logger
    +
    +[hide private] + + + + diff --git a/python_apps/soundcloud-api/docs/api/toc-scapi.config-module.html b/python_apps/soundcloud-api/docs/api/toc-scapi.config-module.html new file mode 100644 index 000000000..bc4635ec9 --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/toc-scapi.config-module.html @@ -0,0 +1,29 @@ + + + + + config + + + + + +

    Module config

    +
    +
    +[hide private] + + + + diff --git a/python_apps/soundcloud-api/docs/api/toc-scapi.json-module.html b/python_apps/soundcloud-api/docs/api/toc-scapi.json-module.html new file mode 100644 index 000000000..49c7fa3f6 --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/toc-scapi.json-module.html @@ -0,0 +1,40 @@ + + + + + json + + + + + +

    Module json

    +
    +

    Classes

    + JsonReader
    JsonWriter
    ReadException
    WriteException
    +

    Functions

    + read
    write

    +[hide private] + + + + diff --git a/python_apps/soundcloud-api/docs/api/toc-scapi.multidict-module.html b/python_apps/soundcloud-api/docs/api/toc-scapi.multidict-module.html new file mode 100644 index 000000000..a4494cd7b --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/toc-scapi.multidict-module.html @@ -0,0 +1,29 @@ + + + + + multidict + + + + + +

    Module multidict

    +
    +
    +[hide private] + + + + diff --git a/python_apps/soundcloud-api/docs/api/toc-scapi.tests-module.html b/python_apps/soundcloud-api/docs/api/toc-scapi.tests-module.html new file mode 100644 index 000000000..4c66376d1 --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/toc-scapi.tests-module.html @@ -0,0 +1,29 @@ + + + + + tests + + + + + +

    Module tests

    +
    +
    +[hide private] + + + + diff --git a/python_apps/soundcloud-api/docs/api/toc-scapi.tests.scapi_tests-module.html b/python_apps/soundcloud-api/docs/api/toc-scapi.tests.scapi_tests-module.html new file mode 100644 index 000000000..ecc870d8c --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/toc-scapi.tests.scapi_tests-module.html @@ -0,0 +1,34 @@ + + + + + scapi_tests + + + + + +

    Module scapi_tests

    +
    +

    Classes

    + SCAPITests

    Variables

    + api_logger
    logger

    +[hide private] + + + + diff --git a/python_apps/soundcloud-api/docs/api/toc-scapi.tests.test_connect-module.html b/python_apps/soundcloud-api/docs/api/toc-scapi.tests.test_connect-module.html new file mode 100644 index 000000000..791b5ba85 --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/toc-scapi.tests.test_connect-module.html @@ -0,0 +1,68 @@ + + + + + test_connect + + + + + +

    Module test_connect

    +
    +

    Functions

    + load_config
    setup
    test_access_token_acquisition
    test_connect
    test_contact_add_and_removal
    test_contact_list
    test_events
    test_favorites
    test_large_list
    test_load_config
    test_me_having_stress
    test_non_global_api
    test_permissions
    test_playlists
    test_scoped_track_creation
    test_setting_comments
    test_setting_comments_the_way_shawn_says_its_correct
    test_setting_permissions
    test_track_creation
    test_track_update
    test_upload

    Variables

    + API_HOST
    CONFIG_NAME
    CONNECTOR
    CONSUMER
    CONSUMER_SECRET
    PASSWORD
    ROOT
    RUN_INTERACTIVE_TESTS
    SECRET
    TOKEN
    USER
    USE_OAUTH
    + logger

    +[hide private] + + + + diff --git a/python_apps/soundcloud-api/docs/api/toc-scapi.tests.test_oauth-module.html b/python_apps/soundcloud-api/docs/api/toc-scapi.tests.test_oauth-module.html new file mode 100644 index 000000000..0d504cee4 --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/toc-scapi.tests.test_oauth-module.html @@ -0,0 +1,41 @@ + + + + + test_oauth + + + + + +

    Module test_oauth

    +
    +

    Functions

    + test_base64_connect
    test_oauth_connect

    Variables

    + CONSUMER
    CONSUMER_SECRET
    SECRET
    TOKEN
    + logger

    +[hide private] + + + + diff --git a/python_apps/soundcloud-api/docs/api/toc-scapi.util-module.html b/python_apps/soundcloud-api/docs/api/toc-scapi.util-module.html new file mode 100644 index 000000000..c94b46b34 --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/toc-scapi.util-module.html @@ -0,0 +1,37 @@ + + + + + util + + + + + +

    Module util

    +
    +

    Classes

    + +

    Functions

    +
    + escape
    +
    +[hide private] + + + + diff --git a/python_apps/soundcloud-api/docs/api/toc.html b/python_apps/soundcloud-api/docs/api/toc.html new file mode 100644 index 000000000..2e38a4044 --- /dev/null +++ b/python_apps/soundcloud-api/docs/api/toc.html @@ -0,0 +1,46 @@ + + + + + Table of Contents + + + + + +

    Table of Contents

    +
    + Everything +
    +

    Modules

    + scapi
    + scapi.config
    scapi.json
    scapi.multidict
    scapi.tests
    scapi.tests.scapi_tests
    scapi.tests.test_connect
    scapi.tests.test_oauth
    +
    + [hide private] + + + + diff --git a/python_apps/soundcloud-api/oauth/__init__.py b/python_apps/soundcloud-api/oauth/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python_apps/soundcloud-api/oauth/example/client.py b/python_apps/soundcloud-api/oauth/example/client.py new file mode 100644 index 000000000..3f995c1a5 --- /dev/null +++ b/python_apps/soundcloud-api/oauth/example/client.py @@ -0,0 +1,157 @@ +''' +Example consumer. +''' +import httplib +import time +import oauth.oauth as oauth +import webbrowser +from scapi import util + +SERVER = 'sandbox-soundcloud.com' # Change to soundcloud.com to reach the live site +PORT = 80 + +REQUEST_TOKEN_URL = 'http://api.' + SERVER + '/oauth/request_token' +ACCESS_TOKEN_URL = 'http://api.' + SERVER + '/oauth/access_token' +AUTHORIZATION_URL = 'http://' + SERVER + '/oauth/authorize' + +CALLBACK_URL = '' +RESOURCE_URL = "http://api." + SERVER + "/me" + +# key and secret granted by the service provider for this consumer application - same as the MockOAuthDataStore +CONSUMER_KEY = 'JysXkO8ErA4EluFnF5nWg' +CONSUMER_SECRET = 'fauVjm61niGckeufkmMvgUo77oWzRHdMmeylJblHk' + +# example client using httplib with headers +class SimpleOAuthClient(oauth.OAuthClient): + + def __init__(self, server, port=httplib.HTTP_PORT, request_token_url='', access_token_url='', authorization_url=''): + self.server = server + self.port = port + self.request_token_url = request_token_url + self.access_token_url = access_token_url + self.authorization_url = authorization_url + self.connection = httplib.HTTPConnection("%s:%d" % (self.server, self.port)) + + def fetch_request_token(self, oauth_request): + # via headers + # -> OAuthToken + print oauth_request.to_url() + #self.connection.request(oauth_request.http_method, self.request_token_url, headers=oauth_request.to_header()) + self.connection.request(oauth_request.http_method, oauth_request.to_url()) + response = self.connection.getresponse() + print "response status", response.status + return oauth.OAuthToken.from_string(response.read()) + + def fetch_access_token(self, oauth_request): + # via headers + # -> OAuthToken + + # This should proably be elsewhere but stays here for now + oauth_request.set_parameter("oauth_signature", util.escape(oauth_request.get_parameter("oauth_signature"))) + self.connection.request(oauth_request.http_method, self.access_token_url, headers=oauth_request.to_header()) + response = self.connection.getresponse() + resp = response.read() + print "*" * 90 + print "response:", resp + print "*" * 90 + + return oauth.OAuthToken.from_string(resp) + + def authorize_token(self, oauth_request): + webbrowser.open(oauth_request.to_url()) + raw_input("press return when authorizing is finished") + + return + + # via url + # -> typically just some okay response + self.connection.request(oauth_request.http_method, oauth_request.to_url()) + response = self.connection.getresponse() + return response.read() + + def access_resource(self, oauth_request): + print "resource url:", oauth_request.to_url() + webbrowser.open(oauth_request.to_url()) + + return + + # via post body + # -> some protected resources + self.connection.request('GET', oauth_request.to_url()) + response = self.connection.getresponse() + return response.read() + +def run_example(): + + # setup + print '** OAuth Python Library Example **' + client = SimpleOAuthClient(SERVER, PORT, REQUEST_TOKEN_URL, ACCESS_TOKEN_URL, AUTHORIZATION_URL) + consumer = oauth.OAuthConsumer(CONSUMER_KEY, CONSUMER_SECRET) + signature_method_plaintext = oauth.OAuthSignatureMethod_PLAINTEXT() + signature_method_hmac_sha1 = oauth.OAuthSignatureMethod_HMAC_SHA1() + pause() + # get request token + print '* Obtain a request token ...' + pause() + oauth_request = oauth.OAuthRequest.from_consumer_and_token(consumer, http_url=client.request_token_url) + #oauth_request.sign_request(signature_method_plaintext, consumer, None) + oauth_request.sign_request(signature_method_hmac_sha1, consumer, None) + + print 'REQUEST (via headers)' + print 'parameters: %s' % str(oauth_request.parameters) + pause() + #import pdb; pdb.set_trace() + + token = client.fetch_request_token(oauth_request) + print 'GOT' + print 'key: %s' % str(token.key) + print 'secret: %s' % str(token.secret) + pause() + + print '* Authorize the request token ...' + pause() + oauth_request = oauth.OAuthRequest.from_token_and_callback(token=token, callback=CALLBACK_URL, http_url=client.authorization_url) + print 'REQUEST (via url query string)' + print 'parameters: %s' % str(oauth_request.parameters) + pause() + # this will actually occur only on some callback + response = client.authorize_token(oauth_request) + print 'GOT' + print response + pause() + + # get access token + print '* Obtain an access token ...' + pause() + oauth_request = oauth.OAuthRequest.from_consumer_and_token(consumer, token=token, http_url=client.access_token_url) + oauth_request.sign_request(signature_method_hmac_sha1, consumer, token) + print 'REQUEST (via headers)' + print 'parameters: %s' % str(oauth_request.parameters) + pause() + token = client.fetch_access_token(oauth_request) + print 'GOT' + print 'key: %s' % str(token.key) + print 'secret: %s' % str(token.secret) + pause() + + # access some protected resources + print '* Access protected resources ...' + pause() + parameters = {} + oauth_request = oauth.OAuthRequest.from_consumer_and_token(consumer, token=token, http_method='GET', http_url=RESOURCE_URL, parameters=parameters) + oauth_request.sign_request(signature_method_hmac_sha1, consumer, token) + print 'REQUEST (via get body)' + print 'parameters: %s' % str(oauth_request.parameters) + pause() + params = client.access_resource(oauth_request) + print 'GOT' + print 'non-oauth parameters: %s' % params + pause() + +def pause(): + print '' + time.sleep(1) + +if __name__ == '__main__': + run_example() + print 'Done.' diff --git a/python_apps/soundcloud-api/oauth/example/server.py b/python_apps/soundcloud-api/oauth/example/server.py new file mode 100644 index 000000000..91fb71538 --- /dev/null +++ b/python_apps/soundcloud-api/oauth/example/server.py @@ -0,0 +1,167 @@ +from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer +import urllib + +import oauth.oauth as oauth + +REQUEST_TOKEN_URL = 'https://photos.example.net/request_token' +ACCESS_TOKEN_URL = 'https://photos.example.net/access_token' +AUTHORIZATION_URL = 'https://photos.example.net/authorize' +RESOURCE_URL = 'http://photos.example.net/photos' +REALM = 'http://photos.example.net/' + +# example store for one of each thing +class MockOAuthDataStore(oauth.OAuthDataStore): + + def __init__(self): + self.consumer = oauth.OAuthConsumer('key', 'secret') + self.request_token = oauth.OAuthToken('requestkey', 'requestsecret') + self.access_token = oauth.OAuthToken('accesskey', 'accesssecret') + self.nonce = 'nonce' + + def lookup_consumer(self, key): + if key == self.consumer.key: + return self.consumer + return None + + def lookup_token(self, token_type, token): + token_attrib = getattr(self, '%s_token' % token_type) + if token == token_attrib.key: + return token_attrib + return None + + def lookup_nonce(self, oauth_consumer, oauth_token, nonce): + if oauth_token and oauth_consumer.key == self.consumer.key and (oauth_token.key == self.request_token.key or token.key == self.access_token.key) and nonce == self.nonce: + return self.nonce + else: + raise oauth.OAuthError('Nonce not found: %s' % str(nonce)) + return None + + def fetch_request_token(self, oauth_consumer): + if oauth_consumer.key == self.consumer.key: + return self.request_token + return None + + def fetch_access_token(self, oauth_consumer, oauth_token): + if oauth_consumer.key == self.consumer.key and oauth_token.key == self.request_token.key: + # want to check here if token is authorized + # for mock store, we assume it is + return self.access_token + return None + + def authorize_request_token(self, oauth_token): + if oauth_token.key == self.request_token.key: + # authorize the request token in the store + # for mock store, do nothing + return self.request_token + return None + +class RequestHandler(BaseHTTPRequestHandler): + + def __init__(self, *args, **kwargs): + self.oauth_server = oauth.OAuthServer(MockOAuthDataStore()) + self.oauth_server.add_signature_method(oauth.OAuthSignatureMethod_PLAINTEXT()) + self.oauth_server.add_signature_method(oauth.OAuthSignatureMethod_HMAC_SHA1()) + BaseHTTPRequestHandler.__init__(self, *args, **kwargs) + + # example way to send an oauth error + def send_oauth_error(self, err=None): + # send a 401 error + self.send_error(401, str(err.message)) + # return the authenticate header + header = oauth.build_authenticate_header(realm=REALM) + for k, v in header.iteritems(): + self.send_header(k, v) + + def do_GET(self): + + # debug info + #print self.command, self.path, self.headers + + # get the post data (if any) + postdata = None + if self.command == 'POST': + try: + length = int(self.headers.getheader('content-length')) + postdata = self.rfile.read(length) + except: + pass + + # construct the oauth request from the request parameters + oauth_request = oauth.OAuthRequest.from_request(self.command, self.path, headers=self.headers, postdata=postdata) + + # request token + if self.path.startswith(REQUEST_TOKEN_URL): + try: + # create a request token + token = self.oauth_server.fetch_request_token(oauth_request) + # send okay response + self.send_response(200, 'OK') + self.end_headers() + # return the token + self.wfile.write(token.to_string()) + except oauth.OAuthError, err: + self.send_oauth_error(err) + return + + # user authorization + if self.path.startswith(AUTHORIZATION_URL): + try: + # get the request token + token = self.oauth_server.fetch_request_token(oauth_request) + callback = self.oauth_server.get_callback(oauth_request) + # send okay response + self.send_response(200, 'OK') + self.end_headers() + # return the callback url (to show server has it) + self.wfile.write('callback: %s' %callback) + # authorize the token (kind of does nothing for now) + token = self.oauth_server.authorize_token(token) + self.wfile.write('\n') + # return the token key + token_key = urllib.urlencode({'oauth_token': token.key}) + self.wfile.write('token key: %s' % token_key) + except oauth.OAuthError, err: + self.send_oauth_error(err) + return + + # access token + if self.path.startswith(ACCESS_TOKEN_URL): + try: + # create an access token + token = self.oauth_server.fetch_access_token(oauth_request) + # send okay response + self.send_response(200, 'OK') + self.end_headers() + # return the token + self.wfile.write(token.to_string()) + except oauth.OAuthError, err: + self.send_oauth_error(err) + return + + # protected resources + if self.path.startswith(RESOURCE_URL): + try: + # verify the request has been oauth authorized + consumer, token, params = self.oauth_server.verify_request(oauth_request) + # send okay response + self.send_response(200, 'OK') + self.end_headers() + # return the extra parameters - just for something to return + self.wfile.write(str(params)) + except oauth.OAuthError, err: + self.send_oauth_error(err) + return + + def do_POST(self): + return self.do_GET() + +def main(): + try: + server = HTTPServer(('', 8080), RequestHandler) + print 'Test server running...' + server.serve_forever() + except KeyboardInterrupt: + server.socket.close() + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/python_apps/soundcloud-api/oauth/oauth.py b/python_apps/soundcloud-api/oauth/oauth.py new file mode 100644 index 000000000..274bbd25b --- /dev/null +++ b/python_apps/soundcloud-api/oauth/oauth.py @@ -0,0 +1,505 @@ +import cgi +import urllib +import time +import random +import urlparse +import hmac +import hashlib +import base64 + +VERSION = '1.0' # Hi Blaine! +HTTP_METHOD = 'GET' +SIGNATURE_METHOD = 'PLAINTEXT' + +# Generic exception class +class OAuthError(RuntimeError): + def __init__(self, message='OAuth error occured'): + self.message = message + +# optional WWW-Authenticate header (401 error) +def build_authenticate_header(realm=''): + return {'WWW-Authenticate': 'OAuth realm="%s"' % realm} + +# url escape +def escape(s): + # escape '/' too + return urllib.quote(s, safe='') + +# util function: current timestamp +# seconds since epoch (UTC) +def generate_timestamp(): + return int(time.time()) + +# util function: nonce +# pseudorandom number +def generate_nonce(length=8): + return ''.join(str(random.randint(0, 9)) for i in range(length)) + +# OAuthConsumer is a data type that represents the identity of the Consumer +# via its shared secret with the Service Provider. +class OAuthConsumer(object): + key = None + secret = None + + def __init__(self, key, secret): + self.key = key + self.secret = secret + +# OAuthToken is a data type that represents an End User via either an access +# or request token. +class OAuthToken(object): + # access tokens and request tokens + key = None + secret = None + + ''' + key = the token + secret = the token secret + ''' + def __init__(self, key, secret): + self.key = key + self.secret = secret + + def to_string(self): + return urllib.urlencode({'oauth_token': self.key, 'oauth_token_secret': self.secret}) + + # return a token from something like: + # oauth_token_secret=digg&oauth_token=digg + @staticmethod + def from_string(s): + params = cgi.parse_qs(s, keep_blank_values=False) + key = params['oauth_token'][0] + secret = params['oauth_token_secret'][0] + return OAuthToken(key, secret) + + def __str__(self): + return self.to_string() + +# OAuthRequest represents the request and can be serialized +class OAuthRequest(object): + ''' + OAuth parameters: + - oauth_consumer_key + - oauth_token + - oauth_signature_method + - oauth_signature + - oauth_timestamp + - oauth_nonce + - oauth_version + ... any additional parameters, as defined by the Service Provider. + ''' + parameters = None # oauth parameters + http_method = HTTP_METHOD + http_url = None + version = VERSION + + def __init__(self, http_method=HTTP_METHOD, http_url=None, parameters=None): + self.http_method = http_method + self.http_url = http_url + self.parameters = parameters or {} + + def set_parameter(self, parameter, value): + self.parameters[parameter] = value + + def get_parameter(self, parameter): + try: + return self.parameters[parameter] + except: + raise OAuthError('Parameter not found: %s' % parameter) + + def _get_timestamp_nonce(self): + return self.get_parameter('oauth_timestamp'), self.get_parameter('oauth_nonce') + + # get any non-oauth parameters + def get_nonoauth_parameters(self): + parameters = {} + for k, v in self.parameters.iteritems(): + # ignore oauth parameters + if k.find('oauth_') < 0: + parameters[k] = v + return parameters + + # serialize as a header for an HTTPAuth request + def to_header(self, realm=''): + auth_header = 'OAuth realm="%s"' % realm + # add the oauth parameters + if self.parameters: + for k, v in self.parameters.iteritems(): + auth_header += ',\n\t %s="%s"' % (k, v) + return {'Authorization': auth_header} + + # serialize as post data for a POST request + def to_postdata(self): + return '&'.join('%s=%s' % (escape(str(k)), escape(str(v))) for k, v in self.parameters.iteritems()) + + # serialize as a url for a GET request + def to_url(self): + return '%s?%s' % (self.get_normalized_http_url(), self.to_postdata()) + + # return a string that consists of all the parameters that need to be signed + def get_normalized_parameters(self): + params = self.parameters + try: + # exclude the signature if it exists + del params['oauth_signature'] + except: + pass + key_values = params.items() + # sort lexicographically, first after key, then after value + key_values.sort() + # combine key value pairs in string and escape + return '&'.join('%s=%s' % (str(k), str(p)) for k, p in key_values) + + # just uppercases the http method + def get_normalized_http_method(self): + return self.http_method.upper() + + # parses the url and rebuilds it to be scheme://host/path + def get_normalized_http_url(self): + parts = urlparse.urlparse(self.http_url) + url_string = '%s://%s%s' % (parts.scheme, parts.netloc, parts.path) + return url_string + + # set the signature parameter to the result of build_signature + def sign_request(self, signature_method, consumer, token): + # set the signature method + self.set_parameter('oauth_signature_method', signature_method.get_name()) + # set the signature + self.set_parameter('oauth_signature', self.build_signature(signature_method, consumer, token)) + + def build_signature(self, signature_method, consumer, token): + # call the build signature method within the signature method + return signature_method.build_signature(self, consumer, token) + + @staticmethod + def from_request(http_method, http_url, headers=None, postdata=None, parameters=None): + + # let the library user override things however they'd like, if they know + # which parameters to use then go for it, for example XMLRPC might want to + # do this + if parameters is not None: + return OAuthRequest(http_method, http_url, parameters) + + # from the headers + if headers is not None: + try: + auth_header = headers['Authorization'] + # check that the authorization header is OAuth + auth_header.index('OAuth') + # get the parameters from the header + parameters = OAuthRequest._split_header(auth_header) + return OAuthRequest(http_method, http_url, parameters) + except: + pass + + # from the parameter string (post body) + if http_method == 'POST' and postdata is not None: + parameters = OAuthRequest._split_url_string(postdata) + + # from the url string + elif http_method == 'GET': + param_str = urlparse.urlparse(http_url).query + parameters = OAuthRequest._split_url_string(param_str) + + if parameters: + return OAuthRequest(http_method, http_url, parameters) + + raise OAuthError('Missing all OAuth parameters. OAuth parameters must be in the headers, post body, or url.') + + @staticmethod + def from_consumer_and_token(oauth_consumer, token=None, http_method=HTTP_METHOD, http_url=None, parameters=None): + if not parameters: + parameters = {} + + defaults = { + 'oauth_consumer_key': oauth_consumer.key, + 'oauth_timestamp': generate_timestamp(), + 'oauth_nonce': generate_nonce(), + 'oauth_version': OAuthRequest.version, + } + + defaults.update(parameters) + parameters = defaults + + if token: + parameters['oauth_token'] = token.key + + return OAuthRequest(http_method, http_url, parameters) + + @staticmethod + def from_token_and_callback(token, callback=None, http_method=HTTP_METHOD, http_url=None, parameters=None): + if not parameters: + parameters = {} + + parameters['oauth_token'] = token.key + + if callback: + parameters['oauth_callback'] = escape(callback) + + return OAuthRequest(http_method, http_url, parameters) + + # util function: turn Authorization: header into parameters, has to do some unescaping + @staticmethod + def _split_header(header): + params = {} + parts = header.split(',') + for param in parts: + # ignore realm parameter + if param.find('OAuth realm') > -1: + continue + # remove whitespace + param = param.strip() + # split key-value + param_parts = param.split('=', 1) + # remove quotes and unescape the value + params[param_parts[0]] = urllib.unquote(param_parts[1].strip('\"')) + return params + + # util function: turn url string into parameters, has to do some unescaping + @staticmethod + def _split_url_string(param_str): + parameters = cgi.parse_qs(param_str, keep_blank_values=False) + for k, v in parameters.iteritems(): + parameters[k] = urllib.unquote(v[0]) + return parameters + +# OAuthServer is a worker to check a requests validity against a data store +class OAuthServer(object): + timestamp_threshold = 300 # in seconds, five minutes + version = VERSION + signature_methods = None + data_store = None + + def __init__(self, data_store=None, signature_methods=None): + self.data_store = data_store + self.signature_methods = signature_methods or {} + + def set_data_store(self, oauth_data_store): + self.data_store = data_store + + def get_data_store(self): + return self.data_store + + def add_signature_method(self, signature_method): + self.signature_methods[signature_method.get_name()] = signature_method + return self.signature_methods + + # process a request_token request + # returns the request token on success + def fetch_request_token(self, oauth_request): + try: + # get the request token for authorization + token = self._get_token(oauth_request, 'request') + except: + # no token required for the initial token request + version = self._get_version(oauth_request) + consumer = self._get_consumer(oauth_request) + self._check_signature(oauth_request, consumer, None) + # fetch a new token + token = self.data_store.fetch_request_token(consumer) + return token + + # process an access_token request + # returns the access token on success + def fetch_access_token(self, oauth_request): + version = self._get_version(oauth_request) + consumer = self._get_consumer(oauth_request) + # get the request token + token = self._get_token(oauth_request, 'request') + self._check_signature(oauth_request, consumer, token) + new_token = self.data_store.fetch_access_token(consumer, token) + return new_token + + # verify an api call, checks all the parameters + def verify_request(self, oauth_request): + # -> consumer and token + version = self._get_version(oauth_request) + consumer = self._get_consumer(oauth_request) + # get the access token + token = self._get_token(oauth_request, 'access') + self._check_signature(oauth_request, consumer, token) + parameters = oauth_request.get_nonoauth_parameters() + return consumer, token, parameters + + # authorize a request token + def authorize_token(self, token): + return self.data_store.authorize_request_token(token) + + # get the callback url + def get_callback(self, oauth_request): + return oauth_request.get_parameter('oauth_callback') + + # optional support for the authenticate header + def build_authenticate_header(self, realm=''): + return {'WWW-Authenticate': 'OAuth realm="%s"' % realm} + + # verify the correct version request for this server + def _get_version(self, oauth_request): + try: + version = oauth_request.get_parameter('oauth_version') + except: + version = VERSION + if version and version != self.version: + raise OAuthError('OAuth version %s not supported' % str(version)) + return version + + # figure out the signature with some defaults + def _get_signature_method(self, oauth_request): + try: + signature_method = oauth_request.get_parameter('oauth_signature_method') + except: + signature_method = SIGNATURE_METHOD + try: + # get the signature method object + signature_method = self.signature_methods[signature_method] + except: + signature_method_names = ', '.join(self.signature_methods.keys()) + raise OAuthError('Signature method %s not supported try one of the following: %s' % (signature_method, signature_method_names)) + + return signature_method + + def _get_consumer(self, oauth_request): + consumer_key = oauth_request.get_parameter('oauth_consumer_key') + if not consumer_key: + raise OAuthError('Invalid consumer key') + consumer = self.data_store.lookup_consumer(consumer_key) + if not consumer: + raise OAuthError('Invalid consumer') + return consumer + + # try to find the token for the provided request token key + def _get_token(self, oauth_request, token_type='access'): + token_field = oauth_request.get_parameter('oauth_token') + token = self.data_store.lookup_token(token_type, token_field) + if not token: + raise OAuthError('Invalid %s token: %s' % (token_type, token_field)) + return token + + def _check_signature(self, oauth_request, consumer, token): + timestamp, nonce = oauth_request._get_timestamp_nonce() + self._check_timestamp(timestamp) + self._check_nonce(consumer, token, nonce) + signature_method = self._get_signature_method(oauth_request) + try: + signature = oauth_request.get_parameter('oauth_signature') + except: + raise OAuthError('Missing signature') + # attempt to construct the same signature + built = signature_method.build_signature(oauth_request, consumer, token) + if signature != built: + raise OAuthError('Invalid signature') + + def _check_timestamp(self, timestamp): + # verify that timestamp is recentish + timestamp = int(timestamp) + now = int(time.time()) + lapsed = now - timestamp + if lapsed > self.timestamp_threshold: + raise OAuthError('Expired timestamp: given %d and now %s has a greater difference than threshold %d' % (timestamp, now, self.timestamp_threshold)) + + def _check_nonce(self, consumer, token, nonce): + # verify that the nonce is uniqueish + try: + self.data_store.lookup_nonce(consumer, token, nonce) + raise OAuthError('Nonce already used: %s' % str(nonce)) + except: + pass + +# OAuthClient is a worker to attempt to execute a request +class OAuthClient(object): + consumer = None + token = None + + def __init__(self, oauth_consumer, oauth_token): + self.consumer = oauth_consumer + self.token = oauth_token + + def get_consumer(self): + return self.consumer + + def get_token(self): + return self.token + + def fetch_request_token(self, oauth_request): + # -> OAuthToken + raise NotImplementedError + + def fetch_access_token(self, oauth_request): + # -> OAuthToken + raise NotImplementedError + + def access_resource(self, oauth_request): + # -> some protected resource + raise NotImplementedError + +# OAuthDataStore is a database abstraction used to lookup consumers and tokens +class OAuthDataStore(object): + + def lookup_consumer(self, key): + # -> OAuthConsumer + raise NotImplementedError + + def lookup_token(self, oauth_consumer, token_type, token_token): + # -> OAuthToken + raise NotImplementedError + + def lookup_nonce(self, oauth_consumer, oauth_token, nonce, timestamp): + # -> OAuthToken + raise NotImplementedError + + def fetch_request_token(self, oauth_consumer): + # -> OAuthToken + raise NotImplementedError + + def fetch_access_token(self, oauth_consumer, oauth_token): + # -> OAuthToken + raise NotImplementedError + + def authorize_request_token(self, oauth_token): + # -> OAuthToken + raise NotImplementedError + +# OAuthSignatureMethod is a strategy class that implements a signature method +class OAuthSignatureMethod(object): + def get_name(): + # -> str + raise NotImplementedError + + def build_signature(oauth_request, oauth_consumer, oauth_token): + # -> str + raise NotImplementedError + +class OAuthSignatureMethod_HMAC_SHA1(OAuthSignatureMethod): + + def get_name(self): + return 'HMAC-SHA1' + + def build_signature(self, oauth_request, consumer, token): + sig = ( + escape(oauth_request.get_normalized_http_method()), + escape(oauth_request.get_normalized_http_url()), + escape(oauth_request.get_normalized_parameters()), + ) + + key = '%s&' % consumer.secret + if token: + key += token.secret + raw = '&'.join(sig) + + # hmac object + hashed = hmac.new(key, raw, hashlib.sha1) + + # calculate the digest base 64 + return base64.b64encode(hashed.digest()) + +class OAuthSignatureMethod_PLAINTEXT(OAuthSignatureMethod): + + def get_name(self): + return 'PLAINTEXT' + + def build_signature(self, oauth_request, consumer, token): + # concatenate the consumer key and secret + sig = escape(consumer.secret) + if token: + sig = '&'.join((sig, escape(token.secret))) + return sig diff --git a/python_apps/soundcloud-api/scapi/MultipartPostHandler.py b/python_apps/soundcloud-api/scapi/MultipartPostHandler.py new file mode 100644 index 000000000..34b12943f --- /dev/null +++ b/python_apps/soundcloud-api/scapi/MultipartPostHandler.py @@ -0,0 +1,135 @@ +#!/usr/bin/python + +#### +# 02/2006 Will Holcomb +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +""" +Usage: + Enables the use of multipart/form-data for posting forms + +Inspirations: + Upload files in python: + http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/146306 + urllib2_file: + Fabien Seisen: + +Example: + import MultipartPostHandler, urllib2, cookielib + + cookies = cookielib.CookieJar() + opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookies), + MultipartPostHandler.MultipartPostHandler) + params = { "username" : "bob", "password" : "riviera", + "file" : open("filename", "rb") } + opener.open("http://wwww.bobsite.com/upload/", params) + +Further Example: + The main function of this file is a sample which downloads a page and + then uploads it to the W3C validator. +""" + +import urllib +import urllib2 +import mimetools, mimetypes +import os, stat + +class Callable: + def __init__(self, anycallable): + self.__call__ = anycallable + +# Controls how sequences are uncoded. If true, elements may be given multiple values by +# assigning a sequence. +doseq = 1 + +class MultipartPostHandler(urllib2.BaseHandler): + handler_order = urllib2.HTTPHandler.handler_order - 10 # needs to run first + + def http_request(self, request): + data = request.get_data() + if data is not None and type(data) != str: + v_files = [] + v_vars = [] + try: + for(key, value) in data.items(): + if type(value) == file: + v_files.append((key, value)) + else: + v_vars.append((key, value)) + except TypeError: + systype, value, traceback = sys.exc_info() + raise TypeError, "not a valid non-string sequence or mapping object", traceback + + if len(v_files) == 0: + data = urllib.urlencode(v_vars, doseq) + else: + boundary, data = self.multipart_encode(v_vars, v_files) + contenttype = 'multipart/form-data; boundary=%s' % boundary + if(request.has_header('Content-Type') + and request.get_header('Content-Type').find('multipart/form-data') != 0): + print "Replacing %s with %s" % (request.get_header('content-type'), 'multipart/form-data') + request.add_unredirected_header('Content-Type', contenttype) + + request.add_data(data) + return request + + def multipart_encode(vars, files, boundary = None, buffer = None): + if boundary is None: + boundary = mimetools.choose_boundary() + if buffer is None: + buffer = '' + for(key, value) in vars: + if isinstance(value, basestring): + value = [value] + for sub_value in value: + buffer += '--%s\r\n' % boundary + buffer += 'Content-Disposition: form-data; name="%s"' % key + buffer += '\r\n\r\n' + sub_value + '\r\n' + for(key, fd) in files: + file_size = os.fstat(fd.fileno())[stat.ST_SIZE] + filename = fd.name.split('/')[-1] + contenttype = mimetypes.guess_type(filename)[0] or 'application/octet-stream' + buffer += '--%s\r\n' % boundary + buffer += 'Content-Disposition: form-data; name="%s"; filename="%s"\r\n' % (key, filename) + buffer += 'Content-Type: %s\r\n' % contenttype + # buffer += 'Content-Length: %s\r\n' % file_size + fd.seek(0) + buffer += '\r\n' + fd.read() + '\r\n' + buffer += '--%s--\r\n\r\n' % boundary + return boundary, buffer + multipart_encode = Callable(multipart_encode) + + https_request = http_request + +def main(): + import tempfile, sys + + validatorURL = "http://validator.w3.org/check" + opener = urllib2.build_opener(MultipartPostHandler) + + def validateFile(url): + temp = tempfile.mkstemp(suffix=".html") + os.write(temp[0], opener.open(url).read()) + params = { "ss" : "0", # show source + "doctype" : "Inline", + "uploaded_file" : open(temp[1], "rb") } + print opener.open(validatorURL, params).read() + os.remove(temp[1]) + + if len(sys.argv[1:]) > 0: + for arg in sys.argv[1:]: + validateFile(arg) + else: + validateFile("http://www.google.com") + +if __name__=="__main__": + main() diff --git a/python_apps/soundcloud-api/scapi/__init__.py b/python_apps/soundcloud-api/scapi/__init__.py new file mode 100644 index 000000000..ae737cb81 --- /dev/null +++ b/python_apps/soundcloud-api/scapi/__init__.py @@ -0,0 +1,1012 @@ +## SouncCloudAPI implements a Python wrapper around the SoundCloud RESTful +## API +## +## Copyright (C) 2008 Diez B. Roggisch +## Contact mailto:deets@soundcloud.com +## +## This library is free software; you can redistribute it and/or +## modify it under the terms of the GNU Lesser General Public +## License as published by the Free Software Foundation; either +## version 2.1 of the License, or (at your option) any later version. +## +## This library is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +## Lesser General Public License for more details. +## +## You should have received a copy of the GNU Lesser General Public +## License along with this library; if not, write to the Free Software +## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +import urllib +import urllib2 +import re + +import logging +import simplejson +import cgi +from scapi.MultipartPostHandler import MultipartPostHandler +from inspect import isclass +import urlparse +from scapi.authentication import BasicAuthenticator +from scapi.util import ( + escape, + MultiDict, + ) + +logging.basicConfig() +logger = logging.getLogger(__name__) + +USE_PROXY = False +""" +Something like http://127.0.0.1:10000/ +""" +PROXY = '' + + +""" +Endpoints, for reference: +The url Soundcould offers to obtain request-tokens: 'http://api.soundcloud.com/oauth/request_token' +The url Soundcould offers to exchange access-tokens for request-tokens: 'http://api.soundcloud.com/oauth/access_token' +The url Soundcould offers to make users authorize a concrete request token: 'http://api.soundcloud.com/oauth/authorize' +""" + +__all__ = ['SoundCloudAPI', 'USE_PROXY', 'PROXY'] + + +class NoResultFromRequest(Exception): + pass + +class InvalidMethodException(Exception): + + def __init__(self, message): + self._message = message + Exception.__init__(self) + + def __repr__(self): + res = Exception.__repr__(self) + res += "\n" + res += "-" * 10 + res += "\nmessage:\n\n" + res += self._message + return res + +class UnknownContentType(Exception): + def __init__(self, msg): + Exception.__init__(self) + self._msg = msg + + def __repr__(self): + return self.__class__.__name__ + ":" + self._msg + + def __str__(self): + return str(self) + +class PartitionCollectionGenerator(): + def __init__(self, scope, method, Gen, NextPartition): + self.NextPartition = NextPartition + self.Generator = Gen + self.Scope = scope + self.Method = method + + def __iter__(self): + return self.Generator + def next(self): + return self.Generator.next() + def __call__(self, someParam): + self.someParam = someParam + for line in self.content: + if line == someParam: + yield line + + def GetNextPartition(self): + if self.NextPartition != None: + method = re.search('(^[a-z]+)', self.Method).group(0) + params = re.search('\?.+', self.NextPartition).group(0) + params = params.replace('u0026', '&') + + return self.Scope._call(method, params) + else: + return None + +class ApiConnector(object): + """ + The ApiConnector holds all the data necessary to authenticate against + the soundcloud-api. You can instantiate several connectors if you like, but usually one + should be sufficient. + """ + + """ + SoundClound imposes a maximum on the number of returned items. This value is that + maximum. + """ + LIST_LIMIT = 50 + + """ + The query-parameter that is used to request results beginning from a certain offset. + """ + LIST_OFFSET_PARAMETER = 'offset' + """ + The query-parameter that is used to request results being limited to a certain amount. + + Currently this is of no use and just for completeness sake. + """ + LIST_LIMIT_PARAMETER = 'limit' + + def __init__(self, host, user=None, password=None, authenticator=None, base="", collapse_scope=True): + """ + Constructor for the API-Singleton. Use it once with parameters, and then the + subsequent calls internal to the API will work. + + @type host: str + @param host: the host to connect to, e.g. "api.soundcloud.com". If a port is needed, use + "api.soundcloud.com:1234" + @type user: str + @param user: if given, the username for basic HTTP authentication + @type password: str + @param password: if the user is given, you have to give a password as well + @type authenticator: OAuthAuthenticator | BasicAuthenticator + @param authenticator: the authenticator to use, see L{scapi.authentication} + """ + self.host = host + self.host = self.host.replace("http://", "") + if self.host[-1] == '/': # Remove a trailing slash, but leave other slashes alone + self.host = self.host[0:-1] + + if authenticator is not None: + self.authenticator = authenticator + elif user is not None and password is not None: + self.authenticator = BasicAuthenticator(user, password) + self._base = base + self.collapse_scope = collapse_scope + + def normalize_method(self, method): + """ + This method will take a method that has been part of a redirect of some sort + and see if it's valid, which means that it's located beneath our base. + If yes, we return it normalized without that very base. + """ + _, _, path, _, _, _ = urlparse.urlparse(method) + if path.startswith("/"): + path = path[1:] + # if the base is "", we return the whole path, + # otherwise normalize it away + if self._base == "": + return path + if path.startswith(self._base): + return path[len(self._base)-1:] + raise InvalidMethodException("Not a valid API method: %s" % method) + + + + def fetch_request_token(self, url=None, oauth_callback="oob", oauth_verifier=None): + """ + Helper-function for a registered consumer to obtain a request token, as + used by oauth. + + Use it like this: + + >>> oauth_authenticator = scapi.authentication.OAuthAuthenticator(CONSUMER, + CONSUMER_SECRET, + None, + None) + + >>> sca = scapi.ApiConnector(host=API_HOST, authenticator=oauth_authenticator) + >>> token, secret = sca.fetch_request_token() + >>> authorization_url = sca.get_request_token_authorization_url(token) + + Please note the None passed as token & secret to the authenticator. + """ + request_url = "http://" + self.host + "/oauth/request_token" + if url is None: + url = request_url + req = urllib2.Request(url) + self.authenticator.augment_request(req, None, oauth_callback=oauth_callback, oauth_verifier=oauth_verifier) + handlers = [] + if USE_PROXY: + handlers.append(urllib2.ProxyHandler({'http' : PROXY})) + opener = urllib2.build_opener(*handlers) + handle = opener.open(req, None) + info = handle.info() + content = handle.read() + params = cgi.parse_qs(content, keep_blank_values=False) + key = params['oauth_token'][0] + secret = params['oauth_token_secret'][0] + return key, secret + + + def fetch_access_token(self, oauth_verifier): + """ + Helper-function for a registered consumer to exchange an access token for + a request token. + + Use it like this: + + >>> oauth_authenticator = scapi.authentication.OAuthAuthenticator(CONSUMER, + CONSUMER_SECRET, + request_token, + request_token_secret) + + >>> sca = scapi.ApiConnector(host=API_HOST, authenticator=oauth_authenticator) + >>> token, secret = sca.fetch_access_token() + + Please note the values passed as token & secret to the authenticator. + """ + access_token_url = "http://" + self.host + "/oauth/access_token" + return self.fetch_request_token(access_token_url, oauth_verifier=oauth_verifier) + + + def get_request_token_authorization_url(self, token): + """ + Simple helper function to generate the url needed + to ask a user for request token authorization. + + See also L{fetch_request_token}. + + Possible usage: + + >>> import webbrowser + >>> sca = scapi.ApiConnector() + >>> authorization_url = sca.get_request_token_authorization_url(token) + >>> webbrowser.open(authorization_url) + """ + + auth_url = self.host.split("/")[0] + auth_url = "http://" + auth_url + "/oauth/authorize" + auth_url = auth_url.replace("api.", "") + return "%s?oauth_token=%s" % (auth_url, token) + + + +class SCRedirectHandler(urllib2.HTTPRedirectHandler): + """ + A urllib2-Handler to deal with the redirects the RESTful API of SC uses. + """ + alternate_method = None + + def http_error_303(self, req, fp, code, msg, hdrs): + """ + In case of return-code 303 (See-other), we have to store the location we got + because that will determine the actual type of resource returned. + """ + self.alternate_method = hdrs['location'] + # for oauth, we need to re-create the whole header-shizzle. This + # does it - it recreates a full url and signs the request + new_url = self.alternate_method +# if USE_PROXY: +# import pdb; pdb.set_trace() +# old_url = req.get_full_url() +# protocol, host, _, _, _, _ = urlparse.urlparse(old_url) +# new_url = urlparse.urlunparse((protocol, host, self.alternate_method, None, None, None)) + req = req.recreate_request(new_url) + return urllib2.HTTPRedirectHandler.http_error_303(self, req, fp, code, msg, hdrs) + + def http_error_201(self, req, fp, code, msg, hdrs): + """ + We fake a 201 being a 303 so that our redirection-scheme takes place + for the 201 the API throws in case we created something. If the location is + not available though, that means that whatever we created has succeded - without + being a named resource. Assigning an asset to a track is an example of such + case. + """ + if 'location' not in hdrs: + raise NoResultFromRequest() + return self.http_error_303(req, fp, 303, msg, hdrs) + +class Scope(object): + """ + The basic means to query and create resources. The Scope uses the L{ApiConnector} to + create the proper URIs for querying or creating resources. + + For accessing resources from the root level, you explcitly create a Scope and pass it + an L{ApiConnector}-instance. Then you can query it + or create new resources like this: + + >>> connector = scapi.ApiConnector(host='host', user='user', password='password') # initialize the API + >>> scope = scapi.Scope(connector) # get the root scope + >>> users = list(scope.users()) + [, ...] + + Please not that all resources that are lists are returned as B{generator}. So you need + to either iterate over them, or call list(resources) on them. + + When accessing resources that belong to another resource, like contacts of a user, you access + the parent's resource scope implicitly through the resource instance like this: + + >>> user = scope.users().next() + >>> list(user.contacts()) + [, ...] + + """ + def __init__(self, connector, scope=None, parent=None): + """ + Create the Scope. It can have a resource as scope, and possibly a parent-scope. + + @param connector: The connector to use. + @type connector: ApiConnector + @type scope: scapi.RESTBase + @param scope: the resource to make this scope belong to + @type parent: scapi.Scope + @param parent: the parent scope of this scope + """ + + if scope is None: + scope = () + else: + scope = scope, + if parent is not None: + scope = parent._scope + scope + self._scope = scope + self._connector = connector + + def _get_connector(self): + return self._connector + + + def oauth_sign_get_request(self, url): + """ + This method will take an arbitrary url, and rewrite it + so that the current authenticator's oauth-headers are appended + as query-parameters. + + This is used in streaming and downloading, because those content + isn't served from the SoundCloud servers themselves. + + A usage example would look like this: + + >>> sca = scapi.Scope(connector) + >>> track = sca.tracks(params={ + "filter" : "downloadable", + }).next() + + + >>> download_url = track.download_url + >>> signed_url = track.oauth_sign_get_request(download_url) + >>> data = urllib2.urlopen(signed_url).read() + + """ + scheme, netloc, path, params, query, fragment = urlparse.urlparse(url) + + req = urllib2.Request(url) + + all_params = {} + if query: + all_params.update(cgi.parse_qs(query)) + + if not all_params: + all_params = None + + self._connector.authenticator.augment_request(req, all_params, False) + + auth_header = req.get_header("Authorization") + auth_header = auth_header[len("OAuth "):] + + query_params = [] + if query: + query_params.append(query) + + for part in auth_header.split(","): + key, value = part.split("=") + assert key.startswith("oauth") + value = value[1:-1] + query_params.append("%s=%s" % (key, value)) + + query = "&".join(query_params) + url = urlparse.urlunparse((scheme, netloc, path, params, query, fragment)) + return url + + + def _create_request(self, url, connector, parameters, queryparams, alternate_http_method=None, use_multipart=False): + """ + This method returnes the urllib2.Request to perform the actual HTTP-request. + + We return a subclass that overload the get_method-method to return a custom method like "PUT". + Additionally, the request is enhanced with the current authenticators authorization scheme + headers. + + @param url: the destination url + @param connector: our connector-instance + @param parameters: the POST-parameters to use. + @type parameters: None|dict> + @param queryparams: the queryparams to use + @type queryparams: None|dict> + @param alternate_http_method: an alternate HTTP-method to use + @type alternate_http_method: str + @return: the fully equipped request + @rtype: urllib2.Request + """ + class MyRequest(urllib2.Request): + def get_method(self): + if alternate_http_method is not None: + return alternate_http_method + return urllib2.Request.get_method(self) + + def has_data(self): + return parameters is not None + + def augment_request(self, params, use_multipart=False): + connector.authenticator.augment_request(self, params, use_multipart) + + @classmethod + def recreate_request(cls, location): + return self._create_request(location, connector, None, None) + + req = MyRequest(url) + all_params = {} + if parameters is not None: + all_params.update(parameters) + if queryparams is not None: + all_params.update(queryparams) + if not all_params: + all_params = None + req.augment_request(all_params, use_multipart) + req.add_header("Accept", "application/json") + return req + + + def _create_query_string(self, queryparams): + """ + Small helpermethod to create the querystring from a dict. + + @type queryparams: None|dict> + @param queryparams: the queryparameters. + @return: either the empty string, or a "?" followed by the parameters joined by "&" + @rtype: str + """ + if not queryparams: + return "" + h = [] + for key, values in queryparams.iteritems(): + if isinstance(values, (int, long, float)): + values = str(values) + if isinstance(values, basestring): + values = [values] + for v in values: + v = v.encode("utf-8") + h.append("%s=%s" % (key, escape(v))) + return "?" + "&".join(h) + + + def _call(self, method, *args, **kwargs): + """ + The workhorse. It's complicated, convoluted and beyond understanding of a mortal being. + + You have been warned. + """ + + queryparams = {} + __offset__ = ApiConnector.LIST_LIMIT + if "__offset__" in kwargs: + offset = kwargs.pop("__offset__") + queryparams['offset'] = offset + __offset__ = offset + ApiConnector.LIST_LIMIT + + if "params" in kwargs: + queryparams.update(kwargs.pop("params")) + + # create a closure to invoke this method again with a greater offset + _cl_method = method + _cl_args = tuple(args) + _cl_kwargs = {} + _cl_kwargs.update(kwargs) + _cl_kwargs["__offset__"] = __offset__ + def continue_list_fetching(): + return self._call(method, *_cl_args, **_cl_kwargs) + connector = self._get_connector() + def filelike(v): + if isinstance(v, file): + return True + if hasattr(v, "read"): + return True + return False + alternate_http_method = None + if "_alternate_http_method" in kwargs: + alternate_http_method = kwargs.pop("_alternate_http_method") + urlparams = kwargs if kwargs else None + use_multipart = False + if urlparams is not None: + fileargs = dict((key, value) for key, value in urlparams.iteritems() if filelike(value)) + use_multipart = bool(fileargs) + + # ensure the method has a trailing / + if method[-1] != "/": + method = method + "/" + if args: + method = "%s%s" % (method, "/".join(str(a) for a in args)) + + scope = '' + if self._scope: + scopes = self._scope + if connector.collapse_scope: + scopes = scopes[-1:] + scope = "/".join([sc._scope() for sc in scopes]) + "/" + url = "http://%(host)s/%(base)s%(scope)s%(method)s%(queryparams)s" % dict(host=connector.host, method=method, base=connector._base, scope=scope, queryparams=self._create_query_string(queryparams)) + + # we need to install SCRedirectHandler + # to gather possible See-Other redirects + # so that we can exchange our method + redirect_handler = SCRedirectHandler() + handlers = [redirect_handler] + if USE_PROXY: + handlers.append(urllib2.ProxyHandler({'http' : PROXY})) + req = self._create_request(url, connector, urlparams, queryparams, alternate_http_method, use_multipart) + + http_method = req.get_method() + if urlparams is not None: + logger.debug("Posting url: %s, method: %s", url, http_method) + else: + logger.debug("Fetching url: %s, method: %s", url, http_method) + + + if use_multipart: + handlers.extend([MultipartPostHandler]) + else: + if urlparams is not None: + urlparams = urllib.urlencode(urlparams.items(), True) + opener = urllib2.build_opener(*handlers) + try: + handle = opener.open(req, urlparams) + except NoResultFromRequest: + return None + except urllib2.HTTPError, e: + if http_method == "GET" and e.code == 404: + return None + raise + + info = handle.info() + ct = info['Content-Type'] + content = handle.read() + logger.debug("Content-type:%s", ct) + logger.debug("Request Content:\n%s", content) + if redirect_handler.alternate_method is not None: + method = connector.normalize_method(redirect_handler.alternate_method) + logger.debug("Method changed through redirect to: <%s>", method) + + try: + if "application/json" in ct: + content = content.strip() + #If linked partitioning is on, extract the URL to the next collection: + partition_url = None + if method.find('linked_partitioning=1') != -1: + pattern = re.compile('(next_partition_href":")(.*?)(")') + if pattern.search(content): + partition_url = pattern.search(content).group(2) + + if not content: + content = "{}" + try: + res = simplejson.loads(content) + except: + logger.error("Couldn't decode returned json") + logger.error(content) + raise + res = self._map(res, method, continue_list_fetching, partition_url) + return res + elif len(content) <= 1: + # this might be the famous SeeOtherSpecialCase which means that + # all that matters is just the method + pass + raise UnknownContentType("%s, returned:\n%s" % (ct, content)) + finally: + handle.close() + + def _map(self, res, method, continue_list_fetching, next_partition = None): + """ + This method will take the JSON-result of a HTTP-call and return our domain-objects. + + It's also deep magic, don't look. + """ + pathparts = reversed(method.split("/")) + stack = [] + for part in pathparts: + stack.append(part) + if part in RESTBase.REGISTRY: + cls = RESTBase.REGISTRY[part] + # multiple objects, without linked partitioning + if isinstance(res, list): + def result_gen(): + count = 0 + for item in res: + yield cls(item, self, stack) + count += 1 + if count == ApiConnector.LIST_LIMIT: + for item in continue_list_fetching(): + yield item + logger.debug(res) + return PartitionCollectionGenerator(self, method, result_gen(), next_partition) + # multiple objects, with linked partitioning + elif isinstance(res, dict) and res.has_key('next_partition_href'): + def result_gen(): + count = 0 + for item in res['collection']: + yield cls(item, self, stack) + count += 1 + if count == ApiConnector.LIST_LIMIT: + for item in continue_list_fetching(): + yield item + logger.debug(res) + return PartitionCollectionGenerator(self, method, result_gen(), next_partition) + else: + return cls(res, self, stack) + logger.debug("don't know how to handle result") + logger.debug(res) + return res + + def __getattr__(self, _name): + """ + Retrieve an API-method or a scoped domain-class. + + If the former, result is a callable that supports the following invocations: + + - calling (...), with possible arguments (positional/keyword), return the resulting resource or list of resources. + When calling, you can pass a keyword-argument B{params}. This must be a dict or L{MultiDict} and will be used to add additional query-get-parameters. + + - invoking append(resource) on it will PUT the resource, making it part of the current resource. Makes + sense only if it's a collection of course. + + - invoking remove(resource) on it will DELETE the resource from it's container. Also only usable on collections. + + TODO: describe the latter + """ + scope = self + + class api_call(object): + def __call__(selfish, *args, **kwargs): + return self._call(_name, *args, **kwargs) + + def new(self, **kwargs): + """ + Will invoke the new method on the named resource _name, with + self as scope. + """ + cls = RESTBase.REGISTRY[_name] + return cls.new(scope, **kwargs) + + def append(selfish, resource): + """ + If the current scope is + """ + try: + self._call(_name, str(resource.id), _alternate_http_method="PUT") + except AttributeError: + self._call(_name, str(resource), _alternate_http_method="PUT") + + def remove(selfish, resource): + try: + self._call(_name, str(resource.id), _alternate_http_method="DELETE") + except AttributeError: + self._call(_name, str(resource), _alternate_http_method="DELETE") + + if _name in RESTBase.ALL_DOMAIN_CLASSES: + cls = RESTBase.ALL_DOMAIN_CLASSES[_name] + + class ScopeBinder(object): + def new(self, *args, **data): + + d = MultiDict() + name = cls._singleton() + + def unfold_value(key, value): + if isinstance(value, (basestring, file)): + d.add(key, value) + elif isinstance(value, dict): + for sub_key, sub_value in value.iteritems(): + unfold_value("%s[%s]" % (key, sub_key), sub_value) + else: + # assume iteration else + for sub_value in value: + unfold_value(key + "[]", sub_value) + + + for key, value in data.iteritems(): + unfold_value("%s[%s]" % (name, key), value) + + return scope._call(cls.KIND, **d) + + def create(self, **data): + return cls.create(scope, **data) + + def get(self, id): + return cls.get(scope, id) + + + return ScopeBinder() + return api_call() + + def __repr__(self): + return str(self) + + def __str__(self): + scopes = self._scope + base = "" + if len(scopes) > 1: + base = str(scopes[-2]) + return base + "/" + str(scopes[-1]) + + +# maybe someday I'll make that work. +# class RESTBaseMeta(type): +# def __new__(self, name, bases, d): +# clazz = type(name, bases, d) +# if 'KIND' in d: +# kind = d['KIND'] +# RESTBase.REGISTRY[kind] = clazz +# return clazz + +class RESTBase(object): + """ + The baseclass for all our domain-objects/resources. + + + """ + REGISTRY = {} + + ALL_DOMAIN_CLASSES = {} + + ALIASES = [] + + KIND = None + + def __init__(self, data, scope, path_stack=None): + self.__data = data + self.__scope = scope + # try and see if we can/must create an id out of our path + logger.debug("path_stack: %r", path_stack) + if path_stack: + try: + id = int(path_stack[0]) + self.__data['id'] = id + except ValueError: + pass + + def __getattr__(self, name): + if name in self.__data: + obj = self.__data[name] + if name in RESTBase.REGISTRY: + if isinstance(obj, dict): + obj = RESTBase.REGISTRY[name](obj, self.__scope) + elif isinstance(obj, list): + obj = [RESTBase.REGISTRY[name](o, self.__scope) for o in obj] + else: + logger.warning("Found %s in our registry, but don't know what to do with"\ + "the object.") + return obj + scope = Scope(self.__scope._get_connector(), scope=self, parent=self.__scope) + return getattr(scope, name) + + def __setattr__(self, name, value): + """ + This method is used to set a property, a resource or a list of resources as property of the resource the + method is invoked on. + + For example, to set a comment on a track, do + + >>> sca = scapi.Scope(connector) + >>> track = scapi.Track.new(title='bar', sharing="private") + >>> comment = scapi.Comment.create(body="This is the body of my comment", timestamp=10) + >>> track.comments = comment + + To set a list of users as permissions, do + + >>> sca = scapi.Scope(connector) + >>> me = sca.me() + >>> track = scapi.Track.new(title='bar', sharing="private") + >>> users = sca.users() + >>> users_to_set = [user for user in users[:10] if user != me] + >>> track.permissions = users_to_set + + And finally, to simply change the title of a track, do + + >>> sca = scapi.Scope(connector) + >>> track = sca.Track.get(track_id) + >>> track.title = "new_title" + + @param name: the property name + @type name: str + @param value: the property, resource or resources to set + @type value: RESTBase | list | basestring | long | int | float + @return: None + """ + + # update "private" data, such as __data + if "_RESTBase__" in name: + self.__dict__[name] = value + else: + if isinstance(value, list) and len(value): + # the parametername is something like + # permissions[user_id][] + # so we try to infer that. + parameter_name = "%s[%s_id][]" % (name, value[0]._singleton()) + values = [o.id for o in value] + kwargs = {"_alternate_http_method" : "PUT", + parameter_name : values} + self.__scope._call(self.KIND, self.id, name, **kwargs) + elif isinstance(value, RESTBase): + # we got a single instance, so make that an argument + self.__scope._call(self.KIND, self.id, name, **value._as_arguments()) + else: + # we have a simple property + parameter_name = "%s[%s]" % (self._singleton(), name) + kwargs = {"_alternate_http_method" : "PUT", + parameter_name : self._convert_value(value)} + self.__scope._call(self.KIND, self.id, **kwargs) + + def _as_arguments(self): + """ + Converts a resource to a argument-string the way Rails expects it. + """ + res = {} + for key, value in self.__data.items(): + value = self._convert_value(value) + res["%s[%s]" % (self._singleton(), key)] = value + return res + + def _convert_value(self, value): + if isinstance(value, unicode): + value = value.encode("utf-8") + elif isinstance(value, file): + pass + else: + value = str(value) + return value + + @classmethod + def create(cls, scope, **data): + """ + This is a convenience-method for creating an object that will be passed + as parameter - e.g. a comment. A usage would look like this: + + >>> sca = scapi.Scope(connector) + >>> track = sca.Track.new(title='bar', sharing="private") + >>> comment = sca.Comment.create(body="This is the body of my comment", timestamp=10) + >>> track.comments = comment + + """ + return cls(data, scope) + + @classmethod + def new(cls, scope, **data): + """ + Create a new resource inside a given Scope. The actual values are in data. + + So for creating new resources, you have two options: + + - create an instance directly using the class: + + >>> scope = scapi.Scope(connector) + >>> scope.User.new(...) + + + - create a instance in a certain scope: + + >>> scope = scapi.Scope(connector) + >>> user = scapi.User("1") + >>> track = user.tracks.new() + + + @param scope: if not empty, a one-element tuple containing the Scope + @type scope: tuple[1] + @param data: the data + @type data: dict + @return: new instance of the resource + """ + return getattr(scope, cls.__name__).new(**data) + + @classmethod + def get(cls, scope, id): + """ + Fetch a resource by id. + + Simply pass a known id as argument. For example + + >>> sca = scapi.Scope(connector) + >>> track = sca.Track.get(id) + + """ + return getattr(scope, cls.KIND)(id) + + + def _scope(self): + """ + Return the scope this resource lives in, which is the KIND and id + + @return: "/" + """ + return "%s/%s" % (self.KIND, str(self.id)) + + @classmethod + def _singleton(cls): + """ + This method will take a resource name like "users" and + return the single-case, in the example "user". + + Currently, it's not very sophisticated, only strips a trailing s. + """ + name = cls.KIND + if name[-1] == 's': + return name[:-1] + raise ValueError("Can't make %s to a singleton" % name) + + def __repr__(self): + res = [] + res.append("\n\n******\n%s:" % self.__class__.__name__) + res.append("") + for key, v in self.__data.iteritems(): + key = str(key) + if isinstance(v, unicode): + v = v.encode('utf-8') + else: + v = str(v) + res.append("%s=%s" % (key, v)) + return "\n".join(res) + + def __hash__(self): + return hash("%s%i" % (self.KIND, self.id)) + + def __eq__(self, other): + """ + Test for equality. + + Resources are considered equal if the have the same kind and id. + """ + if not isinstance(other, RESTBase): + return False + res = self.KIND == other.KIND and self.id == other.id + return res + + def __ne__(self, other): + return not self == other + +class User(RESTBase): + """ + A user domain object/resource. + """ + KIND = 'users' + ALIASES = ['me', 'permissions', 'contacts', 'user'] + +class Track(RESTBase): + """ + A track domain object/resource. + """ + KIND = 'tracks' + ALIASES = ['favorites'] + +class Comment(RESTBase): + """ + A comment domain object/resource. + """ + KIND = 'comments' + +class Event(RESTBase): + """ + A event domain object/resource. + """ + KIND = 'events' + +class Playlist(RESTBase): + """ + A playlist/set domain object/resource + """ + KIND = 'playlists' + +class Group(RESTBase): + """ + A group domain object/resource + """ + KIND = 'groups' + + + +# this registers all the RESTBase subclasses. +# One day using a metaclass will make this a tad +# less ugly. +def register_classes(): + g = {} + g.update(globals()) + for name, cls in [(k, v) for k, v in g.iteritems() if isclass(v) and issubclass(v, RESTBase) and not v == RESTBase]: + RESTBase.REGISTRY[cls.KIND] = cls + RESTBase.ALL_DOMAIN_CLASSES[cls.__name__] = cls + for alias in cls.ALIASES: + RESTBase.REGISTRY[alias] = cls + __all__.append(name) +register_classes() diff --git a/python_apps/soundcloud-api/scapi/authentication.py b/python_apps/soundcloud-api/scapi/authentication.py new file mode 100644 index 000000000..52c527704 --- /dev/null +++ b/python_apps/soundcloud-api/scapi/authentication.py @@ -0,0 +1,195 @@ +## SouncCloudAPI implements a Python wrapper around the SoundCloud RESTful +## API +## +## Copyright (C) 2008 Diez B. Roggisch +## Contact mailto:deets@soundcloud.com +## +## This library is free software; you can redistribute it and/or +## modify it under the terms of the GNU Lesser General Public +## License as published by the Free Software Foundation; either +## version 2.1 of the License, or (at your option) any later version. +## +## This library is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +## Lesser General Public License for more details. +## +## You should have received a copy of the GNU Lesser General Public +## License along with this library; if not, write to the Free Software +## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +import base64 +import time, random +import urlparse +import hmac +import hashlib +from scapi.util import escape +import logging + + +USE_DOUBLE_ESCAPE_HACK = True +""" +There seems to be an uncertainty on the way +parameters are to be escaped. For now, this +variable switches between two escaping mechanisms. + +If True, the passed parameters - GET or POST - are +escaped *twice*. +""" + +logger = logging.getLogger(__name__) + +class OAuthSignatureMethod_HMAC_SHA1(object): + + FORBIDDEN = ['realm', 'oauth_signature'] + + def get_name(self): + return 'HMAC-SHA1' + + def build_signature(self, request, parameters, consumer_secret, token_secret, oauth_parameters): + if logger.level == logging.DEBUG: + logger.debug("request: %r", request) + logger.debug("parameters: %r", parameters) + logger.debug("consumer_secret: %r", consumer_secret) + logger.debug("token_secret: %r", token_secret) + logger.debug("oauth_parameters: %r", oauth_parameters) + + + temp = {} + temp.update(oauth_parameters) + for p in self.FORBIDDEN: + if p in temp: + del temp[p] + if parameters is not None: + temp.update(parameters) + sig = ( + escape(self.get_normalized_http_method(request)), + escape(self.get_normalized_http_url(request)), + self.get_normalized_parameters(temp), # these are escaped in the method already + ) + + key = '%s&' % consumer_secret + if token_secret is not None: + key += token_secret + raw = '&'.join(sig) + logger.debug("raw basestring: %s", raw) + logger.debug("key: %s", key) + # hmac object + hashed = hmac.new(key, raw, hashlib.sha1) + # calculate the digest base 64 + signature = escape(base64.b64encode(hashed.digest())) + return signature + + + def get_normalized_http_method(self, request): + return request.get_method().upper() + + + # parses the url and rebuilds it to be scheme://host/path + def get_normalized_http_url(self, request): + url = request.get_full_url() + parts = urlparse.urlparse(url) + url_string = '%s://%s%s' % (parts.scheme, parts.netloc, parts.path) + return url_string + + + def get_normalized_parameters(self, params): + if params is None: + params = {} + try: + # exclude the signature if it exists + del params['oauth_signature'] + except: + pass + key_values = [] + + for key, values in params.iteritems(): + if isinstance(values, file): + continue + if isinstance(values, (int, long, float)): + values = str(values) + if isinstance(values, (list, tuple)): + values = [str(v) for v in values] + if isinstance(values, basestring): + values = [values] + if USE_DOUBLE_ESCAPE_HACK and not key.startswith("ouath"): + key = escape(key) + for v in values: + v = v.encode("utf-8") + key = key.encode("utf-8") + if USE_DOUBLE_ESCAPE_HACK and not key.startswith("oauth"): + # this is a dirty hack to make the + # thing work with the current server-side + # implementation. Or is it by spec? + v = escape(v) + key_values.append(escape("%s=%s" % (key, v))) + # sort lexicographically, first after key, then after value + key_values.sort() + # combine key value pairs in string + return escape('&').join(key_values) + + +class OAuthAuthenticator(object): + OAUTH_API_VERSION = '1.0' + AUTHORIZATION_HEADER = "Authorization" + + def __init__(self, consumer=None, consumer_secret=None, token=None, secret=None, signature_method=OAuthSignatureMethod_HMAC_SHA1()): + if consumer == None: + raise ValueError("The consumer key must be passed for all public requests; it may not be None") + self._consumer, self._token, self._secret = consumer, token, secret + self._consumer_secret = consumer_secret + self._signature_method = signature_method + random.seed() + + + def augment_request(self, req, parameters, use_multipart=False, oauth_callback=None, oauth_verifier=None): + oauth_parameters = { + 'oauth_consumer_key': self._consumer, + 'oauth_timestamp': self.generate_timestamp(), + 'oauth_nonce': self.generate_nonce(), + 'oauth_version': self.OAUTH_API_VERSION, + 'oauth_signature_method': self._signature_method.get_name(), + #'realm' : "http://soundcloud.com", + } + if self._token is not None: + oauth_parameters['oauth_token'] = self._token + + if oauth_callback is not None: + oauth_parameters['oauth_callback'] = oauth_callback + + if oauth_verifier is not None: + oauth_parameters['oauth_verifier'] = oauth_verifier + + # in case we upload large files, we don't + # sign the request over the parameters + # There's a bug in the OAuth 1.0 (and a) specs that says that PUT request should omit parameters from the base string. + # This is fixed in the IETF draft, don't know when this will be released though. - HT + if use_multipart or req.get_method() == 'PUT': + parameters = None + + oauth_parameters['oauth_signature'] = self._signature_method.build_signature(req, + parameters, + self._consumer_secret, + self._secret, + oauth_parameters) + def to_header(d): + return ",".join('%s="%s"' % (key, value) for key, value in sorted(oauth_parameters.items())) + + req.add_header(self.AUTHORIZATION_HEADER, "OAuth %s" % to_header(oauth_parameters)) + + def generate_timestamp(self): + return int(time.time())# * 1000.0) + + def generate_nonce(self, length=8): + return ''.join(str(random.randint(0, 9)) for i in range(length)) + + +class BasicAuthenticator(object): + + def __init__(self, user, password, consumer, consumer_secret): + self._base64string = base64.encodestring("%s:%s" % (user, password))[:-1] + self._x_auth_header = 'OAuth oauth_consumer_key="%s" oauth_consumer_secret="%s"' % (consumer, consumer_secret) + + def augment_request(self, req, parameters): + req.add_header("Authorization", "Basic %s" % self._base64string) + req.add_header("X-Authorization", self._x_auth_header) diff --git a/python_apps/soundcloud-api/scapi/config.py b/python_apps/soundcloud-api/scapi/config.py new file mode 100644 index 000000000..139597f9c --- /dev/null +++ b/python_apps/soundcloud-api/scapi/config.py @@ -0,0 +1,2 @@ + + diff --git a/python_apps/soundcloud-api/scapi/json.py b/python_apps/soundcloud-api/scapi/json.py new file mode 100644 index 000000000..a28a13e39 --- /dev/null +++ b/python_apps/soundcloud-api/scapi/json.py @@ -0,0 +1,310 @@ +import string +import types + +## json.py implements a JSON (http://json.org) reader and writer. +## Copyright (C) 2005 Patrick D. Logan +## Contact mailto:patrickdlogan@stardecisions.com +## +## This library is free software; you can redistribute it and/or +## modify it under the terms of the GNU Lesser General Public +## License as published by the Free Software Foundation; either +## version 2.1 of the License, or (at your option) any later version. +## +## This library is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +## Lesser General Public License for more details. +## +## You should have received a copy of the GNU Lesser General Public +## License along with this library; if not, write to the Free Software +## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +class _StringGenerator(object): + def __init__(self, string): + self.string = string + self.index = -1 + def peek(self): + i = self.index + 1 + if i < len(self.string): + return self.string[i] + else: + return None + def next(self): + self.index += 1 + if self.index < len(self.string): + return self.string[self.index] + else: + raise StopIteration + def all(self): + return self.string + +class WriteException(Exception): + pass + +class ReadException(Exception): + pass + +class JsonReader(object): + hex_digits = {'A': 10,'B': 11,'C': 12,'D': 13,'E': 14,'F':15} + escapes = {'t':'\t','n':'\n','f':'\f','r':'\r','b':'\b'} + + def read(self, s): + self._generator = _StringGenerator(s) + result = self._read() + return result + + def _read(self): + self._eatWhitespace() + peek = self._peek() + if peek is None: + raise ReadException, "Nothing to read: '%s'" % self._generator.all() + if peek == '{': + return self._readObject() + elif peek == '[': + return self._readArray() + elif peek == '"': + return self._readString() + elif peek == '-' or peek.isdigit(): + return self._readNumber() + elif peek == 't': + return self._readTrue() + elif peek == 'f': + return self._readFalse() + elif peek == 'n': + return self._readNull() + elif peek == '/': + self._readComment() + return self._read() + else: + raise ReadException, "Input is not valid JSON: '%s'" % self._generator.all() + + def _readTrue(self): + self._assertNext('t', "true") + self._assertNext('r', "true") + self._assertNext('u', "true") + self._assertNext('e', "true") + return True + + def _readFalse(self): + self._assertNext('f', "false") + self._assertNext('a', "false") + self._assertNext('l', "false") + self._assertNext('s', "false") + self._assertNext('e', "false") + return False + + def _readNull(self): + self._assertNext('n', "null") + self._assertNext('u', "null") + self._assertNext('l', "null") + self._assertNext('l', "null") + return None + + def _assertNext(self, ch, target): + if self._next() != ch: + raise ReadException, "Trying to read %s: '%s'" % (target, self._generator.all()) + + def _readNumber(self): + isfloat = False + result = self._next() + peek = self._peek() + while peek is not None and (peek.isdigit() or peek == "."): + isfloat = isfloat or peek == "." + result = result + self._next() + peek = self._peek() + try: + if isfloat: + return float(result) + else: + return int(result) + except ValueError: + raise ReadException, "Not a valid JSON number: '%s'" % result + + def _readString(self): + result = "" + assert self._next() == '"' + try: + while self._peek() != '"': + ch = self._next() + if ch == "\\": + ch = self._next() + if ch in 'brnft': + ch = self.escapes[ch] + elif ch == "u": + ch4096 = self._next() + ch256 = self._next() + ch16 = self._next() + ch1 = self._next() + n = 4096 * self._hexDigitToInt(ch4096) + n += 256 * self._hexDigitToInt(ch256) + n += 16 * self._hexDigitToInt(ch16) + n += self._hexDigitToInt(ch1) + ch = unichr(n) + elif ch not in '"/\\': + raise ReadException, "Not a valid escaped JSON character: '%s' in %s" % (ch, self._generator.all()) + result = result + ch + except StopIteration: + raise ReadException, "Not a valid JSON string: '%s'" % self._generator.all() + assert self._next() == '"' + return result + + def _hexDigitToInt(self, ch): + try: + result = self.hex_digits[ch.upper()] + except KeyError: + try: + result = int(ch) + except ValueError: + raise ReadException, "The character %s is not a hex digit." % ch + return result + + def _readComment(self): + assert self._next() == "/" + second = self._next() + if second == "/": + self._readDoubleSolidusComment() + elif second == '*': + self._readCStyleComment() + else: + raise ReadException, "Not a valid JSON comment: %s" % self._generator.all() + + def _readCStyleComment(self): + try: + done = False + while not done: + ch = self._next() + done = (ch == "*" and self._peek() == "/") + if not done and ch == "/" and self._peek() == "*": + raise ReadException, "Not a valid JSON comment: %s, '/*' cannot be embedded in the comment." % self._generator.all() + self._next() + except StopIteration: + raise ReadException, "Not a valid JSON comment: %s, expected */" % self._generator.all() + + def _readDoubleSolidusComment(self): + try: + ch = self._next() + while ch != "\r" and ch != "\n": + ch = self._next() + except StopIteration: + pass + + def _readArray(self): + result = [] + assert self._next() == '[' + done = self._peek() == ']' + while not done: + item = self._read() + result.append(item) + self._eatWhitespace() + done = self._peek() == ']' + if not done: + ch = self._next() + if ch != ",": + raise ReadException, "Not a valid JSON array: '%s' due to: '%s'" % (self._generator.all(), ch) + assert ']' == self._next() + return result + + def _readObject(self): + result = {} + assert self._next() == '{' + done = self._peek() == '}' + while not done: + key = self._read() + if type(key) is not types.StringType: + raise ReadException, "Not a valid JSON object key (should be a string): %s" % key + self._eatWhitespace() + ch = self._next() + if ch != ":": + raise ReadException, "Not a valid JSON object: '%s' due to: '%s'" % (self._generator.all(), ch) + self._eatWhitespace() + val = self._read() + result[key] = val + self._eatWhitespace() + done = self._peek() == '}' + if not done: + ch = self._next() + if ch != ",": + raise ReadException, "Not a valid JSON array: '%s' due to: '%s'" % (self._generator.all(), ch) + assert self._next() == "}" + return result + + def _eatWhitespace(self): + p = self._peek() + while p is not None and p in string.whitespace or p == '/': + if p == '/': + self._readComment() + else: + self._next() + p = self._peek() + + def _peek(self): + return self._generator.peek() + + def _next(self): + return self._generator.next() + +class JsonWriter(object): + + def _append(self, s): + self._results.append(s) + + def write(self, obj, escaped_forward_slash=False): + self._escaped_forward_slash = escaped_forward_slash + self._results = [] + self._write(obj) + return "".join(self._results) + + def _write(self, obj): + ty = type(obj) + if ty is types.DictType: + n = len(obj) + self._append("{") + for k, v in obj.items(): + self._write(k) + self._append(":") + self._write(v) + n = n - 1 + if n > 0: + self._append(",") + self._append("}") + elif ty is types.ListType or ty is types.TupleType: + n = len(obj) + self._append("[") + for item in obj: + self._write(item) + n = n - 1 + if n > 0: + self._append(",") + self._append("]") + elif ty is types.StringType or ty is types.UnicodeType: + self._append('"') + obj = obj.replace('\\', r'\\') + if self._escaped_forward_slash: + obj = obj.replace('/', r'\/') + obj = obj.replace('"', r'\"') + obj = obj.replace('\b', r'\b') + obj = obj.replace('\f', r'\f') + obj = obj.replace('\n', r'\n') + obj = obj.replace('\r', r'\r') + obj = obj.replace('\t', r'\t') + self._append(obj) + self._append('"') + elif ty is types.IntType or ty is types.LongType: + self._append(str(obj)) + elif ty is types.FloatType: + self._append("%f" % obj) + elif obj is True: + self._append("true") + elif obj is False: + self._append("false") + elif obj is None: + self._append("null") + else: + raise WriteException, "Cannot write in JSON: %s" % repr(obj) + +def write(obj, escaped_forward_slash=False): + return JsonWriter().write(obj, escaped_forward_slash) + +def read(s): + return JsonReader().read(s) diff --git a/python_apps/soundcloud-api/scapi/tests/__init__.py b/python_apps/soundcloud-api/scapi/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python_apps/soundcloud-api/scapi/tests/knaster.mp3 b/python_apps/soundcloud-api/scapi/tests/knaster.mp3 new file mode 100644 index 000000000..138749d65 Binary files /dev/null and b/python_apps/soundcloud-api/scapi/tests/knaster.mp3 differ diff --git a/python_apps/soundcloud-api/scapi/tests/scapi_tests.py b/python_apps/soundcloud-api/scapi/tests/scapi_tests.py new file mode 100644 index 000000000..71ea339dc --- /dev/null +++ b/python_apps/soundcloud-api/scapi/tests/scapi_tests.py @@ -0,0 +1,563 @@ +from __future__ import with_statement + +import os +import urllib2 +import itertools +from textwrap import dedent +import pkg_resources +import logging +import webbrowser +from unittest import TestCase + +from configobj import ConfigObj +from validate import Validator + + +import scapi +import scapi.authentication + +logger = logging.getLogger("scapi.tests") + +api_logger = logging.getLogger("scapi") + + +class SCAPITests(TestCase): + + CONFIG_NAME = "test.ini" + TOKEN = None + SECRET = None + CONSUMER = None + CONSUMER_SECRET = None + API_HOST = None + USER = None + PASSWORD = None + AUTHENTICATOR = None + RUN_INTERACTIVE_TESTS = False + RUN_LONG_TESTS = False + + def setUp(self): + self._load_config() + assert pkg_resources.resource_exists("scapi.tests.test_connect", "knaster.mp3") + self.data = pkg_resources.resource_stream("scapi.tests.test_connect", "knaster.mp3") + self.artwork_data = pkg_resources.resource_stream("scapi.tests.test_connect", "spam.jpg") + + CONFIGSPEC=dedent(""" + [api] + token=string + secret=string + consumer=string + consumer_secret=string + api_host=string + user=string + password=string + authenticator=option('oauth', 'base', default='oauth') + + [proxy] + use_proxy=boolean(default=false) + proxy=string(default=http://127.0.0.1:10000/) + + [logging] + test_logger=string(default=ERROR) + api_logger=string(default=ERROR) + + [test] + run_interactive_tests=boolean(default=false) + """) + + + def _load_config(self): + """ + Loads the configuration by looking from + + - the environment variable SCAPI_CONFIG + - the installation location upwards until it finds test.ini + - the current working directory upwards until it finds test.ini + + Raises an error if there is no config found + """ + config_name = self.CONFIG_NAME + + name = None + + if "SCAPI_CONFIG" in os.environ: + if os.path.exists(os.environ["SCAPI_CONFIG"]): + name = os.environ["SCAPI_CONFIG"] + + def search_for_config(current): + while current: + name = os.path.join(current, config_name) + if os.path.exists(name): + return name + new_current = os.path.dirname(current) + if new_current == current: + return + current = new_current + + if name is None: + name = search_for_config(os.path.dirname(__file__)) + if name is None: + name = search_for_config(os.getcwd()) + + if not name: + raise Exception("No test configuration file found!") + + parser = ConfigObj(name, configspec=self.CONFIGSPEC.split("\n")) + val = Validator() + if not parser.validate(val): + raise Exception("Config file validation error") + + api = parser['api'] + self.TOKEN = api.get('token') + self.SECRET = api.get('secret') + self.CONSUMER = api.get('consumer') + self.CONSUMER_SECRET = api.get('consumer_secret') + self.API_HOST = api.get('api_host') + self.USER = api.get('user', None) + self.PASSWORD = api.get('password', None) + self.AUTHENTICATOR = api.get("authenticator") + + # reset the hard-coded values in the api + if self.API_HOST: + scapi.AUTHORIZATION_URL = "http://%s/oauth/authorize" % self.API_HOST + scapi.REQUEST_TOKEN_URL = 'http://%s/oauth/request_token' % self.API_HOST + scapi.ACCESS_TOKEN_URL = 'http://%s/oauth/access_token' % self.API_HOST + + if "proxy" in parser and parser["proxy"]["use_proxy"]: + scapi.USE_PROXY = True + scapi.PROXY = parser["proxy"]["proxy"] + + if "logging" in parser: + logger.setLevel(getattr(logging, parser["logging"]["test_logger"])) + api_logger.setLevel(getattr(logging, parser["logging"]["api_logger"])) + + self.RUN_INTERACTIVE_TESTS = parser["test"]["run_interactive_tests"] + + + @property + def root(self): + """ + Return the properly configured root-scope. + """ + if self.AUTHENTICATOR == "oauth": + authenticator = scapi.authentication.OAuthAuthenticator(self.CONSUMER, + self.CONSUMER_SECRET, + self.TOKEN, + self.SECRET) + elif self.AUTHENTICATOR == "base": + authenticator = scapi.authentication.BasicAuthenticator(self.USER, self.PASSWORD, self.CONSUMER, self.CONSUMER_SECRET) + else: + raise Exception("Unknown authenticator setting: %s", self.AUTHENTICATOR) + + connector = scapi.ApiConnector(host=self.API_HOST, + authenticator=authenticator) + + logger.debug("RootScope: %s authenticator: %s", self.API_HOST, self.AUTHENTICATOR) + return scapi.Scope(connector) + + + def test_connect(self): + """ + test_connect + + Tries to connect & performs some read-only operations. + """ + sca = self.root + # quite_a_few_users = list(itertools.islice(sca.users(), 0, 127)) + + # logger.debug(quite_a_few_users) + # assert isinstance(quite_a_few_users, list) and isinstance(quite_a_few_users[0], scapi.User) + user = sca.me() + logger.debug(user) + assert isinstance(user, scapi.User) + contacts = list(user.contacts()) + assert isinstance(contacts, list) + if contacts: + assert isinstance(contacts[0], scapi.User) + logger.debug(contacts) + tracks = list(user.tracks()) + assert isinstance(tracks, list) + if tracks: + assert isinstance(tracks[0], scapi.Track) + logger.debug(tracks) + + + def test_access_token_acquisition(self): + """ + This test is commented out because it needs user-interaction. + """ + if not self.RUN_INTERACTIVE_TESTS: + return + oauth_authenticator = scapi.authentication.OAuthAuthenticator(self.CONSUMER, + self.CONSUMER_SECRET, + None, + None) + + sca = scapi.ApiConnector(host=self.API_HOST, authenticator=oauth_authenticator) + token, secret = sca.fetch_request_token() + authorization_url = sca.get_request_token_authorization_url(token) + webbrowser.open(authorization_url) + oauth_verifier = raw_input("please enter verifier code as seen in the browser:") + + oauth_authenticator = scapi.authentication.OAuthAuthenticator(self.CONSUMER, + self.CONSUMER_SECRET, + token, + secret) + + sca = scapi.ApiConnector(self.API_HOST, authenticator=oauth_authenticator) + token, secret = sca.fetch_access_token(oauth_verifier) + logger.info("Access token: '%s'", token) + logger.info("Access token secret: '%s'", secret) + # force oauth-authentication with the new parameters, and + # then invoke some simple test + self.AUTHENTICATOR = "oauth" + self.TOKEN = token + self.SECRET = secret + self.test_connect() + + + def test_track_creation(self): + sca = self.root + track = sca.Track.new(title='bar', asset_data=self.data) + assert isinstance(track, scapi.Track) + + + def test_track_update(self): + sca = self.root + track = sca.Track.new(title='bar', asset_data=self.data) + assert isinstance(track, scapi.Track) + track.title='baz' + track = sca.Track.get(track.id) + assert track.title == "baz" + + + def test_scoped_track_creation(self): + sca = self.root + user = sca.me() + track = user.tracks.new(title="bar", asset_data=self.data) + assert isinstance(track, scapi.Track) + + + def test_upload(self): + sca = self.root + sca = self.root + track = sca.Track.new(title='bar', asset_data=self.data) + assert isinstance(track, scapi.Track) + + + def test_contact_list(self): + sca = self.root + user = sca.me() + contacts = list(user.contacts()) + assert isinstance(contacts, list) + if contacts: + assert isinstance(contacts[0], scapi.User) + + + def test_permissions(self): + sca = self.root + user = sca.me() + tracks = itertools.islice(user.tracks(), 1) + for track in tracks: + permissions = list(track.permissions()) + logger.debug(permissions) + assert isinstance(permissions, list) + if permissions: + assert isinstance(permissions[0], scapi.User) + + + def test_setting_permissions(self): + sca = self.root + me = sca.me() + track = sca.Track.new(title='bar', sharing="private", asset_data=self.data) + assert track.sharing == "private" + users = itertools.islice(sca.users(), 10) + users_to_set = [user for user in users if user != me] + assert users_to_set, "Didn't find any suitable users" + track.permissions = users_to_set + assert set(track.permissions()) == set(users_to_set) + + + def test_setting_comments(self): + sca = self.root + user = sca.me() + track = sca.Track.new(title='bar', sharing="private", asset_data=self.data) + comment = sca.Comment.create(body="This is the body of my comment", timestamp=10) + track.comments = comment + assert track.comments().next().body == comment.body + + + def test_setting_comments_the_way_shawn_says_its_correct(self): + sca = self.root + track = sca.Track.new(title='bar', sharing="private", asset_data=self.data) + cbody = "This is the body of my comment" + track.comments.new(body=cbody, timestamp=10) + assert list(track.comments())[0].body == cbody + + + def test_contact_add_and_removal(self): + sca = self.root + me = sca.me() + for user in sca.users(): + if user != me: + user_to_set = user + break + + contacts = list(me.contacts()) + if user_to_set in contacts: + me.contacts.remove(user_to_set) + + me.contacts.append(user_to_set) + + contacts = list(me.contacts() ) + assert user_to_set.id in [c.id for c in contacts] + + me.contacts.remove(user_to_set) + + contacts = list(me.contacts() ) + assert user_to_set not in contacts + + + def test_favorites(self): + sca = self.root + me = sca.me() + + favorites = list(me.favorites()) + assert favorites == [] or isinstance(favorites[0], scapi.Track) + + track = None + for user in sca.users(): + if user == me: + continue + for track in user.tracks(): + break + if track is not None: + break + + me.favorites.append(track) + + favorites = list(me.favorites()) + assert track in favorites + + me.favorites.remove(track) + + favorites = list(me.favorites()) + assert track not in favorites + + + def test_large_list(self): + if not self.RUN_LONG_TESTS: + return + + sca = self.root + + tracks = list(sca.tracks()) + if len(tracks) < scapi.ApiConnector.LIST_LIMIT: + for i in xrange(scapi.ApiConnector.LIST_LIMIT): + sca.Track.new(title='test_track_%i' % i, asset_data=self.data) + all_tracks = sca.tracks() + assert not isinstance(all_tracks, list) + all_tracks = list(all_tracks) + assert len(all_tracks) > scapi.ApiConnector.LIST_LIMIT + + + + def test_filtered_list(self): + if not self.RUN_LONG_TESTS: + return + + sca = self.root + + tracks = list(sca.tracks(params={ + "bpm[from]" : "180", + })) + if len(tracks) < scapi.ApiConnector.LIST_LIMIT: + for i in xrange(scapi.ApiConnector.LIST_LIMIT): + sca.Track.new(title='test_track_%i' % i, asset_data=self.data) + all_tracks = sca.tracks() + assert not isinstance(all_tracks, list) + all_tracks = list(all_tracks) + assert len(all_tracks) > scapi.ApiConnector.LIST_LIMIT + + + def test_events(self): + events = list(self.root.events()) + assert isinstance(events, list) + assert isinstance(events[0], scapi.Event) + + + def test_me_having_stress(self): + sca = self.root + for _ in xrange(20): + self.setUp() + sca.me() + + + def test_non_global_api(self): + root = self.root + me = root.me() + assert isinstance(me, scapi.User) + + # now get something *from* that user + list(me.favorites()) + + + def test_playlists(self): + sca = self.root + playlists = list(itertools.islice(sca.playlists(), 0, 127)) + for playlist in playlists: + tracks = playlist.tracks + if not isinstance(tracks, list): + tracks = [tracks] + for trackdata in tracks: + print trackdata + #user = trackdata.user + #print user + #print user.tracks() + print playlist.user + break + + + + + def test_playlist_creation(self): + sca = self.root + sca.Playlist.new(title="I'm so happy, happy, happy, happy!") + + + + def test_groups(self): + if not self.RUN_LONG_TESTS: + return + + sca = self.root + groups = list(itertools.islice(sca.groups(), 0, 127)) + for group in groups: + users = group.users() + for user in users: + pass + + + def test_track_creation_with_email_sharers(self): + sca = self.root + emails = [dict(address="deets@web.de"), dict(address="hannes@soundcloud.com")] + track = sca.Track.new(title='bar', asset_data=self.data, + shared_to=dict(emails=emails) + ) + assert isinstance(track, scapi.Track) + + + + def test_track_creation_with_artwork(self): + sca = self.root + track = sca.Track.new(title='bar', + asset_data=self.data, + artwork_data=self.artwork_data, + ) + assert isinstance(track, scapi.Track) + + track.title = "foobarbaz" + + + + def test_oauth_get_signing(self): + sca = self.root + + url = "http://api.soundcloud.dev/oauth/test_request" + params = dict(foo="bar", + baz="padamm", + ) + url += sca._create_query_string(params) + signed_url = sca.oauth_sign_get_request(url) + + + res = urllib2.urlopen(signed_url).read() + assert "oauth_nonce" in res + + + def test_streaming(self): + sca = self.root + + track = sca.tracks(params={ + "filter" : "streamable", + }).next() + + + assert isinstance(track, scapi.Track) + + stream_url = track.stream_url + + signed_url = track.oauth_sign_get_request(stream_url) + + + def test_downloadable(self): + sca = self.root + + track = sca.tracks(params={ + "filter" : "downloadable", + }).next() + + + assert isinstance(track, scapi.Track) + + download_url = track.download_url + + signed_url = track.oauth_sign_get_request(download_url) + + data = urllib2.urlopen(signed_url).read() + assert data + + + + def test_modifying_playlists(self): + sca = self.root + + me = sca.me() + my_tracks = list(me.tracks()) + + assert my_tracks + + playlist = me.playlists().next() + # playlist = sca.Playlist.get(playlist.id) + + assert isinstance(playlist, scapi.Playlist) + + pl_tracks = playlist.tracks + + playlist.title = "foobarbaz" + + + + def test_track_deletion(self): + sca = self.root + track = sca.Track.new(title='bar', asset_data=self.data, + ) + + sca.tracks.remove(track) + + + + def test_track_creation_with_updated_artwork(self): + sca = self.root + track = sca.Track.new(title='bar', + asset_data=self.data, + ) + assert isinstance(track, scapi.Track) + + track.artwork_data = self.artwork_data + + def test_update_own_description(self): + sca = self.root + me = sca.me() + + new_description = "This is my new description" + old_description = "This is my old description" + + if me.description == new_description: + change_to_description = old_description + else: + change_to_description = new_description + + me.description = change_to_description + + user = sca.User.get(me.id) + assert user.description == change_to_description diff --git a/python_apps/soundcloud-api/scapi/tests/spam.jpg b/python_apps/soundcloud-api/scapi/tests/spam.jpg new file mode 100644 index 000000000..a5ca93879 Binary files /dev/null and b/python_apps/soundcloud-api/scapi/tests/spam.jpg differ diff --git a/python_apps/soundcloud-api/scapi/tests/test_connect.py b/python_apps/soundcloud-api/scapi/tests/test_connect.py new file mode 100644 index 000000000..3e045e6c1 --- /dev/null +++ b/python_apps/soundcloud-api/scapi/tests/test_connect.py @@ -0,0 +1,334 @@ +from __future__ import with_statement +import os +import tempfile +import itertools +from ConfigParser import SafeConfigParser +import pkg_resources +import scapi +import scapi.authentication +import logging +import webbrowser + +logger = logging.getLogger(__name__) +logger.setLevel(logging.DEBUG) +_logger = logging.getLogger("scapi") +#_logger.setLevel(logging.DEBUG) + +RUN_INTERACTIVE_TESTS = False +USE_OAUTH = True + +TOKEN = "FjNE9aRTg8kpxuOjzwsX8Q" +SECRET = "NP5PGoyKcQv64E0aZgV4CRNzHfPwR4QghrWoqEgEE" +CONSUMER = "EEi2URUfM97pAAxHTogDpQ" +CONSUMER_SECRET = "NFYd8T3i4jVKGZ9TMy9LHaBQB3Sh8V5sxBiMeMZBow" +API_HOST = "api.soundcloud.dev:3000" +USER = "" +PASSWORD = "" + +CONFIG_NAME = "soundcloud.cfg" + +CONNECTOR = None +ROOT = None +def setup(): + global CONNECTOR, ROOT + # load_config() + #scapi.ApiConnector(host='192.168.2.101:3000', user='tiga', password='test') + #scapi.ApiConnector(host='sandbox-api.soundcloud.com:3030', user='tiga', password='test') + scapi.USE_PROXY = False + scapi.PROXY = 'http://127.0.0.1:10000/' + + if USE_OAUTH: + authenticator = scapi.authentication.OAuthAuthenticator(CONSUMER, + CONSUMER_SECRET, + TOKEN, + SECRET) + else: + authenticator = scapi.authentication.BasicAuthenticator(USER, PASSWORD, CONSUMER, CONSUMER_SECRET) + + logger.debug("API_HOST: %s", API_HOST) + CONNECTOR = scapi.ApiConnector(host=API_HOST, + authenticator=authenticator) + ROOT = scapi.Scope(CONNECTOR) + +def load_config(config_name=None): + global TOKEN, SECRET, CONSUMER_SECRET, CONSUMER, API_HOST, USER, PASSWORD + if config_name is None: + config_name = CONFIG_NAME + parser = SafeConfigParser() + current = os.getcwd() + while current: + name = os.path.join(current, config_name) + if os.path.exists(name): + parser.read([name]) + TOKEN = parser.get('global', 'accesstoken') + SECRET = parser.get('global', 'accesstoken_secret') + CONSUMER = parser.get('global', 'consumer') + CONSUMER_SECRET = parser.get('global', 'consumer_secret') + API_HOST = parser.get('global', 'host') + USER = parser.get('global', 'user') + PASSWORD = parser.get('global', 'password') + logger.debug("token: %s", TOKEN) + logger.debug("secret: %s", SECRET) + logger.debug("consumer: %s", CONSUMER) + logger.debug("consumer_secret: %s", CONSUMER_SECRET) + logger.debug("user: %s", USER) + logger.debug("password: %s", PASSWORD) + logger.debug("host: %s", API_HOST) + break + new_current = os.path.dirname(current) + if new_current == current: + break + current = new_current + + +def test_load_config(): + base = tempfile.mkdtemp() + oldcwd = os.getcwd() + cdir = os.path.join(base, "foo") + os.mkdir(cdir) + os.chdir(cdir) + test_config = """ +[global] +host=host +consumer=consumer +consumer_secret=consumer_secret +accesstoken=accesstoken +accesstoken_secret=accesstoken_secret +user=user +password=password +""" + with open(os.path.join(base, CONFIG_NAME), "w") as cf: + cf.write(test_config) + load_config() + assert TOKEN == "accesstoken" and SECRET == "accesstoken_secret" and API_HOST == 'host' + assert CONSUMER == "consumer" and CONSUMER_SECRET == "consumer_secret" + assert USER == "user" and PASSWORD == "password" + os.chdir(oldcwd) + load_config() + + +def test_connect(): + sca = ROOT + quite_a_few_users = list(itertools.islice(sca.users(), 0, 127)) + + logger.debug(quite_a_few_users) + assert isinstance(quite_a_few_users, list) and isinstance(quite_a_few_users[0], scapi.User) + user = sca.me() + logger.debug(user) + assert isinstance(user, scapi.User) + contacts = list(user.contacts()) + assert isinstance(contacts, list) + assert isinstance(contacts[0], scapi.User) + logger.debug(contacts) + tracks = list(user.tracks()) + assert isinstance(tracks, list) + assert isinstance(tracks[0], scapi.Track) + logger.debug(tracks) + + +def test_access_token_acquisition(): + """ + This test is commented out because it needs user-interaction. + """ + if not RUN_INTERACTIVE_TESTS: + return + oauth_authenticator = scapi.authentication.OAuthAuthenticator(CONSUMER, + CONSUMER_SECRET, + None, + None) + + sca = scapi.ApiConnector(host=API_HOST, authenticator=oauth_authenticator) + token, secret = sca.fetch_request_token() + authorization_url = sca.get_request_token_authorization_url(token) + webbrowser.open(authorization_url) + raw_input("please press return") + oauth_authenticator = scapi.authentication.OAuthAuthenticator(CONSUMER, + CONSUMER_SECRET, + token, + secret) + + sca = scapi.ApiConnector(API_HOST, authenticator=oauth_authenticator) + token, secret = sca.fetch_access_token() + logger.info("Access token: '%s'", token) + logger.info("Access token secret: '%s'", secret) + oauth_authenticator = scapi.authentication.OAuthAuthenticator(CONSUMER, + CONSUMER_SECRET, + token, + secret) + + sca = scapi.ApiConnector(API_HOST, authenticator=oauth_authenticator) + test_track_creation() + +def test_track_creation(): + sca = ROOT + track = sca.Track.new(title='bar') + assert isinstance(track, scapi.Track) + +def test_track_update(): + sca = ROOT + track = sca.Track.new(title='bar') + assert isinstance(track, scapi.Track) + track.title='baz' + track = sca.Track.get(track.id) + assert track.title == "baz" + +def test_scoped_track_creation(): + sca = ROOT + user = sca.me() + track = user.tracks.new(title="bar") + assert isinstance(track, scapi.Track) + +def test_upload(): + assert pkg_resources.resource_exists("scapi.tests.test_connect", "knaster.mp3") + data = pkg_resources.resource_stream("scapi.tests.test_connect", "knaster.mp3") + sca = ROOT + user = sca.me() + logger.debug(user) + asset = sca.assets.new(filedata=data) + assert isinstance(asset, scapi.Asset) + logger.debug(asset) + tracks = list(user.tracks()) + track = tracks[0] + track.assets.append(asset) + +def test_contact_list(): + sca = ROOT + user = sca.me() + contacts = list(user.contacts()) + assert isinstance(contacts, list) + assert isinstance(contacts[0], scapi.User) + +def test_permissions(): + sca = ROOT + user = sca.me() + tracks = itertools.islice(user.tracks(), 1) + for track in tracks: + permissions = list(track.permissions()) + logger.debug(permissions) + assert isinstance(permissions, list) + if permissions: + assert isinstance(permissions[0], scapi.User) + +def test_setting_permissions(): + sca = ROOT + me = sca.me() + track = sca.Track.new(title='bar', sharing="private") + assert track.sharing == "private" + users = itertools.islice(sca.users(), 10) + users_to_set = [user for user in users if user != me] + assert users_to_set, "Didn't find any suitable users" + track.permissions = users_to_set + assert set(track.permissions()) == set(users_to_set) + +def test_setting_comments(): + sca = ROOT + user = sca.me() + track = sca.Track.new(title='bar', sharing="private") + comment = sca.Comment.create(body="This is the body of my comment", timestamp=10) + track.comments = comment + assert track.comments().next().body == comment.body + + +def test_setting_comments_the_way_shawn_says_its_correct(): + sca = ROOT + track = sca.Track.new(title='bar', sharing="private") + cbody = "This is the body of my comment" + track.comments.new(body=cbody, timestamp=10) + assert list(track.comments())[0].body == cbody + +def test_contact_add_and_removal(): + sca = ROOT + me = sca.me() + for user in sca.users(): + if user != me: + user_to_set = user + break + + contacts = list(me.contacts()) + if user_to_set in contacts: + me.contacts.remove(user_to_set) + + me.contacts.append(user_to_set) + + contacts = list(me.contacts() ) + assert user_to_set.id in [c.id for c in contacts] + + me.contacts.remove(user_to_set) + + contacts = list(me.contacts() ) + assert user_to_set not in contacts + + +def test_favorites(): + sca = ROOT + me = sca.me() + + favorites = list(me.favorites()) + assert favorites == [] or isinstance(favorites[0], scapi.Track) + + track = None + for user in sca.users(): + if user == me: + continue + for track in user.tracks(): + break + if track is not None: + break + + me.favorites.append(track) + + favorites = list(me.favorites()) + assert track in favorites + + me.favorites.remove(track) + + favorites = list(me.favorites()) + assert track not in favorites + +def test_large_list(): + sca = ROOT + tracks = list(sca.tracks()) + if len(tracks) < scapi.ApiConnector.LIST_LIMIT: + for i in xrange(scapi.ApiConnector.LIST_LIMIT): + scapi.Track.new(title='test_track_%i' % i) + all_tracks = sca.tracks() + assert not isinstance(all_tracks, list) + all_tracks = list(all_tracks) + assert len(all_tracks) > scapi.ApiConnector.LIST_LIMIT + + +def test_events(): + events = list(ROOT.events()) + assert isinstance(events, list) + assert isinstance(events[0], scapi.Event) + +def test_me_having_stress(): + sca = ROOT + for _ in xrange(20): + setup() + sca.me() + +def test_non_global_api(): + root = scapi.Scope(CONNECTOR) + me = root.me() + assert isinstance(me, scapi.User) + + # now get something *from* that user + favorites = list(me.favorites()) + assert favorites + +def test_playlists(): + sca = ROOT + playlists = list(itertools.islice(sca.playlists(), 0, 127)) + found = False + for playlist in playlists: + tracks = playlist.tracks + if not isinstance(tracks, list): + tracks = [tracks] + for trackdata in tracks: + print trackdata + user = trackdata.user + print user + print user.tracks() + print playlist.user + break diff --git a/python_apps/soundcloud-api/scapi/tests/test_oauth.py b/python_apps/soundcloud-api/scapi/tests/test_oauth.py new file mode 100644 index 000000000..d283c0048 --- /dev/null +++ b/python_apps/soundcloud-api/scapi/tests/test_oauth.py @@ -0,0 +1,36 @@ +import pkg_resources +import scapi +import scapi.authentication +import urllib +import logging + +logger = logging.getLogger(__name__) +logger.setLevel(logging.DEBUG) +_logger = logging.getLogger("scapi") +_logger.setLevel(logging.DEBUG) + +TOKEN = "QcciYu1FSwDSGKAG2mNw" +SECRET = "gJ2ok6ULUsYQB3rsBmpHCRHoFCAPOgK8ZjoIyxzris" +CONSUMER = "Cy2eLPrIMp4vOxjz9icdQ" +CONSUMER_SECRET = "KsBa272x6M2to00Vo5FdvZXt9kakcX7CDIPJoGwTro" + +def test_base64_connect(): + scapi.USE_PROXY = True + scapi.PROXY = 'http://127.0.0.1:10000/' + scapi.SoundCloudAPI(host='192.168.2.31:3000', authenticator=scapi.authentication.BasicAuthenticator('tiga', 'test')) + sca = scapi.Scope() + assert isinstance(sca.me(), scapi.User) + + +def test_oauth_connect(): + scapi.USE_PROXY = True + scapi.PROXY = 'http://127.0.0.1:10000/' + scapi.SoundCloudAPI(host='192.168.2.31:3000', + authenticator=scapi.authentication.OAuthAuthenticator(CONSUMER, + CONSUMER_SECRET, + TOKEN, SECRET)) + + sca = scapi.Scope() + assert isinstance(sca.me(), scapi.User) + + diff --git a/python_apps/soundcloud-api/scapi/util.py b/python_apps/soundcloud-api/scapi/util.py new file mode 100644 index 000000000..00fa66897 --- /dev/null +++ b/python_apps/soundcloud-api/scapi/util.py @@ -0,0 +1,53 @@ +## SouncCloudAPI implements a Python wrapper around the SoundCloud RESTful +## API +## +## Copyright (C) 2008 Diez B. Roggisch +## Contact mailto:deets@soundcloud.com +## +## This library is free software; you can redistribute it and/or +## modify it under the terms of the GNU Lesser General Public +## License as published by the Free Software Foundation; either +## version 2.1 of the License, or (at your option) any later version. +## +## This library is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +## Lesser General Public License for more details. +## +## You should have received a copy of the GNU Lesser General Public +## License along with this library; if not, write to the Free Software +## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +import urllib + +def escape(s): + # escape '/' too + return urllib.quote(s, safe='') + + + + + + +class MultiDict(dict): + + + def add(self, key, new_value): + if key in self: + value = self[key] + if not isinstance(value, list): + value = [value] + self[key] = value + value.append(new_value) + else: + self[key] = new_value + + + def iteritemslist(self): + for key, value in self.iteritems(): + if not isinstance(value, list): + value = [value] + yield key, value + + + diff --git a/python_apps/soundcloud-api/setup.py b/python_apps/soundcloud-api/setup.py new file mode 100644 index 000000000..dc057a603 --- /dev/null +++ b/python_apps/soundcloud-api/setup.py @@ -0,0 +1,22 @@ +from setuptools import setup, find_packages + +TEST_REQUIRES = ["ConfigObj>=4.5.3", "nose>=0.10"] + + +setup( + name = "SoundCloudAPI", + version = "0.1", + packages = find_packages(), + author = "Diez B. Roggisch", + author_email = "deets@web.de", + description = "This is an implementation of the SoundCloud RESTful API", + license = "MIT", + keywords = "Soundcloud client API REST", + url = "http://wiki.github.com/soundcloud/api/python-api-wrapper/", + install_requires = ['simplejson'] + TEST_REQUIRES, +# tests_require = TEST_REQUIRES, +# extras_require = dict( +# test = TEST_REQUIRES +# ), + test_suite = 'nose.collector' +) diff --git a/python_apps/soundcloud-api/test.ini b/python_apps/soundcloud-api/test.ini new file mode 100644 index 000000000..c9d1422b3 --- /dev/null +++ b/python_apps/soundcloud-api/test.ini @@ -0,0 +1,33 @@ +[api] + +# api.soundcloud.dev credentials +api_host=api.soundcloud.dev +consumer=gLnhFeUBnBCZF8a6Ngqq7w +consumer_secret=nbWRdG5X9xUb63l4nIeFYm3nmeVJ2v4s1ROpvRSBvU8 +token=xt5f76c7qPVCBNX3Vrw6A +secret=Ow2WHDKN4YRxrVfcPOt9pHf52Pzv70fp8lG0catQ5w + +# api.sandbox-soundcloud.com credentials +#api_host=api.sandbox-soundcloud.com +#consumer=gLnhFeUBnBCZF8a6Ngqq7w +#consumer_secret=nbWRdG5X9xUb63l4nIeFYm3nmeVJ2v4s1ROpvRSBvU8 +#token=8aX4ApxRweZJsZYe1PTFxQ +#secret=ydZOeG5zQXe8AiExvnzQVfqgySCrbFp0TLSz7BsnBqo + +user=deets +password=kgl27f + +#request_token_url=api.sandbox-soundcloud.com/oauth/request_token +#authorize_token_url=sandbox-soundcloud.com/oauth/authorize_token +#access_token_url=api.sandbox-soundcloud.com/oauth/request_token + +[proxy] +use_proxy=false + +[logging] +test_logger=DEBUG +api_logger=DEBUG + +[test] +run_interactive_tests=false +run_long_tests=false diff --git a/tests/application/controllers/RecorderControllerTest.php b/tests/application/controllers/RecorderControllerTest.php new file mode 100644 index 000000000..5e7700b8c --- /dev/null +++ b/tests/application/controllers/RecorderControllerTest.php @@ -0,0 +1,20 @@ + $tmpdir/$dbxml -echo "Packaging stored files ..." -cd $phpdir -storpath=`php -q getStorPath.php` -cd $storpath/.. -find stor -name "*.xml" -print | tar cf $tmpdir/$xmltar -T - -find stor ! -name "*.xml" -a -type f -print | tar cf $tmpdir/$destfile -T - -cd $tmpdir -tar rf $xmltar $dbxml --remove-files -echo "Compressing XML part ..." -bzip2 $xmltar -tar rf $destfile $xmltar.bz2 --remove-files -mv $destfile "$destdir" -rmdir $tmpdir - -#------------------------------------------------------------------------------- -# Say goodbye -#------------------------------------------------------------------------------- -echo "done" diff --git a/utils/airtime-backup.php b/utils/airtime-backup.php deleted file mode 100644 index 7a544d3eb..000000000 --- a/utils/airtime-backup.php +++ /dev/null @@ -1,194 +0,0 @@ -setFetchMode(DB_FETCHMODE_ASSOC); -$bs = new BasicStor(); - -$stid = $bs->storId; -#var_dump($stid); exit; -#$farr = $bs->bsListFolder($stid); var_dump($farr); exit; - -function admDumpFolder(&$bs, $fid, $ind='') -{ - // NOTE: need to fix this, removed due to tree removal - // $name = M2tree::GetObjName($fid); - // if (PEAR::isError($name)) { - // echo $name->getMessage(); - // exit; - // } - $media = StoredFile::Recall($fid); - $type = $media->getType(); - $gunid = $media->getGunid(); - $pars = array(); - if ($gunid) { - $pars['id']="$gunid"; - } - $pars['name'] = "$name"; - switch ($type) { - case "audioclip": - return XML_Util::createTagFromArray(array( - 'namespace' => NSPACE, - 'localPart' => 'audioClip', - 'attributes'=> $pars, - )); - break; - case "webstream": - return XML_Util::createTagFromArray(array( - 'namespace' => NSPACE, - 'localPart' => 'webstream', - 'attributes'=> $pars, - )); - break; - case "playlist": - return XML_Util::createTagFromArray(array( - 'namespace' => NSPACE, - 'localPart' => 'playlist', - 'attributes'=> $pars, - )); - break; - default: - return ""; - } -} - -function admDumpGroup(&$bs, $gid, $ind='') -{ - $name = Subjects::GetSubjName($gid); - if (PEAR::isError($name)) { - echo $name->getMessage(); - exit; - } - $isGr = Subjects::IsGroup($gid); - if (PEAR::isError($isGr)) { - echo $isGr->getMessage(); - exit; - } - $pars = array('name'=>"$name"); - $pars['id'] = $gid; - if (!$isGr) { - return XML_Util::createTagFromArray(array( - 'namespace' => NSPACE, - 'localPart' => 'member', - 'attributes'=> $pars, - )); - } - $garr = Subjects::ListGroup($gid); - if (PEAR::isError($garr)) { - echo $garr->getMessage(); - exit; - } - $res = ''; - foreach ($garr as $i => $member) { - $fid2 = $member['id']; - $res .= admDumpGroup($bs, $fid2, "$ind "); - } - $tagarr = array( - 'namespace' => NSPACE, - 'localPart' => 'group', - 'attributes'=> $pars, - ); - $prefs = admDumpPrefs($bs, $gid); - if (!is_null($prefs)) { - $res .= $prefs; - } - if ($res) { - $tagarr['content'] = $res; - } - return XML_Util::createTagFromArray($tagarr, $res === ''); - // if (!$res) { - // } else { - // return XML_Util::createTagFromArray(array( - // 'namespace' => NSPACE, - // 'localPart' => 'group', - // 'attributes'=> $pars, - // 'content' => $res, - // ), FALSE); - // } - - } - function admDumpSubjects(&$bs, $ind='') - { - $res =''; - $subjs = Subjects::GetSubjects('id, login, pass, type'); - foreach ($subjs as $i => $member) { - switch ($member['type']) { - case "U": - $prefs = admDumpPrefs($bs, $member['id']); - $pars = array('login'=>"{$member['login']}", 'pass'=>"{$member['pass']}"); - $pars['id'] = $member['id']; - $tagarr = array( - 'namespace' => NSPACE, - 'localPart' => 'user', - 'attributes'=> $pars, - ); - if (!is_null($prefs)) { - $tagarr['content'] = $prefs; - } - $res .= XML_Util::createTagFromArray($tagarr , FALSE); - break; - case "G": - $res .= admDumpGroup($bs, $member['id'], "$ind "); - break; - } - } - # return "$ind\n$res$ind\n"; - return XML_Util::createTagFromArray(array( - 'namespace' => NSPACE, - 'localPart' => 'subjects', - 'content'=> $res, - ), FALSE); - } - - function admDumpPrefs(&$bs, $subjid) - { - $res =''; - $pr = new Prefs($bs); - $prefkeys = $pr->readKeys($subjid); - # var_dump($subjid); var_dump($prefkeys); #exit; - foreach ($prefkeys as $i => $prefk) { - $keystr = $prefk['keystr']; - $prefval = $pr->readVal($subjid, $keystr); - $pars = array('name'=>"$keystr", 'val'=>"$prefval"); - $res .= XML_Util::createTagFromArray(array( - 'namespace' => NSPACE, - 'localPart' => 'pref', - 'attributes'=> $pars, - )); - } - if (!$res) { - return NULL; - } - return XML_Util::createTagFromArray(array( - 'namespace' => NSPACE, - 'localPart' => 'preferences', - 'content'=> $res, - ), FALSE); - } - - $subjects = admDumpSubjects($bs, ' '); - $tree = admDumpFolder($bs, $stid, ' '); - - $res = XML_Util::getXMLDeclaration("1.0", "UTF-8")."\n"; - $node = XML_Util::createTagFromArray(array( - 'namespace' => NSPACE, - 'localPart' => 'storageServer', - 'content' => "$subjects$tree", - ), FALSE); - $res .= $node; - - $fmt = new XML_Beautifier(); - $res = $fmt->formatString($res); - - #var_export($res); - #var_dump($res); - echo "$res"; diff --git a/utils/airtime-import.php b/utils/airtime-import.php index 714da2510..9e0696728 100644 --- a/utils/airtime-import.php +++ b/utils/airtime-import.php @@ -13,7 +13,7 @@ error_reporting(E_ALL); set_error_handler("camp_import_error_handler", E_ALL & !E_NOTICE); require_once(dirname(__FILE__)."/../application/configs/conf.php"); -require_once(dirname(__FILE__)."/../application/models/GreenBox.php"); +require_once(dirname(__FILE__)."/../application/models/StoredFile.php"); require_once('DB.php'); require_once('Console/Getopt.php'); @@ -104,8 +104,6 @@ function camp_import_audio_file($p_filepath, $p_importMode = null, $p_testOnly = return; } - $greenbox = new GreenBox(); - $fileCount = 0; $duplicates = 0; diff --git a/utils/airtime-restore b/utils/airtime-restore deleted file mode 100755 index 04e23f1a7..000000000 --- a/utils/airtime-restore +++ /dev/null @@ -1,110 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------------------- -# Copyright (c) 2010 Sourcefabric O.P.S. -# -# This file is part of the Airtime project. -# http://airtime.sourcefabric.org/ -# -# Airtime is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# Airtime is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Airtime; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -# -#------------------------------------------------------------------------------- -#------------------------------------------------------------------------------- -# This script restores the data which was backed up with airtime-backup. -# -# To get usage help, try the -h option -#------------------------------------------------------------------------------- - -#------------------------------------------------------------------------------- -# Determine directories, files -#------------------------------------------------------------------------------- - -reldir=`dirname $0` -phpdir=$reldir -mkdir -p $reldir/tmp -tmpmaindir=`cd $reldir/tmp; pwd` -dbxml="db.xml" -tarfile0="xmls.tar" - -#------------------------------------------------------------------------------- -# Print the usage information for this script. -#------------------------------------------------------------------------------- -printUsage() -{ - echo "This script restores the data which was backed up with airtime-backup." - echo "parameters:"; - echo ""; - echo " -f, --file File with the backed up data, required."; - echo " -h, --help Print this message and exit."; - echo ""; -} - -#------------------------------------------------------------------------------- -# Process command line parameters -#------------------------------------------------------------------------------- -CMD=${0##*/} - -opts=$(getopt -o hf: -l help,file -n $CMD -- "$@") || exit 1 -eval set -- "$opts" -while true; do - case "$1" in - -h|--help) - printUsage; - exit 0;; - -f|--file) - tarfile=$2 - shift; shift;; - --) - shift; - break;; - *) - echo "Unrecognized option $1."; - printUsage; - exit 1; - esac -done - -if [ "x$tarfile" == "x" ]; then - echo "Required parameter file not specified."; - printUsage; - exit 1; -fi - -tfdir=`dirname $tarfile` -tfdir=`cd $tfdir; pwd` -tfbname=`basename $tarfile` -tarfile="$tfdir/$tfbname" - -#------------------------------------------------------------------------------- -# Do restore -#------------------------------------------------------------------------------- - -tmpdir=`mktemp -d $tmpmaindir/tmp.XXXXXX` - -echo "Restoring database from $tarfile ..." -cd $tmpdir -tar xf $tarfile -tar xjf $tarfile0.bz2 -rm -f $tarfile0.bz2 -cd $phpdir -php -q restore.php $tmpdir/$dbxml $tmpdir -rm -rf "$tmpdir/stor" -rm -f $tmpdir/* -rmdir "$tmpdir" - - -#------------------------------------------------------------------------------- -# Say goodbye -#------------------------------------------------------------------------------- -echo "done" diff --git a/utils/restore.php b/utils/restore.php deleted file mode 100644 index 1daf99498..000000000 --- a/utils/restore.php +++ /dev/null @@ -1,257 +0,0 @@ - $el->attrs['name']->val, - 'type' => $el->name, - ); - switch ($res['type']) { - case 'folder': - foreach ($el->children as $i => $o) { - $res['children'][] = ls_restore_processObject($o); - } - break; - default: - $res['gunid'] = $el->attrs['id']->val; - break; - } - return $res; -} - -function ls_restore_checkErr($r, $ln='') -{ - if (PEAR::isError($r)) { - echo "ERROR $ln: ".$r->getMessage()." ".$r->getUserInfo()."\n"; - exit; - } -} - -function ls_restore_restoreObject($obj, /*$parid,*/ $reallyInsert=TRUE){ - global $tmpdir, $bs; - switch ($obj['type']) { - case "audioClip"; - case "webstream"; - case "playlist"; - $gunid = $obj['gunid']; - if (is_null($gunid)) { - break; - } - $gunid3 = substr($gunid, 0, 3); - $mediaFile = "$tmpdir/stor/$gunid3/$gunid"; -# echo "X1 $gunid, $gunid3, $mediaFile\n"; - if (!file_exists($mediaFile)) { - $mediaFile = NULL; - } - $mdataFile = "$tmpdir/stor/$gunid3/$gunid.xml"; - if (!file_exists($mdataFile)) { - $mdataFile = NULL; - } - if ($reallyInsert) { - if (VERBOSE) { - echo " creating file {$obj['name']} ...\n"; - } - $values = array( - "filename" => $obj['name'], - "filepath" => $mediaFile, - "metadata" => $mdataFile, - "gunid" => $obj['gunid'], - "filetype" => strtolower($obj['type']) - ); - $r = StoredFile::Insert($values); - ls_restore_checkErr($r, __LINE__); - } - break; - } -} - -/* =============================================================== processing */ - -PEAR::setErrorHandling(PEAR_ERROR_RETURN); -$CC_DBC = DB::connect($CC_CONFIG['dsn'], TRUE); -$CC_DBC->setFetchMode(DB_FETCHMODE_ASSOC); -$bs = new BasicStor(); -$pr = new Prefs($bs); - -$dbxml = file_get_contents($argv[1]); -$tmpdir = $argv[2]; - -require_once("$STORAGE_SERVER_PATH/var/XmlParser.php"); -$parser = new XmlParser($dbxml); -if ($parser->isError()) { - return PEAR::raiseError( - "MetaData::parse: ".$parser->getError() - ); -} -$xmlTree = $parser->getTree(); - - -/* ----------------------------------------- processing storageServer element */ -$subjArr = FALSE; -//$tree = FALSE; -foreach ($xmlTree->children as $i => $el) { - switch ($el->name) { - case "subjects": - if ($subjArr !== FALSE) { - echo "ERROR: unexpected subjects element\n"; - } - $subjArr = $el->children; - break; -// case "folder": -// if ($tree !== FALSE) { -// echo "ERROR: unexpected folder element\n"; -// } -// $tree = ls_restore_processObject($el); -// break; - default: - echo "ERROR: unknown element name {$el->name}\n"; - exit; - } -// echo "{$el->name}\n"; -} - -/* ---------------------------------------------- processing subjects element */ -$subjects = array(); -$groups = array(); -foreach ($subjArr as $i => $el) { - switch ($el->name) { - case "group": - $grname = $el->attrs['name']->val; - $groups[$grname] = $el->children; - $subjects[$grname] = array( - 'type' => 'group', - ); - break; - case "user": - $login = $el->attrs['login']->val; - $subjects[$login] = array( - 'type' => 'user', - 'pass' => $el->attrs['pass']->val, -# 'realname' => $el->attrs['realname']->val, - 'realname' => '', - 'prefs' => (isset($el->children[0]) ? $el->children[0]->children : NULL), - ); - break; - } -} - -/* -------------------------------------------------------- processing groups */ -foreach ($groups as $grname => $group) { - foreach ($group as $i => $el) { - switch ($el->name) { - case "member": - $groups[$grname][$i] = $el->attrs['name']->val; - break; - case "preferences": - $subjects[$grname]['prefs'] = $el->children; - unset($groups[$grname][$i]); - break; - } - } -} - -#var_dump($xmlTree); -#var_dump($subjArr); -#var_dump($groups); -#var_dump($subjects); -#var_dump($tree); - -#exit; - -/* ================================================================ restoring */ - -if (VERBOSE) { - echo " resetting storage ...\n"; -} -$bs->resetStorage(FALSE); -$storId = $bs->storId; - -/* ------------------------------------------------------- restoring subjects */ -foreach ($subjects as $login => $subj) { - $uid0 = Subjects::GetSubjId($login); - ls_restore_checkErr($uid0); - switch ($subj['type']) { - case "user": - if ($login=='root') { - $r = $bs->passwd($login, NULL, $subj['pass'], TRUE); - ls_restore_checkErr($r, __LINE__); - $uid = $uid0; - } else { - if (!is_null($uid0)) { - $r = $bs->removeSubj($login); - ls_restore_checkErr($r, __LINE__); - } - if (VERBOSE) { - echo " adding user $login ...\n"; - } - $uid = BasicStor::addSubj($login, $subj['pass'], $subj['realname'], TRUE); - ls_restore_checkErr($uid, __LINE__); - } - break; - case "group": - if (!is_null($uid0)) { - $r = $bs->removeSubj($login); - if (PEAR::isError($r)) { - $uid = $uid0; - break; - } - //ls_restore_checkErr($r, __LINE__); - } - if (VERBOSE) { - echo " adding group $login ...\n"; - } - $uid = BasicStor::addSubj($login, NULL); - ls_restore_checkErr($uid, __LINE__); -// var_export($uid); echo " "; - break; - } // switch - -// echo "$login/$uid :\n"; - if (isset($subj['prefs'])) { -// var_dump($subj['prefs']); exit; - foreach ($subj['prefs'] as $i => $el) { - switch ($el->name) { - case "pref": - $prefkey = $el->attrs['name']->val; - $prefval = $el->attrs['val']->val; -// echo" PREF($prefkey)=$prefval\n"; - $res = $pr->insert($uid, $prefkey, $prefval); - ls_restore_checkErr($res, __LINE__); - break; - default: - var_dump($el); - } - } - } -} - -/* --------------------------------------------------------- restoring groups */ -#var_dump($groups); -foreach ($groups as $grname => $group) { - foreach ($group as $i => $login) { - if (VERBOSE) { - echo " adding subject $login to group $grname ...\n"; - } - $r = Subjects::AddSubjectToGroup($login, $grname); - ls_restore_checkErr($r, __LINE__); - } -} - -/* -------------------------------------------------------- restoring objects */ -ls_restore_restoreObject($tree, /*$storId,*/ FALSE); - diff --git a/utils/restore2.php b/utils/restore2.php deleted file mode 100755 index 026a6fe18..000000000 --- a/utils/restore2.php +++ /dev/null @@ -1,36 +0,0 @@ -getMessage()); -} -$CC_DBC->setFetchMode(DB_FETCHMODE_ASSOC); - -$gb = new GreenBox(); -$rs = new Restore($gb); - -if ($rs->loglevel=='debug') { - $rs->addLogItem('argv:'.print_r($argv,true)); -} - -$backupfile = $argv[1]; -$token = $argv[3]; -$sessid = $argv[4]; -$rs->startRestore($backupfile,$token,$sessid); - diff --git a/utils/upgrade-sql.php b/utils/upgrade-sql.php new file mode 100644 index 000000000..5d792357a --- /dev/null +++ b/utils/upgrade-sql.php @@ -0,0 +1,3 @@ +