diff --git a/.gitignore b/.gitignore index 47bb04716..2db593e69 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ .* *.pyc -files/ -pypo/liquidsoap/liquidsoap +/files +python_apps/pypo/liquidsoap/liquidsoap build/build.properties diff --git a/application/Bootstrap.php b/application/Bootstrap.php index 9911b8931..19a2cc72c 100644 --- a/application/Bootstrap.php +++ b/application/Bootstrap.php @@ -1,7 +1,7 @@ setFetchMode(DB_FETCHMODE_ASSOC); //Zend_Session::start(); Zend_Validate::setDefaultNamespaces("Zend"); +$front = Zend_Controller_Front::getInstance(); +$front->registerPlugin(new RabbitMqPlugin()); + class Bootstrap extends Zend_Application_Bootstrap_Bootstrap { protected function _initDoctype() @@ -45,9 +51,7 @@ class Bootstrap extends Zend_Application_Bootstrap_Bootstrap protected function _initHeadLink() { $view = $this->getResource('view'); - $view->headLink()->appendStylesheet('/css/redmond/jquery-ui-1.8.8.custom.css'); - $this->view->headLink()->appendStylesheet('/css/pro_dropdown_3.css'); $this->view->headLink()->appendStylesheet('/css/styles.css'); } @@ -64,17 +68,20 @@ class Bootstrap extends Zend_Application_Bootstrap_Bootstrap $this->view->headScript()->appendFile('/js/playlist/helperfunctions.js','text/javascript'); $this->view->headScript()->appendFile('/js/playlist/playlist.js','text/javascript'); - $view->headScript()->appendFile('/js/airtime/common/common.js','text/javascript'); + $view->headScript()->appendFile('/js/airtime/common/common.js','text/javascript'); } - - protected function _initViewHelpers(){ + + protected function _initViewHelpers() + { $view = $this->getResource('view'); $view->addHelperPath('../application/views/helpers', 'Airtime_View_Helper'); } - - protected function _initTitle(){ + + protected function _initTitle() + { $view = $this->getResource('view'); $view->headTitle(Application_Model_Preference::GetHeadTitle()); } + } diff --git a/application/configs/conf.php b/application/configs/conf.php index f43dde788..74c3b10ac 100644 --- a/application/configs/conf.php +++ b/application/configs/conf.php @@ -1,14 +1,15 @@ 'www-data', -// *********************************************************************** + 'rabbitmq' => array("host" => "127.0.0.1", + "port" => "5672", + "user" => "guest", + "password" => "guest", + "vhost" => "/"), + + // *********************************************************************** // STOP CUSTOMIZING HERE // - // You don't need to touch anything below this point. + // You don't need to touch anything below this point. // *********************************************************************** 'baseFilesDir' => $baseFilesDir, @@ -46,6 +53,9 @@ $CC_CONFIG = array( 'apiKey' => $values['api_key'], 'apiPath' => '/api/', + 'soundcloud-client-id' => '2CLCxcSXYzx7QhhPVHN4A', + 'soundcloud-client-secret' => 'pZ7beWmF06epXLHVUP1ufOg2oEnIt9XhE8l8xt0bBs', + "rootDir" => __DIR__."/../..", 'pearPath' => dirname(__FILE__).'/../../library/pear', 'zendPath' => dirname(__FILE__).'/../../library/Zend', @@ -55,26 +65,26 @@ $CC_CONFIG = array( //'AdminsGr' => 'Admins', // name of station preferences group - 'StationPrefsGr'=> 'StationPrefs', +// 'StationPrefsGr'=> 'StationPrefs', // name of 'all users' group //'AllGr' => 'All', /* ==================================== application-specific configuration */ - 'objtypes' => array( - 'Storage' => array(/*'Folder',*/ 'File' /*, 'Replica'*/), - 'File' => array(), - 'audioclip' => array(), - 'playlist' => array(), - ), - 'allowedActions'=> array( - 'File' => array('editPrivs', 'write', 'read'), - 'audioclip' => array('editPrivs', 'write', 'read'), - 'playlist' => array('editPrivs', 'write', 'read'), - ), - 'allActions' => array( - 'editPrivs', 'write', 'read', 'subjects' - ), +// 'objtypes' => array( +// 'Storage' => array(/*'Folder',*/ 'File' /*, 'Replica'*/), +// 'File' => array(), +// 'audioclip' => array(), +// 'playlist' => array(), +// ), +// 'allowedActions'=> array( +// 'File' => array('editPrivs', 'write', 'read'), +// 'audioclip' => array('editPrivs', 'write', 'read'), +// 'playlist' => array('editPrivs', 'write', 'read'), +// ), +// 'allActions' => array( +// 'editPrivs', 'write', 'read', 'subjects' +// ), /* =================================================== cron configuration */ 'cronUserName' => 'www-data', @@ -82,7 +92,7 @@ $CC_CONFIG = array( 'lockfile' => dirname(__FILE__).'/stor/buffer/cron.lock', 'cronfile' => dirname(__FILE__).'/cron/croncall.php', 'paramdir' => dirname(__FILE__).'/cron/params', - 'systemPrefId' => "0", // ID for system prefs in prefs table +// 'systemPrefId' => "0", // ID for system prefs in prefs table ); // Add database table names @@ -94,10 +104,8 @@ $CC_CONFIG['permTable'] = $CC_CONFIG['tblNamePrefix'].'perms'; $CC_CONFIG['sessTable'] = $CC_CONFIG['tblNamePrefix'].'sess'; $CC_CONFIG['subjTable'] = $CC_CONFIG['tblNamePrefix'].'subjs'; $CC_CONFIG['smembTable'] = $CC_CONFIG['tblNamePrefix'].'smemb'; -$CC_CONFIG['transTable'] = $CC_CONFIG['tblNamePrefix'].'trans'; $CC_CONFIG['prefTable'] = $CC_CONFIG['tblNamePrefix'].'pref'; $CC_CONFIG['scheduleTable'] = $CC_CONFIG['tblNamePrefix'].'schedule'; -$CC_CONFIG['backupTable'] = $CC_CONFIG['tblNamePrefix'].'backup'; $CC_CONFIG['playListTimeView'] = $CC_CONFIG['tblNamePrefix'].'playlisttimes'; $CC_CONFIG['showSchedule'] = $CC_CONFIG['tblNamePrefix'].'show_schedule'; $CC_CONFIG['showDays'] = $CC_CONFIG['tblNamePrefix'].'show_days'; @@ -106,16 +114,15 @@ $CC_CONFIG['showInstances'] = $CC_CONFIG['tblNamePrefix'].'show_instances'; $CC_CONFIG['playListSequence'] = $CC_CONFIG['playListTable'].'_id'; $CC_CONFIG['filesSequence'] = $CC_CONFIG['filesTable'].'_id'; -$CC_CONFIG['transSequence'] = $CC_CONFIG['transTable'].'_id'; $CC_CONFIG['prefSequence'] = $CC_CONFIG['prefTable'].'_id'; $CC_CONFIG['permSequence'] = $CC_CONFIG['permTable'].'_id'; $CC_CONFIG['subjSequence'] = $CC_CONFIG['subjTable'].'_id'; $CC_CONFIG['smembSequence'] = $CC_CONFIG['smembTable'].'_id'; // System users/groups - they cannot be deleted -$CC_CONFIG['sysSubjs'] = array( - 'root', /*$CC_CONFIG['AdminsGr'],*/ /*$CC_CONFIG['AllGr'],*/ $CC_CONFIG['StationPrefsGr'] -); +//$CC_CONFIG['sysSubjs'] = array( +// 'root', /*$CC_CONFIG['AdminsGr'],*/ /*$CC_CONFIG['AllGr'],*/ $CC_CONFIG['StationPrefsGr'] +//); // Add libs to the PHP path $old_include_path = get_include_path(); @@ -125,9 +132,9 @@ set_include_path('.'.PATH_SEPARATOR.$CC_CONFIG['pearPath'] function load_airtime_config(){ $ini_array = parse_ini_file(dirname(__FILE__).'/../../build/airtime.conf', true); - + return array( - 'database' => array( + 'database' => array( 'username' => $ini_array['database']['dbuser'], 'password' => $ini_array['database']['dbpass'], 'hostspec' => $ini_array['database']['host'], diff --git a/application/configs/navigation.php b/application/configs/navigation.php index d696cc4b5..3c4f758f2 100644 --- a/application/configs/navigation.php +++ b/application/configs/navigation.php @@ -2,7 +2,7 @@ /* * Navigation container (config/array) - + * Each element in the array will be passed to * Zend_Navigation_Page::factory() when constructing * the navigation container below. @@ -16,7 +16,7 @@ $pages = array( 'resource' => 'nowplaying' ), array( - 'label' => 'Add Audio', + 'label' => 'Add Media', 'module' => 'default', 'controller' => 'Plupload', 'action' => 'plupload', @@ -51,7 +51,7 @@ $pages = array( 'module' => 'default', 'controller' => 'user', 'action' => 'add-user', - 'resource' => 'user' + 'resource' => 'user' ) ) ), @@ -64,10 +64,10 @@ $pages = array( ) ); - + // Create container from array $container = new Zend_Navigation($pages); $container->id = "nav"; - + //store it in the registry: Zend_Registry::set('Zend_Navigation', $container); diff --git a/application/controllers/ApiController.php b/application/controllers/ApiController.php index aa4696f89..7f1cf524e 100644 --- a/application/controllers/ApiController.php +++ b/application/controllers/ApiController.php @@ -6,8 +6,10 @@ class ApiController extends Zend_Controller_Action public function init() { /* Initialize action controller here */ - $ajaxContext = $this->_helper->getHelper('AjaxContext'); - $ajaxContext->addActionContext('version', 'json') + $context = $this->_helper->getHelper('contextSwitch'); + $context->addActionContext('version', 'json') + ->addActionContext('recorded-shows', 'json') + ->addActionContext('upload-recorded', 'json') ->initContext(); } @@ -24,7 +26,7 @@ class ApiController extends Zend_Controller_Action * in application/conf.php * * @return void - * + * */ public function versionAction() { @@ -101,6 +103,29 @@ class ApiController extends Zend_Controller_Action exit; } + public function liveInfoAction(){ + global $CC_CONFIG; + + // disable the view and the layout + $this->view->layout()->disableLayout(); + $this->_helper->viewRenderer->setNoRender(true); + + $result = Schedule::GetPlayOrderRange(0, 1); + + $date = new Application_Model_DateHelper; + $timeNow = $date->getDate(); + $result = array("env"=>APPLICATION_ENV, + "schedulerTime"=>gmdate("Y-m-d H:i:s"), + "currentShow"=>Show_DAL::GetCurrentShow($timeNow), + "nextShow"=>Show_DAL::GetNextShows($timeNow, 5), + "timezone"=> date("T"), + "timezoneOffset"=> date("Z")); + + //echo json_encode($result); + header("Content-type: text/javascript"); + echo $_GET['callback'].'('.json_encode($result).')'; + } + public function scheduleAction() { global $CC_CONFIG; @@ -110,6 +135,7 @@ class ApiController extends Zend_Controller_Action $this->_helper->viewRenderer->setNoRender(true); $api_key = $this->_getParam('api_key'); + if(!in_array($api_key, $CC_CONFIG["apiKey"])) { header('HTTP/1.0 401 Unauthorized'); @@ -119,18 +145,10 @@ class ApiController extends Zend_Controller_Action PEAR::setErrorHandling(PEAR_ERROR_RETURN); - $from = $this->_getParam("from"); - $to = $this->_getParam("to"); - if (Schedule::ValidPypoTimeFormat($from) && Schedule::ValidPypoTimeFormat($to)) { - $result = Schedule::ExportRangeAsJson($from, $to); - $result['stream_metadata'] = array(); - $result['stream_metadata']['format'] = Application_Model_Preference::GetStreamLabelFormat(); - $result['stream_metadata']['station_name'] = Application_Model_Preference::GetStationName(); - echo json_encode($result); - } + $result = Schedule::GetScheduledPlaylists(); + echo json_encode($result); } - public function notifyMediaItemStartPlayAction() { global $CC_CONFIG; @@ -197,5 +215,53 @@ class ApiController extends Zend_Controller_Action exit; } } + + public function recordedShowsAction() + { + global $CC_CONFIG; + + $api_key = $this->_getParam('api_key'); + if (!in_array($api_key, $CC_CONFIG["apiKey"])) + { + header('HTTP/1.0 401 Unauthorized'); + print 'You are not allowed to access this resource.'; + exit; + } + + $today_timestamp = date("Y-m-d H:i:s"); + $this->view->shows = Show::getShows($today_timestamp, null, $excludeInstance=NULL, $onlyRecord=TRUE); + } + + public function uploadRecordedAction() + { + global $CC_CONFIG; + + $api_key = $this->_getParam('api_key'); + if (!in_array($api_key, $CC_CONFIG["apiKey"])) + { + header('HTTP/1.0 401 Unauthorized'); + print 'You are not allowed to access this resource.'; + exit; + } + + $upload_dir = ini_get("upload_tmp_dir"); + $file = StoredFile::uploadFile($upload_dir); + + $show_instance = $this->_getParam('show_instance'); + + $show_inst = new ShowInstance($show_instance); + $show_inst->setRecordedFile($file->getId()); + + if(Application_Model_Preference::GetDoSoundCloudUpload()) + { + $show = new Show($show_inst->getShowId()); + $description = $show->getDescription(); + + $soundcloud = new ATSoundcloud(); + $soundcloud->uploadTrack($file->getRealFilePath(), $file->getName(), $description); + } + + $this->view->id = $file->getId(); + } } diff --git a/application/controllers/PluploadController.php b/application/controllers/PluploadController.php index fea649499..90687f477 100644 --- a/application/controllers/PluploadController.php +++ b/application/controllers/PluploadController.php @@ -11,144 +11,6 @@ class PluploadController extends Zend_Controller_Action ->initContext(); } - public function upload($targetDir) - { - // HTTP headers for no cache etc - header('Content-type: text/plain; charset=UTF-8'); - header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); - header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT"); - header("Cache-Control: no-store, no-cache, must-revalidate"); - header("Cache-Control: post-check=0, pre-check=0", false); - header("Pragma: no-cache"); - - // Settings - //$targetDir = ini_get("upload_tmp_dir"); //. DIRECTORY_SEPARATOR . "plupload"; - $cleanupTargetDir = false; // Remove old files - $maxFileAge = 60 * 60; // Temp file age in seconds - - // 5 minutes execution time - @set_time_limit(5 * 60); - // usleep(5000); - - // Get parameters - $chunk = isset($_REQUEST["chunk"]) ? $_REQUEST["chunk"] : 0; - $chunks = isset($_REQUEST["chunks"]) ? $_REQUEST["chunks"] : 0; - $fileName = isset($_REQUEST["name"]) ? $_REQUEST["name"] : ''; - - // Clean the fileName for security reasons - //$fileName = preg_replace('/[^\w\._]+/', '', $fileName); - - // Create target dir - if (!file_exists($targetDir)) - @mkdir($targetDir); - - // Remove old temp files - if (is_dir($targetDir) && ($dir = opendir($targetDir))) { - while (($file = readdir($dir)) !== false) { - $filePath = $targetDir . DIRECTORY_SEPARATOR . $file; - - // Remove temp files if they are older than the max age - if (preg_match('/\.tmp$/', $file) && (filemtime($filePath) < time() - $maxFileAge)) - @unlink($filePath); - } - - closedir($dir); - } else - die('{"jsonrpc" : "2.0", "error" : {"code": 100, "message": "Failed to open temp directory."}, "id" : "id"}'); - - // Look for the content type header - if (isset($_SERVER["HTTP_CONTENT_TYPE"])) - $contentType = $_SERVER["HTTP_CONTENT_TYPE"]; - - if (isset($_SERVER["CONTENT_TYPE"])) - $contentType = $_SERVER["CONTENT_TYPE"]; - - if (strpos($contentType, "multipart") !== false) { - if (isset($_FILES['file']['tmp_name']) && is_uploaded_file($_FILES['file']['tmp_name'])) { - // Open temp file - $out = fopen($targetDir . DIRECTORY_SEPARATOR . $fileName, $chunk == 0 ? "wb" : "ab"); - if ($out) { - // Read binary input stream and append it to temp file - $in = fopen($_FILES['file']['tmp_name'], "rb"); - - if ($in) { - while ($buff = fread($in, 4096)) - fwrite($out, $buff); - } else - die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": "Failed to open input stream."}, "id" : "id"}'); - - fclose($out); - unlink($_FILES['file']['tmp_name']); - } else - die('{"jsonrpc" : "2.0", "error" : {"code": 102, "message": "Failed to open output stream."}, "id" : "id"}'); - } else - die('{"jsonrpc" : "2.0", "error" : {"code": 103, "message": "Failed to move uploaded file."}, "id" : "id"}'); - } else { - // Open temp file - $out = fopen($targetDir . DIRECTORY_SEPARATOR . $fileName, $chunk == 0 ? "wb" : "ab"); - if ($out) { - // Read binary input stream and append it to temp file - $in = fopen("php://input", "rb"); - - if ($in) { - while ($buff = fread($in, 4096)) - fwrite($out, $buff); - } else - die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": "Failed to open input stream."}, "id" : "id"}'); - - fclose($out); - } else - die('{"jsonrpc" : "2.0", "error" : {"code": 102, "message": "Failed to open output stream."}, "id" : "id"}'); - } - - $audio_file = $targetDir . DIRECTORY_SEPARATOR . $fileName; - - $md5 = md5_file($audio_file); - $duplicate = StoredFile::RecallByMd5($md5); - if ($duplicate) { - if (PEAR::isError($duplicate)) { - die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": ' . $duplicate->getMessage() .'}}'); - } - else { - $duplicateName = $duplicate->getMetadataValue(UI_MDATA_KEY_TITLE); - die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": "An identical audioclip named ' . $duplicateName . ' already exists in the storage server."}}'); - } - } - - $metadata = Metadata::LoadFromFile($audio_file); - - if (PEAR::isError($metadata)) { - die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": ' + $metadata->getMessage() + '}}'); - } - - // #2196 no id tag -> use the original filename - if (basename($audio_file) == $metadata[UI_MDATA_KEY_TITLE]) { - $metadata[UI_MDATA_KEY_TITLE] = basename($audio_file); - $metadata[UI_MDATA_KEY_FILENAME] = basename($audio_file); - } - - // setMetadataBatch doesnt like these values - unset($metadata['audio']); - unset($metadata['playtime_seconds']); - - $values = array( - "filename" => basename($audio_file), - "filepath" => $audio_file, - "filetype" => "audioclip", - "mime" => $metadata[UI_MDATA_KEY_FORMAT], - "md5" => $md5 - ); - $storedFile = StoredFile::Insert($values); - - if (PEAR::isError($storedFile)) { - die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": ' + $storedFile->getMessage() + '}}'); - } - - $storedFile->setMetadataBatch($metadata); - - // Return JSON-RPC response - die('{"jsonrpc" : "2.0", "id" : '.$storedFile->getId().' }'); - } public function indexAction() { @@ -158,13 +20,9 @@ class PluploadController extends Zend_Controller_Action public function uploadAction() { $upload_dir = ini_get("upload_tmp_dir") . DIRECTORY_SEPARATOR . "plupload"; - $this->upload($upload_dir); - } + $file = StoredFile::uploadFile($upload_dir); - public function uploadRecordedAction() - { - $upload_dir = ini_get("upload_tmp_dir"); - $this->upload($upload_dir); + die('{"jsonrpc" : "2.0", "id" : '.$file->getId().' }'); } public function pluploadAction() diff --git a/application/controllers/PreferenceController.php b/application/controllers/PreferenceController.php index 1b8eb9e0c..61508bb1c 100644 --- a/application/controllers/PreferenceController.php +++ b/application/controllers/PreferenceController.php @@ -29,7 +29,11 @@ class PreferenceController extends Zend_Controller_Action $values = $form->getValues(); Application_Model_Preference::SetHeadTitle($values["stationName"], $this->view); Application_Model_Preference::SetDefaultFade($values["stationDefaultFade"]); - Application_Model_Preference::SetStreamLabelFormat($values["streamFormat"]); + Application_Model_Preference::SetStreamLabelFormat($values["streamFormat"]); + Application_Model_Preference::SetDoSoundCloudUpload($values["UseSoundCloud"]); + Application_Model_Preference::SetSoundCloudUser($values["SoundCloudUser"]); + Application_Model_Preference::SetSoundCloudPassword($values["SoundCloudPassword"]); + Application_Model_Preference::SetSoundCloudTags($values["SoundCloudTags"]); $this->view->statusMsg = "Preferences Updated."; } diff --git a/application/controllers/RecorderController.php b/application/controllers/RecorderController.php deleted file mode 100644 index 1aafbef15..000000000 --- a/application/controllers/RecorderController.php +++ /dev/null @@ -1,28 +0,0 @@ -_helper->getHelper('contextSwitch'); - $ajaxContext->addActionContext('get-show-schedule', 'json') - ->addActionContext('get-uploaded-file', 'json') - ->initContext(); - } - - public function indexAction() - { - // action body - } - - public function getShowScheduleAction() - { - $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/ScheduleController.php b/application/controllers/ScheduleController.php index db0ec0526..acbc7bc60 100644 --- a/application/controllers/ScheduleController.php +++ b/application/controllers/ScheduleController.php @@ -19,9 +19,9 @@ class ScheduleController extends Zend_Controller_Action ->addActionContext('schedule-show-dialog', 'json') ->addActionContext('show-content-dialog', 'json') ->addActionContext('clear-show', 'json') - ->addActionContext('get-current-playlist', 'json') + ->addActionContext('get-current-playlist', 'json') ->addActionContext('find-playlists', 'json') - ->addActionContext('remove-group', 'json') + ->addActionContext('remove-group', 'json') ->addActionContext('edit-show', 'json') ->addActionContext('add-show', 'json') ->addActionContext('cancel-show', 'json') @@ -64,7 +64,7 @@ class ScheduleController extends Zend_Controller_Action $formRepeats->removeDecorator('DtDdWrapper'); $formStyle->removeDecorator('DtDdWrapper'); $formRecord->removeDecorator('DtDdWrapper'); - + $this->view->what = $formWhat; $this->view->when = $formWhen; @@ -84,7 +84,7 @@ class ScheduleController extends Zend_Controller_Action { $start = $this->_getParam('start', null); $end = $this->_getParam('end', null); - + $userInfo = Zend_Auth::getInstance()->getStorage()->read(); $user = new User($userInfo->id); if($user->isAdmin()) @@ -111,6 +111,7 @@ class ScheduleController extends Zend_Controller_Action if(isset($error)) $this->view->error = $error; + } public function resizeShowAction() @@ -134,7 +135,7 @@ class ScheduleController extends Zend_Controller_Action public function deleteShowAction() { $showInstanceId = $this->_getParam('id'); - + $userInfo = Zend_Auth::getInstance()->getStorage()->read(); $user = new User($userInfo->id); @@ -158,41 +159,41 @@ class ScheduleController extends Zend_Controller_Action if (strtotime($today_timestamp) < strtotime($show->getShowStart())) { if (($user->isHost($show->getShowId()) || $user->isAdmin()) && !$show->isRecorded() && !$show->isRebroadcast()) { - - $menu[] = array('action' => array('type' => 'ajax', 'url' => '/Schedule/schedule-show-dialog'.$params, - 'callback' => 'window["buildScheduleDialog"]'), 'title' => 'Add Content'); - $menu[] = array('action' => array('type' => 'ajax', 'url' => '/Schedule/clear-show'.$params, + $menu[] = array('action' => array('type' => 'ajax', 'url' => '/Schedule/schedule-show-dialog'.$params, + 'callback' => 'window["buildScheduleDialog"]'), 'title' => 'Add / Remove Content'); + + $menu[] = array('action' => array('type' => 'ajax', 'url' => '/Schedule/clear-show'.$params, 'callback' => 'window["scheduleRefetchEvents"]'), 'title' => 'Remove All Content'); } } if(!$show->isRecorded()) { - $menu[] = array('action' => array('type' => 'ajax', 'url' => '/Schedule/show-content-dialog'.$params, + $menu[] = array('action' => array('type' => 'ajax', 'url' => '/Schedule/show-content-dialog'.$params, 'callback' => 'window["buildContentDialog"]'), 'title' => 'Show Content'); } - + if (strtotime($show->getShowStart()) <= strtotime($today_timestamp) && strtotime($today_timestamp) < strtotime($show->getShowEnd())) { $menu[] = array('action' => array('type' => 'fn', //'url' => '/Schedule/cancel-current-show'.$params, - 'callback' => "window['confirmCancelShow']($id)"), - 'title' => 'Cancel Current Show'); + 'callback' => "window['confirmCancelShow']($id)"), + 'title' => 'Cancel Current Show'); } - + if (strtotime($today_timestamp) < strtotime($show->getShowStart())) { if ($user->isAdmin()) { - $menu[] = array('action' => array('type' => 'ajax', 'url' => '/Schedule/delete-show'.$params, + $menu[] = array('action' => array('type' => 'ajax', 'url' => '/Schedule/delete-show'.$params, 'callback' => 'window["scheduleRefetchEvents"]'), 'title' => 'Delete This Instance'); - $menu[] = array('action' => array('type' => 'ajax', 'url' => '/Schedule/cancel-show'.$params, + $menu[] = array('action' => array('type' => 'ajax', 'url' => '/Schedule/cancel-show'.$params, 'callback' => 'window["scheduleRefetchEvents"]'), 'title' => 'Delete This Instance and All Following'); } } - + //returns format jjmenu is looking for. die(json_encode($menu)); } @@ -219,7 +220,7 @@ class ScheduleController extends Zend_Controller_Action $this->view->timeFilled = $show->getTimeScheduled(); $this->view->percentFilled = $show->getPercentScheduled(); - $this->view->chosen = $this->view->render('schedule/scheduled-content.phtml'); + $this->view->chosen = $this->view->render('schedule/scheduled-content.phtml'); unset($this->view->showContent); } @@ -242,7 +243,7 @@ class ScheduleController extends Zend_Controller_Action public function findPlaylistsAction() { $post = $this->getRequest()->getPost(); - + $show = new ShowInstance($this->sched_sess->showInstanceId); $playlists = $show->searchPlaylistsForShow($post); @@ -267,7 +268,7 @@ class ScheduleController extends Zend_Controller_Action $this->view->showContent = $show->getShowContent(); $this->view->timeFilled = $show->getTimeScheduled(); $this->view->percentFilled = $show->getPercentScheduled(); - $this->view->chosen = $this->view->render('schedule/scheduled-content.phtml'); + $this->view->chosen = $this->view->render('schedule/scheduled-content.phtml'); unset($this->view->showContent); } @@ -275,7 +276,7 @@ class ScheduleController extends Zend_Controller_Action { $showInstanceId = $this->_getParam('id'); $this->sched_sess->showInstanceId = $showInstanceId; - + $show = new ShowInstance($showInstanceId); $start_timestamp = $show->getShowStart(); $end_timestamp = $show->getShowEnd(); @@ -285,14 +286,14 @@ class ScheduleController extends Zend_Controller_Action $this->view->error = "cannot schedule an overlapping show."; return; } - + $start = explode(" ", $start_timestamp); $end = explode(" ", $end_timestamp); $startTime = explode(":", $start[1]); $endTime = explode(":", $end[1]); $dateInfo_s = getDate(strtotime($start_timestamp)); $dateInfo_e = getDate(strtotime($end_timestamp)); - + $this->view->showContent = $show->getShowContent(); $this->view->timeFilled = $show->getTimeScheduled(); $this->view->showName = $show->getName(); @@ -308,7 +309,7 @@ class ScheduleController extends Zend_Controller_Action $this->view->startTime = sprintf("%d:%02d", $startTime[0], $startTime[1]); $this->view->endTime = sprintf("%d:%02d", $endTime[0], $endTime[1]); - $this->view->chosen = $this->view->render('schedule/scheduled-content.phtml'); + $this->view->chosen = $this->view->render('schedule/scheduled-content.phtml'); $this->view->dialog = $this->view->render('schedule/schedule-show-dialog.phtml'); unset($this->view->showContent); } @@ -335,7 +336,7 @@ class ScheduleController extends Zend_Controller_Action { $js = $this->_getParam('data'); $data = array(); - + //need to convert from serialized jQuery array. foreach($js as $j){ $data[$j["name"]] = $j["value"]; @@ -392,8 +393,8 @@ class ScheduleController extends Zend_Controller_Action $rebroadAb = $formAbsoluteRebroadcast->isValid($data); $rebroad = $formRebroadcast->isValid($data); - if ($what && $when && $repeats && $who && $style && $record && $rebroadAb && $rebroad) { - + if ($what && $when && $repeats && $who && $style && $record && $rebroadAb && $rebroad) { + $userInfo = Zend_Auth::getInstance()->getStorage()->read(); $user = new User($userInfo->id); if($user->isAdmin()) { @@ -413,7 +414,7 @@ class ScheduleController extends Zend_Controller_Action $formRecord->reset(); $formAbsoluteRebroadcast->reset(); $formRebroadcast->reset(); - + $this->view->newForm = $this->view->render('schedule/add-show-form.phtml'); } else { @@ -426,7 +427,7 @@ class ScheduleController extends Zend_Controller_Action { $userInfo = Zend_Auth::getInstance()->getStorage()->read(); $user = new User($userInfo->id); - + if($user->isAdmin()) { $showInstanceId = $this->_getParam('id'); @@ -434,14 +435,14 @@ class ScheduleController extends Zend_Controller_Action $show = new Show($showInstance->getShowId()); $show->cancelShow($showInstance->getShowStart()); - } + } } public function cancelCurrentShowAction() { $userInfo = Zend_Auth::getInstance()->getStorage()->read(); $user = new User($userInfo->id); - + if($user->isAdmin()) { $showInstanceId = $this->_getParam('id'); $show = new ShowInstance($showInstanceId); diff --git a/application/controllers/UserController.php b/application/controllers/UserController.php index 72b6a20ef..081c32b70 100644 --- a/application/controllers/UserController.php +++ b/application/controllers/UserController.php @@ -23,6 +23,8 @@ class UserController extends Zend_Controller_Action $this->view->headScript()->appendFile('/js/airtime/user/user.js','text/javascript'); $request = $this->getRequest(); $form = new Application_Form_AddUser(); + + $this->view->successMessage = ""; if ($request->isPost()) { if ($form->isValid($request->getPost())) { @@ -42,6 +44,12 @@ class UserController extends Zend_Controller_Action $user->save(); $form->reset(); + + if (strlen($formdata['user_id']) == 0){ + $this->view->successMessage = "
User added successfully!
"; + } else { + $this->view->successMessage = "
User updated successfully!
"; + } } } } diff --git a/application/controllers/plugins/Acl_plugin.php b/application/controllers/plugins/Acl_plugin.php index a1a641ce4..b380478e7 100644 --- a/application/controllers/plugins/Acl_plugin.php +++ b/application/controllers/plugins/Acl_plugin.php @@ -110,7 +110,7 @@ class Zend_Controller_Plugin_Acl extends Zend_Controller_Plugin_Abstract { $controller = strtolower($request->getControllerName()); - if ($controller == 'api' || $controller == 'recorder' || $controller == 'plupload' && $request->getActionName() == 'upload-recorded'){ + if ($controller == 'api'){ $this->setRoleName("G"); } diff --git a/application/controllers/plugins/RabbitMqPlugin.php b/application/controllers/plugins/RabbitMqPlugin.php new file mode 100644 index 000000000..3316f01b2 --- /dev/null +++ b/application/controllers/plugins/RabbitMqPlugin.php @@ -0,0 +1,9 @@ + 'form/add-show-rebroadcast.phtml')) )); + + $relativeDates = array(); + $relativeDates[""] = ""; + for($i=0; $i <=30; $i++) { + $relativeDates["$i days"] = "+$i days"; + } + //Add date select $this->addElement('select', 'add_show_rebroadcast_date_1', array( 'required' => false, 'class' => ' input_select', - 'multiOptions' => array( - "" => "", - "0 days" => "+0 days", - "1 day" => "+1 day", - "2 days" => "+2 days", - "3 days" => "+3 days" - ), + 'multiOptions' => $relativeDates, 'decorators' => array( 'ViewHelper' ) @@ -44,13 +45,7 @@ class Application_Form_AddShowRebroadcastDates extends Zend_Form_SubForm $this->addElement('select', 'add_show_rebroadcast_date_2', array( 'required' => false, 'class' => ' input_select', - 'multiOptions' => array( - "" => "", - "0 days" => "+0 days", - "1 day" => "+1 day", - "2 days" => "+2 days", - "3 days" => "+3 days" - ), + 'multiOptions' => $relativeDates, 'decorators' => array( 'ViewHelper' ) @@ -75,13 +70,7 @@ class Application_Form_AddShowRebroadcastDates extends Zend_Form_SubForm $this->addElement('select', 'add_show_rebroadcast_date_3', array( 'required' => false, 'class' => ' input_select', - 'multiOptions' => array( - "" => "", - "0 days" => "+0 days", - "1 day" => "+1 day", - "2 days" => "+2 days", - "3 days" => "+3 days" - ), + 'multiOptions' => $relativeDates, 'decorators' => array( 'ViewHelper' ) @@ -106,13 +95,7 @@ class Application_Form_AddShowRebroadcastDates extends Zend_Form_SubForm $this->addElement('select', 'add_show_rebroadcast_date_4', array( 'required' => false, 'class' => ' input_select', - 'multiOptions' => array( - "" => "", - "0 days" => "+0 days", - "1 day" => "+1 day", - "2 days" => "+2 days", - "3 days" => "+3 days" - ), + 'multiOptions' => $relativeDates, 'decorators' => array( 'ViewHelper' ) @@ -137,13 +120,7 @@ class Application_Form_AddShowRebroadcastDates extends Zend_Form_SubForm $this->addElement('select', 'add_show_rebroadcast_date_5', array( 'required' => false, 'class' => ' input_select', - 'multiOptions' => array( - "" => "", - "0 days" => "+0 days", - "1 day" => "+1 day", - "2 days" => "+2 days", - "3 days" => "+3 days" - ), + 'multiOptions' => $relativeDates, 'decorators' => array( 'ViewHelper' ) diff --git a/application/forms/AddShowWhat.php b/application/forms/AddShowWhat.php index 7fafdb46e..74e7b0dcc 100644 --- a/application/forms/AddShowWhat.php +++ b/application/forms/AddShowWhat.php @@ -16,7 +16,7 @@ class Application_Form_AddShowWhat extends Zend_Form_SubForm // Add URL element $this->addElement('text', 'add_show_url', array( - 'label' => 'Show URL:', + 'label' => 'Website:', 'class' => 'input_text', 'required' => false, 'filters' => array('StringTrim'), diff --git a/application/forms/AddUser.php b/application/forms/AddUser.php index e1f0453a7..58bc91543 100644 --- a/application/forms/AddUser.php +++ b/application/forms/AddUser.php @@ -68,12 +68,13 @@ class Application_Form_AddUser extends Zend_Form $this->addElement($jabber); $select = new Zend_Form_Element_Select('type'); + $select->setLabel('User Type:'); $select->setAttrib('class', 'input_select'); $select->setAttrib('style', 'width: 40%'); $select->setMultiOptions(array( - "A" => "admin", - "H" => "host", - "G" => "guest", + "G" => "Guest", + "H" => "Host", + "A" => "Admin" )); $select->setRequired(true); $this->addElement($select); diff --git a/application/forms/Preferences.php b/application/forms/Preferences.php index 6b6903936..2e770a9a6 100644 --- a/application/forms/Preferences.php +++ b/application/forms/Preferences.php @@ -7,7 +7,7 @@ class Application_Form_Preferences extends Zend_Form { $this->setAction('/Preference/update')->setMethod('post'); - // Add login element + //Station name $this->addElement('text', 'stationName', array( 'class' => 'input_text', 'label' => 'Station Name:', @@ -17,12 +17,12 @@ class Application_Form_Preferences extends Zend_Form 'value' => Application_Model_Preference::GetValue("station_name") )); - $defaultFade = Application_Model_Preference::GetValue("default_fade"); + $defaultFade = Application_Model_Preference::GetDefaultFade(); if($defaultFade == ""){ $defaultFade = '00:00:00.000000'; } - // Add login element + //Default station fade $this->addElement('text', 'stationDefaultFade', array( 'class' => 'input_text', 'label' => 'Default Fade:', @@ -42,11 +42,46 @@ class Application_Form_Preferences extends Zend_Form $stream_format->setValue(Application_Model_Preference::GetStreamLabelFormat()); $this->addElement($stream_format); + + $this->addElement('checkbox', 'UseSoundCloud', array( + 'label' => 'Automatically Upload Recorded Shows To SoundCloud', + 'required' => false, + 'value' => Application_Model_Preference::GetDoSoundCloudUpload() + )); + + //SoundCloud Username + $this->addElement('text', 'SoundCloudUser', array( + 'class' => 'input_text', + 'label' => 'SoundCloud Email:', + 'required' => false, + 'filters' => array('StringTrim'), + 'value' => Application_Model_Preference::GetSoundCloudUser() + )); + + //SoundCloud Password + $this->addElement('text', 'SoundCloudPassword', array( + 'class' => 'input_text', + 'label' => 'SoundCloud Password:', + 'required' => false, + 'filters' => array('StringTrim'), + 'value' => Application_Model_Preference::GetSoundCloudPassword() + )); + + // Add the description element + $this->addElement('textarea', 'SoundCloudTags', array( + 'label' => 'space separated SoundCloud Tags', + 'required' => false, + 'class' => 'input_text_area', + 'value' => Application_Model_Preference::GetSoundCloudTags() + )); + $this->addElement('submit', 'submit', array( 'class' => 'ui-button ui-state-default', 'ignore' => true, 'label' => 'Submit', )); + + } } diff --git a/application/models/DateHelper.php b/application/models/DateHelper.php index 640e5cd6a..d0f56f647 100644 --- a/application/models/DateHelper.php +++ b/application/models/DateHelper.php @@ -2,61 +2,85 @@ class Application_Model_DateHelper { - private $_timestamp; - - function __construct() { + private $_timestamp; + + function __construct() + { $this->_timestamp = date("U"); - } - - function getDate(){ - return date("Y-m-d H:i:s", $this->_timestamp); - } - - function getTime(){ - return date("H:i:s", $this->_timestamp); - } - - function setDate($dateString){ + } + + /** + * Get time of object construction in the format + * YYYY-MM-DD HH:mm:ss + */ + function getDate() + { + return date("Y-m-d H:i:s", $this->_timestamp); + } + + /** + * Get time of object construction in the format + * HH:mm:ss + */ + function getTime() + { + return date("H:i:s", $this->_timestamp); + } + + /** + * Set the internal timestamp of the object. + */ + function setDate($dateString) + { $this->_timestamp = strtotime($dateString); - } - - function getNowDayStartDiff(){ - $dayStartTS = strtotime(date("Y-m-d", $this->_timestamp)); - return $this->_timestamp - $dayStartTS; - } + } - function getNowDayEndDiff(){ - $dayEndTS = strtotime(date("Y-m-d", $this->_timestamp+(86400))); - return $dayEndTS - $this->_timestamp; - } + /** + * + * Enter description here ... + */ + function getNowDayStartDiff() + { + $dayStartTS = strtotime(date("Y-m-d", $this->_timestamp)); + return $this->_timestamp - $dayStartTS; + } - function getEpochTime(){ + function getNowDayEndDiff() + { + $dayEndTS = strtotime(date("Y-m-d", $this->_timestamp+(86400))); + return $dayEndTS - $this->_timestamp; + } + + function getEpochTime() + { return $this->_timestamp; - } + } - public static function TimeDiff($time1, $time2){ + public static function TimeDiff($time1, $time2) + { return strtotime($time2) - strtotime($time1); - } - - public static function ConvertMSToHHMMSSmm($time){ + } + + public static function ConvertMSToHHMMSSmm($time) + { $hours = floor($time / 3600000); $time -= 3600000*$hours; - + $minutes = floor($time / 60000); $time -= 60000*$minutes; - + $seconds = floor($time / 1000); $time -= 1000*$seconds; - + $ms = $time; - + if (strlen($hours) == 1) - $hours = "0".$hours; + $hours = "0".$hours; if (strlen($minutes) == 1) - $minutes = "0".$minutes; + $minutes = "0".$minutes; if (strlen($seconds) == 1) - $seconds = "0".$seconds; - + $seconds = "0".$seconds; + return $hours.":".$minutes.":".$seconds.".".$ms; } } diff --git a/application/models/Preference.php b/application/models/Preference.php index 5b2f80cfa..948b29645 100644 --- a/application/models/Preference.php +++ b/application/models/Preference.php @@ -8,30 +8,30 @@ class Application_Model_Preference $auth = Zend_Auth::getInstance(); $id = $auth->getIdentity()->id; - + //Check if key already exists $sql = "SELECT COUNT(*) FROM cc_pref" ." WHERE keystr = '$key'"; $result = $CC_DBC->GetOne($sql); - + if ($result == 1){ $sql = "UPDATE cc_pref" ." SET subjid = $id, valstr = '$value'" - ." WHERE keystr = '$key'"; + ." WHERE keystr = '$key'"; } else { $sql = "INSERT INTO cc_pref (subjid, keystr, valstr)" ." VALUES ($id, '$key', '$value')"; } return $CC_DBC->query($sql); } - + public static function GetValue($key){ global $CC_CONFIG, $CC_DBC; //Check if key already exists $sql = "SELECT COUNT(*) FROM cc_pref" ." WHERE keystr = '$key'"; $result = $CC_DBC->GetOne($sql); - + if ($result == 0) return ""; else { @@ -40,9 +40,9 @@ class Application_Model_Preference $result = $CC_DBC->GetOne($sql); return $result; } - + } - + public static function GetHeadTitle(){ /* Caches the title name as a session variable so we dont access * the database on every page load. */ @@ -55,31 +55,32 @@ class Application_Model_Preference } if (strlen($title) > 0) $title .= " - "; - + return $title."Airtime"; } - + public static function SetHeadTitle($title, $view){ - Application_Model_Preference::SetValue("station_name", $title); - $defaultNamespace = new Zend_Session_Namespace('title_name'); + Application_Model_Preference::SetValue("station_name", $title); + $defaultNamespace = new Zend_Session_Namespace('title_name'); $defaultNamespace->title = $title; - + RabbitMq::PushSchedule(); + //set session variable to new station name so that html title is updated. //should probably do this in a view helper to keep this controller as minimal as possible. $view->headTitle()->exchangeArray(array()); //clear headTitle ArrayObject $view->headTitle(Application_Model_Preference::GetHeadTitle()); } - public static function SetShowsPopulatedUntil($timestamp) { - Application_Model_Preference::SetValue("shows_populated_until", $timestamp); + public static function SetShowsPopulatedUntil($timestamp) { + Application_Model_Preference::SetValue("shows_populated_until", $timestamp); } public static function GetShowsPopulatedUntil() { return Application_Model_Preference::GetValue("shows_populated_until"); } - public static function SetDefaultFade($fade) { - Application_Model_Preference::SetValue("default_fade", $fade); + public static function SetDefaultFade($fade) { + Application_Model_Preference::SetValue("default_fade", $fade); } public static function GetDefaultFade() { @@ -88,6 +89,7 @@ class Application_Model_Preference public static function SetStreamLabelFormat($type){ Application_Model_Preference::SetValue("stream_label_format", $type); + RabbitMq::PushSchedule(); } public static function GetStreamLabelFormat(){ @@ -97,5 +99,38 @@ class Application_Model_Preference public static function GetStationName(){ return Application_Model_Preference::getValue("station_name"); } + + public static function SetDoSoundCloudUpload($upload) { + Application_Model_Preference::SetValue("soundcloud_upload", $upload); + } + + public static function GetDoSoundCloudUpload() { + return Application_Model_Preference::GetValue("soundcloud_upload"); + } + + public static function SetSoundCloudUser($user) { + Application_Model_Preference::SetValue("soundcloud_user", $user); + } + + public static function GetSoundCloudUser() { + return Application_Model_Preference::GetValue("soundcloud_user"); + } + + public static function SetSoundCloudPassword($password) { + Application_Model_Preference::SetValue("soundcloud_password", $password); + } + + public static function GetSoundCloudPassword() { + return Application_Model_Preference::GetValue("soundcloud_password"); + } + + public static function SetSoundCloudTags($tags) { + Application_Model_Preference::SetValue("soundcloud_tags", $tags); + } + + public static function GetSoundCloudTags() { + return Application_Model_Preference::GetValue("soundcloud_tags"); + } + } diff --git a/application/models/RabbitMq.php b/application/models/RabbitMq.php new file mode 100644 index 000000000..1672dd4e2 --- /dev/null +++ b/application/models/RabbitMq.php @@ -0,0 +1,44 @@ +channel(); + $channel->access_request($CC_CONFIG["rabbitmq"]["vhost"], false, false, true, true); + + $EXCHANGE = 'airtime-schedule'; + $channel->exchange_declare($EXCHANGE, 'direct', false, true); + + $data = json_encode(Schedule::GetScheduledPlaylists()); + $msg = new AMQPMessage($data, array('content_type' => 'text/plain')); + + $channel->basic_publish($msg, $EXCHANGE); + $channel->close(); + $conn->close(); + } + } + +} + diff --git a/application/models/Schedule.php b/application/models/Schedule.php index 5fe66f2ca..d1d556e1f 100644 --- a/application/models/Schedule.php +++ b/application/models/Schedule.php @@ -24,41 +24,6 @@ class ScheduleGroup { return $result != "0"; } - /** - * Convert a date to an ID by stripping out all characters - * and padding with zeros. - * - * @param string $p_dateStr - */ - public static function dateToId($p_dateStr) { - $p_dateStr = str_replace(":", "", $p_dateStr); - $p_dateStr = str_replace(" ", "", $p_dateStr); - $p_dateStr = str_replace(".", "", $p_dateStr); - $p_dateStr = str_replace("-", "", $p_dateStr); - $p_dateStr = substr($p_dateStr, 0, 17); - $p_dateStr = str_pad($p_dateStr, 17, "0"); - return $p_dateStr; - } - - /** - * Add the two times together, return the result. - * - * @param string $p_baseTime - * Specified as YYYY-MM-DD HH:MM:SS - * - * @param string $p_addTime - * Specified as HH:MM:SS.nnnnnn - * - * @return string - * The end time, to the nearest second. - */ - // protected function calculateEndTime($p_startTime, $p_trackTime) { - // $p_trackTime = substr($p_startTime, 0, ); - // $start = new DateTime(); - // $interval = new DateInterval() - // - // } - /** * Add a music clip or playlist to the schedule. * @@ -77,6 +42,7 @@ class ScheduleGroup { */ public function add($show_instance, $p_datetime, $p_audioFileId = null, $p_playlistId = null, $p_options = null) { global $CC_CONFIG, $CC_DBC; + if (!is_null($p_audioFileId)) { // Schedule a single audio track @@ -92,27 +58,24 @@ class ScheduleGroup { if (empty($length)) { return new PEAR_Error("Length is empty."); } - if (!Schedule::isScheduleEmptyInRange($p_datetime, $length)) { - return new PEAR_Error("Schedule conflict.", 555); - } - + // Insert into the table $this->groupId = $CC_DBC->GetOne("SELECT nextval('schedule_group_id_seq')"); - $id = $this->dateToId($p_datetime); + $sql = "INSERT INTO ".$CC_CONFIG["scheduleTable"] - ." (playlist_id, starts, ends, clip_length, group_id, file_id)" - ." VALUES (0, TIMESTAMP '$p_datetime', " + ." (instance_id, starts, ends, clip_length, group_id, file_id, cue_out)" + ." VALUES ($show_instance, TIMESTAMP '$p_datetime', " ." (TIMESTAMP '$p_datetime' + INTERVAL '$length')," ." '$length'," - ." {$this->groupId}, $p_audioFileId)"; + ." {$this->groupId}, $p_audioFileId, '$length')"; $result = $CC_DBC->query($sql); if (PEAR::isError($result)) { //var_dump($sql); return $result; } - return $this->groupId; - } elseif (!is_null($p_playlistId)){ + } + elseif (!is_null($p_playlistId)){ // Schedule a whole playlist // Load existing playlist @@ -130,7 +93,6 @@ class ScheduleGroup { // Insert all items into the schedule $this->groupId = $CC_DBC->GetOne("SELECT nextval('schedule_group_id_seq')"); - $id = $this->dateToId($p_datetime); $itemStartTime = $p_datetime; $plItems = $playlist->getContents(); @@ -151,13 +113,13 @@ class ScheduleGroup { return $result; } $itemStartTime = $CC_DBC->getOne("SELECT TIMESTAMP '$itemStartTime' + INTERVAL '$trackLength'"); - $id = $this->dateToId($itemStartTime); } - return $this->groupId; } + RabbitMq::PushSchedule(); + return $this->groupId; } - public function addAfter($show_instance, $p_groupId, $p_audioFileId) { + public function addFileAfter($show_instance, $p_groupId, $p_audioFileId) { global $CC_CONFIG, $CC_DBC; // Get the end time for the given entry $sql = "SELECT MAX(ends) FROM ".$CC_CONFIG["scheduleTable"] @@ -176,10 +138,6 @@ class ScheduleGroup { return $this->add($show_instance, $startTime, null, $p_playlistId); } - public function update() { - - } - /** * Remove the group from the schedule. * Note: does not check if it is in the past, you can remove anything. @@ -195,7 +153,9 @@ class ScheduleGroup { $sql = "DELETE FROM ".$CC_CONFIG["scheduleTable"] ." WHERE group_id = ".$this->groupId; //echo $sql; - return $CC_DBC->query($sql); + $retVal = $CC_DBC->query($sql); + RabbitMq::PushSchedule(); + return $retVal; } /** @@ -231,17 +191,13 @@ class ScheduleGroup { return $CC_DBC->GetAll($sql); } - public function reschedule($toDateTime) { - global $CC_CONFIG, $CC_DBC; - // $sql = "UPDATE ".$CC_CONFIG["scheduleTable"]. " SET id=, starts=,ends=" - } - public function notifyGroupStartPlay() { global $CC_CONFIG, $CC_DBC; $sql = "UPDATE ".$CC_CONFIG['scheduleTable'] ." SET schedule_group_played=TRUE" ." WHERE group_id=".$this->groupId; - return $CC_DBC->query($sql); + $retVal = $CC_DBC->query($sql); + return $retVal; } public function notifyMediaItemStartPlay($p_fileId) { @@ -250,7 +206,8 @@ class ScheduleGroup { ." SET media_item_played=TRUE" ." WHERE group_id=".$this->groupId ." AND file_id=".pg_escape_string($p_fileId); - return $CC_DBC->query($sql); + $retVal = $CC_DBC->query($sql); + return $retVal; } } @@ -334,9 +291,10 @@ class Schedule { return $res; } - public static function GetPercentScheduled($instance_id, $s_datetime, $e_datetime){ + public static function GetPercentScheduled($instance_id, $s_datetime, $e_datetime) + { $time = Schedule::GetTotalShowTime($instance_id); - + $s_epoch = strtotime($s_datetime); $e_epoch = strtotime($e_datetime); @@ -396,12 +354,14 @@ class Schedule { * @return array * Returns empty array if nothing found */ - public static function GetItems($p_fromDateTime, $p_toDateTime, $p_playlistsOnly = true) { + + public static function GetItems($p_currentDateTime, $p_toDateTime, $p_playlistsOnly = true) + { global $CC_CONFIG, $CC_DBC; $rows = array(); if (!$p_playlistsOnly) { $sql = "SELECT * FROM ".$CC_CONFIG["scheduleTable"] - ." WHERE (starts >= TIMESTAMP '$p_fromDateTime') " + ." WHERE (starts >= TIMESTAMP '$p_currentDateTime') " ." AND (ends <= TIMESTAMP '$p_toDateTime')"; $rows = $CC_DBC->GetAll($sql); foreach ($rows as &$row) { @@ -429,11 +389,11 @@ class Schedule { ." ON st.instance_id = si.id" ." LEFT JOIN $CC_CONFIG[showTable] as sh" ." ON si.show_id = sh.id" - ." WHERE (st.starts >= TIMESTAMP '$p_fromDateTime')" + ." WHERE (st.ends >= TIMESTAMP '$p_currentDateTime')" ." AND (st.ends <= TIMESTAMP '$p_toDateTime')" //next line makes sure that we aren't returning items that //are past the show's scheduled timeslot. - ." AND (st.starts < si.ends)" + ." AND (st.starts < si.ends)" ." GROUP BY st.group_id" ." ORDER BY starts"; @@ -457,7 +417,8 @@ class Schedule { * @param int $next * @return date */ - public static function GetPlayOrderRange($prev = 1, $next = 1) { + public static function GetPlayOrderRange($prev = 1, $next = 1) + { if (!is_int($prev) || !is_int($next)){ //must enter integers to specify ranges return array(); @@ -469,11 +430,11 @@ class Schedule { $timeNow = $date->getDate(); return array("env"=>APPLICATION_ENV, "schedulerTime"=>gmdate("Y-m-d H:i:s"), - "previous"=>Schedule::Get_Scheduled_Item_Data($timeNow, -1, $prev, "24 hours"), - "current"=>Schedule::Get_Scheduled_Item_Data($timeNow, 0), - "next"=>Schedule::Get_Scheduled_Item_Data($timeNow, 1, $next, "48 hours"), + "previous"=>Schedule::GetScheduledItemData($timeNow, -1, $prev, "24 hours"), + "current"=>Schedule::GetScheduledItemData($timeNow, 0), + "next"=>Schedule::GetScheduledItemData($timeNow, 1, $next, "48 hours"), "currentShow"=>Show_DAL::GetCurrentShow($timeNow), - "nextShow"=>Show_DAL::GetNextShow($timeNow), + "nextShow"=>Show_DAL::GetNextShows($timeNow, 1), "timezone"=> date("T"), "timezoneOffset"=> date("Z"), "apiKey"=>$CC_CONFIG['apiKey'][0]); @@ -501,7 +462,8 @@ class Schedule { * want to search the database. For example "5 days", "18 hours", "60 minutes", * "30 seconds" etc. */ - public static function Get_Scheduled_Item_Data($timeStamp, $timePeriod=0, $count = 0, $interval="0 hours"){ + public static function GetScheduledItemData($timeStamp, $timePeriod=0, $count = 0, $interval="0 hours") + { global $CC_CONFIG, $CC_DBC; $sql = "SELECT DISTINCT pt.name, ft.track_title, ft.artist_name, ft.album_title, st.starts, st.ends, st.clip_length, st.media_item_played, st.group_id, show.name as show_name, st.instance_id" @@ -531,7 +493,8 @@ class Schedule { return $rows; } - public static function GetShowInstanceItems($instance_id){ + public static function GetShowInstanceItems($instance_id) + { global $CC_CONFIG, $CC_DBC; $sql = "SELECT DISTINCT pt.name, ft.track_title, ft.artist_name, ft.album_title, st.starts, st.ends, st.clip_length, st.media_item_played, st.group_id, show.name as show_name, st.instance_id" @@ -547,12 +510,14 @@ class Schedule { return $rows; } - public static function UpdateMediaPlayedStatus($id){ + public static function UpdateMediaPlayedStatus($p_id) + { global $CC_CONFIG, $CC_DBC; $sql = "UPDATE ".$CC_CONFIG['scheduleTable'] ." SET media_item_played=TRUE" - ." WHERE id=$id"; - return $CC_DBC->query($sql); + ." WHERE id=$p_id"; + $retVal = $CC_DBC->query($sql); + return $retVal; } @@ -563,7 +528,7 @@ class Schedule { * @param string $p_time * @return string */ - private static function CcTimeToPypoTime($p_time) + private static function AirtimeTimeToPypoTime($p_time) { $p_time = substr($p_time, 0, 19); $p_time = str_replace(" ", "-", $p_time); @@ -578,7 +543,7 @@ class Schedule { * @param string $p_time * @return string */ - private static function PypoTimeToCcTime($p_time) + private static function PypoTimeToAirtimeTime($p_time) { $t = explode("-", $p_time); return $t[0]."-".$t[1]."-".$t[2]." ".$t[3].":".$t[4].":00"; @@ -658,17 +623,21 @@ class Schedule { /** * Export the schedule in json formatted for pypo (the liquidsoap scheduler) * - * @param string $range - * In the format "YYYY-MM-DD HH:mm:ss" - * @param string $source - * In the format "YYYY-MM-DD HH:mm:ss" + * @param string $p_fromDateTime + * In the format "YYYY-MM-DD-HH-mm-SS" + * @param string $p_toDateTime + * In the format "YYYY-MM-DD-HH-mm-SS" */ - public static function ExportRangeAsJson($p_fromDateTime, $p_toDateTime) + public static function GetScheduledPlaylists() { global $CC_CONFIG, $CC_DBC; - $range_start = Schedule::PypoTimeToCcTime($p_fromDateTime); - $range_end = Schedule::PypoTimeToCcTime($p_toDateTime); + $t1 = new DateTime(); + $range_start = $t1->format("Y-m-d H:i:s"); + + $t2 = new DateTime(); + $t2->add(new DateInterval("PT24H")); + $range_end = $t2->format("Y-m-d H:i:s"); // Scheduler wants everything in a playlist $data = Schedule::GetItems($range_start, $range_end, true); @@ -684,10 +653,10 @@ class Schedule { $start = substr($start, 0, 19); //Start time is the array key, needs to be in the format "YYYY-MM-DD-HH-mm-ss" - $pkey = Schedule::CcTimeToPypoTime($start); + $pkey = Schedule::AirtimeTimeToPypoTime($start); $timestamp = strtotime($start); $playlists[$pkey]['source'] = "PLAYLIST"; - $playlists[$pkey]['x_ident'] = $dx["playlist_id"]; + $playlists[$pkey]['x_ident'] = $dx['group_id']; $playlists[$pkey]['subtype'] = '1'; // Just needs to be between 1 and 4 inclusive $playlists[$pkey]['timestamp'] = $timestamp; $playlists[$pkey]['duration'] = $dx['clip_length']; @@ -695,9 +664,9 @@ class Schedule { $playlists[$pkey]['schedule_id'] = $dx['group_id']; $playlists[$pkey]['show_name'] = $dx['show_name']; $playlists[$pkey]['user_id'] = 0; - $playlists[$pkey]['id'] = $dx["playlist_id"]; - $playlists[$pkey]['start'] = Schedule::CcTimeToPypoTime($dx["start"]); - $playlists[$pkey]['end'] = Schedule::CcTimeToPypoTime($dx["end"]); + $playlists[$pkey]['id'] = $dx['group_id']; + $playlists[$pkey]['start'] = Schedule::AirtimeTimeToPypoTime($dx["start"]); + $playlists[$pkey]['end'] = Schedule::AirtimeTimeToPypoTime($dx["end"]); } } @@ -734,30 +703,15 @@ class Schedule { $result = array(); $result['status'] = array('range' => array('start' => $range_start, 'end' => $range_end), - 'version' => "1.1"); + 'version' => AIRTIME_REST_VERSION); $result['playlists'] = $playlists; $result['check'] = 1; + $result['stream_metadata'] = array(); + $result['stream_metadata']['format'] = Application_Model_Preference::GetStreamLabelFormat(); + $result['stream_metadata']['station_name'] = Application_Model_Preference::GetStationName(); + $result['server_timezone'] = date('O'); return $result; } - - - /** - * Remove all items from the schedule in the given range. - * - * @param string $p_start - * In the format YYYY-MM-DD HH:MM:SS.nnnnnn - * @param string $p_end - * In the format YYYY-MM-DD HH:MM:SS.nnnnnn - */ - public static function RemoveItemsInRange($p_start, $p_end) - { - $items = Schedule::GetItems($p_start, $p_end); - foreach ($items as $item) { - $scheduleGroup = new ScheduleGroup($item["group_id"]); - $scheduleGroup->remove(); - } - } - } diff --git a/application/models/Shows.php b/application/models/Shows.php index 959a6bf00..8aa83beae 100644 --- a/application/models/Shows.php +++ b/application/models/Shows.php @@ -6,50 +6,60 @@ class Show { public function __construct($showId=NULL) { - $this->_showId = $showId; + $this->_showId = $showId; } - public function getName() { + public function getName() + { $show = CcShowQuery::create()->findPK($this->_showId); return $show->getDbName(); } - - public function setName($name) { + + public function setName($name) + { $show = CcShowQuery::create()->findPK($this->_showId); $show->setDbName($name); + RabbitMq::PushSchedule(); } - public function getDescription() { + public function getDescription() + { $show = CcShowQuery::create()->findPK($this->_showId); return $show->getDbDescription(); } - - public function setDescription($description) { + + public function setDescription($description) + { $show = CcShowQuery::create()->findPK($this->_showId); $show->setDbDescription($description); } - public function getColor() { + public function getColor() + { $show = CcShowQuery::create()->findPK($this->_showId); return $show->getDbColor(); } - - public function setColor($color) { + + public function setColor($color) + { $show = CcShowQuery::create()->findPK($this->_showId); $show->setDbColor($color); } - public function getBackgroundColor() { + public function getBackgroundColor() + { $show = CcShowQuery::create()->findPK($this->_showId); return $show->getDbBackgroundColor(); } - - public function setBackgroundColor($backgroundColor) { + + public function setBackgroundColor($backgroundColor) + { $show = CcShowQuery::create()->findPK($this->_showId); $show->setDbBackgroundColor($backgroundColor); } - public function cancelShow($day_timestamp) { + public function cancelShow($day_timestamp) + { global $CC_DBC; $timeinfo = explode(" ", $day_timestamp); @@ -62,20 +72,21 @@ class Show { WHERE starts >= '{$day_timestamp}' AND show_id = {$this->_showId}"; $CC_DBC->query($sql); + RabbitMq::PushSchedule(); } //end dates are non inclusive. - public static function addShow($data) { - + public static function addShow($data) + { $con = Propel::getConnection(CcShowPeer::DATABASE_NAME); $sql = "SELECT time '{$data['add_show_start_time']}' + INTERVAL '{$data['add_show_duration']} hour' "; $r = $con->query($sql); - $endTime = $r->fetchColumn(0); + $endTime = $r->fetchColumn(0); $sql = "SELECT EXTRACT(DOW FROM TIMESTAMP '{$data['add_show_start_date']} {$data['add_show_start_time']}')"; $r = $con->query($sql); - $startDow = $r->fetchColumn(0); + $startDow = $r->fetchColumn(0); if($data['add_show_no_end']) { $endDate = NULL; @@ -84,13 +95,13 @@ class Show { else if($data['add_show_repeats']) { $sql = "SELECT date '{$data['add_show_end_date']}' + INTERVAL '1 day' "; $r = $con->query($sql); - $endDate = $r->fetchColumn(0); + $endDate = $r->fetchColumn(0); } else { $sql = "SELECT date '{$data['add_show_start_date']}' + INTERVAL '1 day' "; $r = $con->query($sql); $endDate = $r->fetchColumn(0); - } + } //only want the day of the week from the start date. if(!$data['add_show_repeats']) { @@ -98,7 +109,7 @@ class Show { } else if($data['add_show_repeats'] && $data['add_show_day_check'] == "") { $data['add_show_day_check'] = array($startDow); - } + } //find repeat type or set to a non repeating show. if($data['add_show_repeats']) { @@ -114,7 +125,7 @@ class Show { $show->setDbUrl($data['add_show_url']); $show->setDbColor($data['add_show_color']); $show->setDbBackgroundColor($data['add_show_background_color']); - $show->save(); + $show->save(); $showId = $show->getDbId(); @@ -127,7 +138,6 @@ class Show { //don't set day for monthly repeat type, it's invalid. if($data['add_show_repeats'] && $data["add_show_repeat_type"] == 2) { - $showDay = new CcShowDays(); $showDay->setDbFirstShow($data['add_show_start_date']); $showDay->setDbLastShow($endDate); @@ -137,29 +147,25 @@ class Show { $showDay->setDbShowId($showId); $showDay->setDbRecord($isRecorded); $showDay->save(); - } else { - foreach ($data['add_show_day_check'] as $day) { - if($startDow !== $day){ - - if($startDow > $day) + + if ($startDow > $day) $daysAdd = 6 - $startDow + 1 + $day; else - $daysAdd = $day - $startDow; + $daysAdd = $day - $startDow; $sql = "SELECT date '{$data['add_show_start_date']}' + INTERVAL '{$daysAdd} day' "; $r = $con->query($sql); - $start = $r->fetchColumn(0); + $start = $r->fetchColumn(0); } else { $start = $data['add_show_start_date']; } if(strtotime($start) < strtotime($endDate) || is_null($endDate)) { - $showDay = new CcShowDays(); $showDay->setDbFirstShow($start); $showDay->setDbLastShow($endDate); @@ -180,7 +186,6 @@ class Show { for($i=1; $i<=5; $i++) { if($data['add_show_rebroadcast_date_'.$i]) { - $showRebroad = new CcShowRebroadcast(); $showRebroad->setDbDayOffset($data['add_show_rebroadcast_date_'.$i]); $showRebroad->setDbStartTime($data['add_show_rebroadcast_time_'.$i]); @@ -190,14 +195,13 @@ class Show { } } else if($data['add_show_record'] && $data['add_show_rebroadcast'] && $repeat_type == -1){ - + for($i=1; $i<=5; $i++) { if($data['add_show_rebroadcast_absolute_date_'.$i]) { - $sql = "SELECT date '{$data['add_show_rebroadcast_absolute_date_'.$i]}' - date '{$data['add_show_start_date']}' "; $r = $con->query($sql); - $offset_days = $r->fetchColumn(0); + $offset_days = $r->fetchColumn(0); $showRebroad = new CcShowRebroadcast(); $showRebroad->setDbDayOffset($offset_days." days"); @@ -207,7 +211,7 @@ class Show { } } } - + if(is_array($data['add_show_hosts'])) { //add selected hosts to cc_show_hosts table. foreach ($data['add_show_hosts'] as $host) { @@ -219,18 +223,20 @@ class Show { } Show::populateShowUntilLastGeneratedDate($showId); + RabbitMq::PushSchedule(); } - public static function getShows($start_timestamp, $end_timestamp, $excludeInstance=NULL, $onlyRecord=FALSE) { + public static function getShows($start_timestamp, $end_timestamp, $excludeInstance=NULL, $onlyRecord=FALSE) + { global $CC_DBC; - $sql = "SELECT starts, ends, record, rebroadcast, instance_id, show_id, name, description, - color, background_color, cc_show_instances.id AS instance_id - FROM cc_show_instances + $sql = "SELECT starts, ends, record, rebroadcast, instance_id, 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"; //only want shows that are starting at the time or later. - if($onlyRecord) { + if ($onlyRecord) { $sql = $sql." WHERE (starts >= '{$start_timestamp}' AND starts < timestamp '{$start_timestamp}' + interval '2 hours')"; $sql = $sql." AND (record = 1)"; @@ -240,10 +246,10 @@ class Show { $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)) { + + if (isset($excludeInstance)) { foreach($excludeInstance as $instance) { $sql_exclude[] = "cc_show_instances.id != {$instance}"; } @@ -257,8 +263,8 @@ class Show { return $CC_DBC->GetAll($sql); } - private static function setNextPop($next_date, $show_id, $day) { - + private static function setNextPop($next_date, $show_id, $day) + { $nextInfo = explode(" ", $next_date); $repeatInfo = CcShowDaysQuery::create() @@ -271,15 +277,16 @@ class Show { } //for a show with repeat_type == -1 - private static function populateNonRepeatingShow($show_id, $first_show, $start_time, $duration, $day, $record, $end_timestamp) { + private static function populateNonRepeatingShow($show_id, $first_show, $start_time, $duration, $day, $record, $end_timestamp) + { global $CC_DBC; - + $next_date = $first_show." ".$start_time; - + if(strtotime($next_date) < strtotime($end_timestamp)) { - + $start = $next_date; - + $sql = "SELECT timestamp '{$start}' + interval '{$duration}'"; $end = $CC_DBC->GetOne($sql); @@ -298,10 +305,10 @@ class Show { foreach($rebroadcasts as $rebroadcast) { $timeinfo = explode(" ", $start); - + $sql = "SELECT timestamp '{$timeinfo[0]}' + interval '{$rebroadcast["day_offset"]}' + interval '{$rebroadcast["start_time"]}'"; $rebroadcast_start_time = $CC_DBC->GetOne($sql); - + $sql = "SELECT timestamp '{$rebroadcast_start_time}' + interval '{$duration}'"; $rebroadcast_end_time = $CC_DBC->GetOne($sql); @@ -315,12 +322,13 @@ class Show { $newRebroadcastInstance->save(); } } + RabbitMq::PushSchedule(); } //for a show with repeat_type == 0,1,2 - private static function populateRepeatingShow($show_id, $next_pop_date, $first_show, $last_show, + private static function populateRepeatingShow($show_id, $next_pop_date, $first_show, $last_show, $start_time, $duration, $day, $record, $end_timestamp, $interval) { - global $CC_DBC; + global $CC_DBC; if(isset($next_pop_date)) { $next_date = $next_pop_date." ".$start_time; @@ -333,9 +341,9 @@ class Show { $rebroadcasts = $CC_DBC->GetAll($sql); while(strtotime($next_date) < strtotime($end_timestamp) && (strtotime($last_show) > strtotime($next_date) || is_null($last_show))) { - + $start = $next_date; - + $sql = "SELECT timestamp '{$start}' + interval '{$duration}'"; $end = $CC_DBC->GetOne($sql); @@ -351,10 +359,10 @@ class Show { foreach($rebroadcasts as $rebroadcast) { $timeinfo = explode(" ", $next_date); - + $sql = "SELECT timestamp '{$timeinfo[0]}' + interval '{$rebroadcast["day_offset"]}' + interval '{$rebroadcast["start_time"]}'"; $rebroadcast_start_time = $CC_DBC->GetOne($sql); - + $sql = "SELECT timestamp '{$rebroadcast_start_time}' + interval '{$duration}'"; $rebroadcast_end_time = $CC_DBC->GetOne($sql); @@ -373,64 +381,65 @@ class Show { } Show::setNextPop($next_date, $show_id, $day); + RabbitMq::PushSchedule(); } - private static function populateShow($repeat_type, $show_id, $next_pop_date, + private static function populateShow($repeat_type, $show_id, $next_pop_date, $first_show, $last_show, $start_time, $duration, $day, $record, $end_timestamp) { if($repeat_type == -1) { Show::populateNonRepeatingShow($show_id, $first_show, $start_time, $duration, $day, $record, $end_timestamp); } else if($repeat_type == 0) { - Show::populateRepeatingShow($show_id, $next_pop_date, $first_show, $last_show, + Show::populateRepeatingShow($show_id, $next_pop_date, $first_show, $last_show, $start_time, $duration, $day, $record, $end_timestamp, '7 days'); } else if($repeat_type == 1) { - Show::populateRepeatingShow($show_id, $next_pop_date, $first_show, $last_show, + Show::populateRepeatingShow($show_id, $next_pop_date, $first_show, $last_show, $start_time, $duration, $day, $record, $end_timestamp, '14 days'); } else if($repeat_type == 2) { - Show::populateRepeatingShow($show_id, $next_pop_date, $first_show, $last_show, + Show::populateRepeatingShow($show_id, $next_pop_date, $first_show, $last_show, $start_time, $duration, $day, $record, $end_timestamp, '1 month'); } - } + } //used to catch up a newly added show private static function populateShowUntilLastGeneratedDate($show_id) { global $CC_DBC; $showsPopUntil = Application_Model_Preference::GetShowsPopulatedUntil(); - + $sql = "SELECT * FROM cc_show_days WHERE show_id = {$show_id}"; - $res = $CC_DBC->GetAll($sql); + $res = $CC_DBC->GetAll($sql); foreach($res as $row) { - Show::populateShow($row["repeat_type"], $row["show_id"], $row["next_pop_date"], $row["first_show"], - $row["last_show"], $row["start_time"], $row["duration"], $row["day"], $row["record"], $showsPopUntil); - } + Show::populateShow($row["repeat_type"], $row["show_id"], $row["next_pop_date"], $row["first_show"], + $row["last_show"], $row["start_time"], $row["duration"], $row["day"], $row["record"], $showsPopUntil); + } } public static function populateShowsUntil($pop_timestamp, $end_timestamp) { global $CC_DBC; if($pop_timestamp != "") { - $sql = "SELECT * FROM cc_show_days - WHERE last_show IS NULL + $sql = "SELECT * FROM cc_show_days + WHERE last_show IS NULL OR first_show < '{$end_timestamp}' AND last_show > '{$pop_timestamp}'"; } else { $today_timestamp = date("Y-m-d"); - $sql = "SELECT * FROM cc_show_days - WHERE last_show IS NULL + $sql = "SELECT * FROM cc_show_days + WHERE last_show IS NULL OR first_show < '{$end_timestamp}' AND last_show > '{$today_timestamp}'"; } - $res = $CC_DBC->GetAll($sql); + $res = $CC_DBC->GetAll($sql); foreach($res as $row) { - Show::populateShow($row["repeat_type"], $row["show_id"], $row["next_pop_date"], $row["first_show"], - $row["last_show"], $row["start_time"], $row["duration"], $row["day"], $row["record"], $end_timestamp); - } + Show::populateShow($row["repeat_type"], $row["show_id"], $row["next_pop_date"], $row["first_show"], + $row["last_show"], $row["start_time"], $row["duration"], $row["day"], $row["record"], $end_timestamp); + } } public static function getFullCalendarEvents($start, $end, $editable=false) { @@ -460,19 +469,15 @@ class Show { private static function makeFullCalendarEvent($show, $options=array()) { global $CC_DBC; - + $event = array(); if($show["rebroadcast"]) { - $title = "REBROADCAST ".$show["name"]; $event["disableResizing"] = true; } - else { - $title = $show["name"]; - } $event["id"] = $show["instance_id"]; - $event["title"] = $title; + $event["title"] = $show["name"]; $event["start"] = $show["starts"]; $event["end"] = $show["ends"]; $event["allDay"] = false; @@ -500,56 +505,68 @@ class ShowInstance { public function __construct($instanceId) { - $this->_instanceId = $instanceId; + $this->_instanceId = $instanceId; } - public function getShowId() { + public function getShowId() + { $showInstance = CcShowInstancesQuery::create()->findPK($this->_instanceId); return $showInstance->getDbShowId(); } - public function getShowInstanceId() { + public function getShowInstanceId() + { return $this->_instanceId; } - public function isRebroadcast() { + public function isRebroadcast() + { $showInstance = CcShowInstancesQuery::create()->findPK($this->_instanceId); return $showInstance->getDbOriginalShow(); } - public function isRecorded() { + public function isRecorded() + { $showInstance = CcShowInstancesQuery::create()->findPK($this->_instanceId); return $showInstance->getDbRecord(); } - public function getName() { + public function getName() + { $show = CcShowQuery::create()->findPK($this->getShowId()); return $show->getDbName(); } - public function getShowStart() { + public function getShowStart() + { $showInstance = CcShowInstancesQuery::create()->findPK($this->_instanceId); return $showInstance->getDbStarts(); } - public function getShowEnd() { + public function getShowEnd() + { $showInstance = CcShowInstancesQuery::create()->findPK($this->_instanceId); return $showInstance->getDbEnds(); } - public function setShowStart($start) { + public function setShowStart($start) + { $showInstance = CcShowInstancesQuery::create()->findPK($this->_instanceId); $showInstance->setDbStarts($start) ->save(); + RabbitMq::PushSchedule(); } - public function setShowEnd($end) { + public function setShowEnd($end) + { $showInstance = CcShowInstancesQuery::create()->findPK($this->_instanceId); $showInstance->setDbEnds($end) - ->save(); + ->save(); + RabbitMq::PushSchedule(); } - public function moveScheduledShowContent($deltaDay, $deltaHours, $deltaMin) { + public function moveScheduledShowContent($deltaDay, $deltaHours, $deltaMin) + { global $CC_DBC; $sql = "UPDATE cc_schedule @@ -558,9 +575,11 @@ class ShowInstance { WHERE instance_id = '{$this->_instanceId}'"; $CC_DBC->query($sql); + RabbitMq::PushSchedule(); } - public function moveShow($deltaDay, $deltaMin){ + public function moveShow($deltaDay, $deltaMin) + { global $CC_DBC; $hours = $deltaMin/60; @@ -572,7 +591,7 @@ class ShowInstance { $mins = abs($deltaMin%60); $starts = $this->getShowStart(); - $ends = $this->getShowEnd(); + $ends = $this->getShowEnd(); $sql = "SELECT timestamp '{$starts}' + interval '{$deltaDay} days' + interval '{$hours}:{$mins}'"; $new_starts = $CC_DBC->GetOne($sql); @@ -595,18 +614,20 @@ class ShowInstance { if($rebroadcast) { $sql = "SELECT timestamp '{$new_starts}' < (SELECT starts FROM cc_show_instances WHERE id = {$rebroadcast})"; $isBeforeRecordedOriginal = $CC_DBC->GetOne($sql); - + if($isBeforeRecordedOriginal === 't'){ return "Cannot move a rebroadcast show before its original"; } } - + $this->moveScheduledShowContent($deltaDay, $hours, $mins); $this->setShowStart($new_starts); - $this->setShowEnd($new_ends); + $this->setShowEnd($new_ends); + RabbitMq::PushSchedule(); } - public function resizeShow($deltaDay, $deltaMin){ + public function resizeShow($deltaDay, $deltaMin) + { global $CC_DBC; $hours = $deltaMin/60; @@ -618,7 +639,7 @@ class ShowInstance { $mins = abs($deltaMin%60); $starts = $this->getShowStart(); - $ends = $this->getShowEnd(); + $ends = $this->getShowEnd(); $sql = "SELECT timestamp '{$ends}' + interval '{$deltaDay} days' + interval '{$hours}:{$mins}'"; $new_ends = $CC_DBC->GetOne($sql); @@ -639,106 +660,142 @@ class ShowInstance { WHERE rebroadcast = 1 AND instance_id = {$this->_instanceId}"; $CC_DBC->query($sql); } - + $this->setShowEnd($new_ends); + RabbitMq::PushSchedule(); } - private function getLastGroupId() { + private function getLastGroupId() + { global $CC_DBC; - $sql = "SELECT group_id FROM cc_schedule WHERE instance_id = '{$this->_instanceId}' ORDER BY ends DESC LIMIT 1"; - $res = $CC_DBC->GetOne($sql); - + $res = $CC_DBC->GetOne($sql); return $res; } - public function addPlaylistToShow($plId) { - + public function addPlaylistToShow($plId) + { $sched = new ScheduleGroup(); $lastGroupId = $this->getLastGroupId(); - + if(is_null($lastGroupId)) { - $groupId = $sched->add($this->_instanceId, $this->getShowStart(), null, $plId); + $groupId = $sched->add($this->_instanceId, $this->getShowStart(), null, $plId); } else { $groupId = $sched->addPlaylistAfter($this->_instanceId, $lastGroupId, $plId); } + RabbitMq::PushSchedule(); } + public function addFileToShow($file_id) + { + $sched = new ScheduleGroup(); + $lastGroupId = $this->getLastGroupId(); + + if(is_null($lastGroupId)) { + + $groupId = $sched->add($this->_instanceId, $this->getShowStart(), $file_id); + } + else { + $groupId = $sched->addFileAfter($this->_instanceId, $lastGroupId, $file_id); + } + RabbitMq::PushSchedule(); + } + public function scheduleShow($plIds) { - + foreach($plIds as $plId) { $this->addPlaylistToShow($plId); } } - public function removeGroupFromShow($group_id){ + public function removeGroupFromShow($group_id) + { global $CC_DBC; $sql = "SELECT MAX(ends) as end_timestamp, (MAX(ends) - MIN(starts)) as length - FROM cc_schedule + FROM cc_schedule WHERE group_id = '{$group_id}'"; - + $groupBoundry = $CC_DBC->GetRow($sql); $group = CcScheduleQuery::create() ->filterByDbGroupId($group_id) ->delete(); - $sql = "UPDATE cc_schedule - SET starts = (starts - INTERVAL '{$groupBoundry["length"]}'), ends = (ends - INTERVAL '{$groupBoundry["length"]}') + $sql = "UPDATE cc_schedule + SET starts = (starts - INTERVAL '{$groupBoundry["length"]}'), ends = (ends - INTERVAL '{$groupBoundry["length"]}') WHERE starts >= '{$groupBoundry["end_timestamp"]}' AND instance_id = {$this->_instanceId}"; $CC_DBC->query($sql); + RabbitMq::PushSchedule(); } - public function clearShow() { - + public function clearShow() + { CcScheduleQuery::create() ->filterByDbInstanceId($this->_instanceId) ->delete(); + RabbitMq::PushSchedule(); } - public function deleteShow() { - + public function deleteShow() + { CcShowInstancesQuery::create() ->findPK($this->_instanceId) ->delete(); + RabbitMq::PushSchedule(); } - public function getTimeScheduled() { + public function setRecordedFile($file_id) + { + $showInstance = CcShowInstancesQuery::create() + ->findPK($this->_instanceId); + $showInstance->setDbRecordedFile($file_id) + ->save(); + $rebroadcasts = CcShowInstancesQuery::create() + ->filterByDbOriginalShow($this->_instanceId) + ->find(); + + foreach ($rebroadcasts as $rebroadcast) { + + $rebroad = new ShowInstance($rebroadcast->getDbId()); + $rebroad->addFileToShow($file_id); + RabbitMq::PushSchedule(); + } + } + + public function getTimeScheduled() + { $instance_id = $this->getShowInstanceId(); $time = Schedule::GetTotalShowTime($instance_id); - return $time; } - public function getTimeUnScheduled() { - - $start_timestamp = $this->getShowStart(); + public function getTimeUnScheduled() + { + $start_timestamp = $this->getShowStart(); $end_timestamp = $this->getShowEnd(); $instance_id = $this->getShowInstanceId(); - $time = Schedule::getTimeUnScheduledInRange($instance_id, $start_timestamp, $end_timestamp); - return $time; } - public function getPercentScheduled() { - - $start_timestamp = $this->getShowStart(); + public function getPercentScheduled() + { + $start_timestamp = $this->getShowStart(); $end_timestamp = $this->getShowEnd(); $instance_id = $this->getShowInstanceId(); - return Schedule::GetPercentScheduled($instance_id, $start_timestamp, $end_timestamp); } - public function getShowLength() { + public function getShowLength() + { global $CC_DBC; - $start_timestamp = $this->getShowStart(); + $start_timestamp = $this->getShowStart(); $end_timestamp = $this->getShowEnd(); $sql = "SELECT TIMESTAMP '{$end_timestamp}' - TIMESTAMP '{$start_timestamp}' "; @@ -747,26 +804,27 @@ class ShowInstance { return $length; } - public function searchPlaylistsForShow($datatables){ - + public function searchPlaylistsForShow($datatables) + { $time_remaining = $this->getTimeUnScheduled(); - return StoredFile::searchPlaylistsForSchedule($time_remaining, $datatables); } - public function getShowListContent() { + public function getShowListContent() + { global $CC_DBC; - $sql = "SELECT * + $sql = "SELECT * FROM (cc_schedule AS s LEFT JOIN cc_files AS f ON f.id = s.file_id LEFT JOIN cc_playlist AS p ON p.id = s.playlist_id ) WHERE s.instance_id = '{$this->_instanceId}' ORDER BY starts"; - return $CC_DBC->GetAll($sql); + return $CC_DBC->GetAll($sql); } - public function getShowContent() { + public function getShowContent() + { global $CC_DBC; $res = $this->getShowListContent(); @@ -788,7 +846,7 @@ class ShowInstance { $items[$pl_counter]["pl_name"] = $row["name"]; $items[$pl_counter]["pl_creator"] = $row["creator"]; $items[$pl_counter]["pl_description"] = $row["description"]; - $items[$pl_counter]["pl_group"] = $row["group_id"]; + $items[$pl_counter]["pl_group"] = $row["group_id"]; $sql = "SELECT SUM(clip_length) FROM cc_schedule WHERE group_id = '{$currGroupId}'"; $length = $CC_DBC->GetOne($sql); @@ -802,46 +860,49 @@ class ShowInstance { $items[$pl_counter]["pl_content"][$f_counter]["f_length"] = $row["length"]; } - return $items; + return $items; } } /* Show Data Access Layer */ -class Show_DAL{ - - public static function GetCurrentShow($timeNow) { +class Show_DAL { + + public static function GetCurrentShow($timeNow) + { global $CC_CONFIG, $CC_DBC; - + $timestamp = explode(" ", $timeNow); $date = $timestamp[0]; $time = $timestamp[1]; - + $sql = "SELECT si.starts as start_timestamp, si.ends as end_timestamp, s.name, s.id, si.id as instance_id, si.record" ." FROM $CC_CONFIG[showInstances] si, $CC_CONFIG[showTable] s" ." WHERE si.show_id = s.id" ." AND si.starts <= TIMESTAMP '$timeNow'" ." AND si.ends > TIMESTAMP '$timeNow'"; - + $rows = $CC_DBC->GetAll($sql); return $rows; } - - public static function GetNextShow($timeNow) { + + public static function GetNextShows($timeNow, $limit) + { global $CC_CONFIG, $CC_DBC; - + $sql = "SELECT *, si.starts as start_timestamp, si.ends as end_timestamp FROM " ." $CC_CONFIG[showInstances] si, $CC_CONFIG[showTable] s" ." WHERE si.show_id = s.id" ." AND si.starts >= TIMESTAMP '$timeNow'" ." AND si.starts < TIMESTAMP '$timeNow' + INTERVAL '48 hours'" ." ORDER BY si.starts" - ." LIMIT 1"; - + ." LIMIT $limit"; + $rows = $CC_DBC->GetAll($sql); return $rows; } - public static function GetShowsInRange($timeNow, $start, $end){ + public static function GetShowsInRange($timeNow, $start, $end) + { global $CC_CONFIG, $CC_DBC; $sql = "SELECT" ." si.starts as show_starts," @@ -873,5 +934,5 @@ class Show_DAL{ return $CC_DBC->GetAll($sql); } - + } diff --git a/application/models/Soundcloud.php b/application/models/Soundcloud.php new file mode 100644 index 000000000..78e9223a5 --- /dev/null +++ b/application/models/Soundcloud.php @@ -0,0 +1,69 @@ +_soundcloud = new Services_Soundcloud($CC_CONFIG['soundcloud-client-id'], $CC_CONFIG['soundcloud-client-secret']); + } + + private function getToken() + { + $username = Application_Model_Preference::GetSoundCloudUser(); + $password = Application_Model_Preference::GetSoundCloudPassword(); + + if($username === "" || $password === "") + { + return false; + } + + $token = $this->_soundcloud->accessTokenResourceOwner($username, $password); + + return $token; + } + + public function uploadTrack($filepath, $filename, $description, $tags=array()) + { + if($this->getToken()) + { + if(count($tags)) { + $tags = join(" ", $tags); + $tags = $tags." ".Application_Model_Preference::GetSoundCloudTags(); + } + else { + $tags = Application_Model_Preference::GetSoundCloudTags(); + } + + $track_data = array( + 'track[sharing]' => 'private', + 'track[title]' => $filename, + 'track[asset_data]' => '@' . $filepath, + 'track[tag_list]' => $tags, + 'track[description]' => $description + ); + + try { + $response = json_decode( + $this->_soundcloud->post('tracks', $track_data), + true + ); + + echo var_dump($response); + } + catch (Services_Soundcloud_Invalid_Http_Response_Code_Exception $e) { + echo $e->getMessage(); + } + } + else + { + echo "could not get soundcloud token"; + } + } + +} diff --git a/application/models/StoredFile.php b/application/models/StoredFile.php index 32d1ab0dc..ecff91caa 100644 --- a/application/models/StoredFile.php +++ b/application/models/StoredFile.php @@ -1647,5 +1647,143 @@ class StoredFile { return array("sEcho" => intval($data["sEcho"]), "iTotalDisplayRecords" => $totalDisplayRows, "iTotalRecords" => $totalRows, "aaData" => $results); } + public static function uploadFile($targetDir) { + + // HTTP headers for no cache etc + header('Content-type: text/plain; charset=UTF-8'); + header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); + header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT"); + header("Cache-Control: no-store, no-cache, must-revalidate"); + header("Cache-Control: post-check=0, pre-check=0", false); + header("Pragma: no-cache"); + + // Settings + //$targetDir = ini_get("upload_tmp_dir"); //. DIRECTORY_SEPARATOR . "plupload"; + $cleanupTargetDir = false; // Remove old files + $maxFileAge = 60 * 60; // Temp file age in seconds + + // 5 minutes execution time + @set_time_limit(5 * 60); + // usleep(5000); + + // Get parameters + $chunk = isset($_REQUEST["chunk"]) ? $_REQUEST["chunk"] : 0; + $chunks = isset($_REQUEST["chunks"]) ? $_REQUEST["chunks"] : 0; + $fileName = isset($_REQUEST["name"]) ? $_REQUEST["name"] : ''; + + // Clean the fileName for security reasons + //$fileName = preg_replace('/[^\w\._]+/', '', $fileName); + + // Create target dir + if (!file_exists($targetDir)) + @mkdir($targetDir); + + // Remove old temp files + if (is_dir($targetDir) && ($dir = opendir($targetDir))) { + while (($file = readdir($dir)) !== false) { + $filePath = $targetDir . DIRECTORY_SEPARATOR . $file; + + // Remove temp files if they are older than the max age + if (preg_match('/\.tmp$/', $file) && (filemtime($filePath) < time() - $maxFileAge)) + @unlink($filePath); + } + + closedir($dir); + } else + die('{"jsonrpc" : "2.0", "error" : {"code": 100, "message": "Failed to open temp directory."}, "id" : "id"}'); + + // Look for the content type header + if (isset($_SERVER["HTTP_CONTENT_TYPE"])) + $contentType = $_SERVER["HTTP_CONTENT_TYPE"]; + + if (isset($_SERVER["CONTENT_TYPE"])) + $contentType = $_SERVER["CONTENT_TYPE"]; + + if (strpos($contentType, "multipart") !== false) { + if (isset($_FILES['file']['tmp_name']) && is_uploaded_file($_FILES['file']['tmp_name'])) { + // Open temp file + $out = fopen($targetDir . DIRECTORY_SEPARATOR . $fileName, $chunk == 0 ? "wb" : "ab"); + if ($out) { + // Read binary input stream and append it to temp file + $in = fopen($_FILES['file']['tmp_name'], "rb"); + + if ($in) { + while ($buff = fread($in, 4096)) + fwrite($out, $buff); + } else + die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": "Failed to open input stream."}, "id" : "id"}'); + + fclose($out); + unlink($_FILES['file']['tmp_name']); + } else + die('{"jsonrpc" : "2.0", "error" : {"code": 102, "message": "Failed to open output stream."}, "id" : "id"}'); + } else + die('{"jsonrpc" : "2.0", "error" : {"code": 103, "message": "Failed to move uploaded file."}, "id" : "id"}'); + } else { + // Open temp file + $out = fopen($targetDir . DIRECTORY_SEPARATOR . $fileName, $chunk == 0 ? "wb" : "ab"); + if ($out) { + // Read binary input stream and append it to temp file + $in = fopen("php://input", "rb"); + + if ($in) { + while ($buff = fread($in, 4096)) + fwrite($out, $buff); + } else + die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": "Failed to open input stream."}, "id" : "id"}'); + + fclose($out); + } else + die('{"jsonrpc" : "2.0", "error" : {"code": 102, "message": "Failed to open output stream."}, "id" : "id"}'); + } + + $audio_file = $targetDir . DIRECTORY_SEPARATOR . $fileName; + + $md5 = md5_file($audio_file); + $duplicate = StoredFile::RecallByMd5($md5); + if ($duplicate) { + if (PEAR::isError($duplicate)) { + die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": ' . $duplicate->getMessage() .'}}'); + } + else { + $duplicateName = $duplicate->getMetadataValue(UI_MDATA_KEY_TITLE); + die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": "An identical audioclip named ' . $duplicateName . ' already exists in the storage server."}}'); + } + } + + $metadata = Metadata::LoadFromFile($audio_file); + + if (PEAR::isError($metadata)) { + die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": ' + $metadata->getMessage() + '}}'); + } + + // #2196 no id tag -> use the original filename + if (basename($audio_file) == $metadata[UI_MDATA_KEY_TITLE]) { + $metadata[UI_MDATA_KEY_TITLE] = basename($audio_file); + $metadata[UI_MDATA_KEY_FILENAME] = basename($audio_file); + } + + // setMetadataBatch doesnt like these values + unset($metadata['audio']); + unset($metadata['playtime_seconds']); + + $values = array( + "filename" => basename($audio_file), + "filepath" => $audio_file, + "filetype" => "audioclip", + "mime" => $metadata[UI_MDATA_KEY_FORMAT], + "md5" => $md5 + ); + $storedFile = StoredFile::Insert($values); + + if (PEAR::isError($storedFile)) { + die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": ' + $storedFile->getMessage() + '}}'); + } + + $storedFile->setMetadataBatch($metadata); + + return $storedFile; + } + } diff --git a/application/models/airtime/map/CcScheduleTableMap.php b/application/models/airtime/map/CcScheduleTableMap.php index 3f2502f1c..92afaab0c 100644 --- a/application/models/airtime/map/CcScheduleTableMap.php +++ b/application/models/airtime/map/CcScheduleTableMap.php @@ -39,7 +39,7 @@ class CcScheduleTableMap extends TableMap { $this->setPrimaryKeyMethodInfo('cc_schedule_id_seq'); // columns $this->addPrimaryKey('ID', 'DbId', 'INTEGER', true, null, null); - $this->addColumn('PLAYLIST_ID', 'DbPlaylistId', 'INTEGER', true, null, null); + $this->addColumn('PLAYLIST_ID', 'DbPlaylistId', 'INTEGER', false, null, null); $this->addColumn('STARTS', 'DbStarts', 'TIMESTAMP', true, null, null); $this->addColumn('ENDS', 'DbEnds', 'TIMESTAMP', true, null, null); $this->addColumn('GROUP_ID', 'DbGroupId', 'INTEGER', false, null, null); diff --git a/application/models/tests/SchedulerTests.php b/application/models/tests/SchedulerTests.php index 2064830b8..8f86b4ef9 100644 --- a/application/models/tests/SchedulerTests.php +++ b/application/models/tests/SchedulerTests.php @@ -110,6 +110,7 @@ class SchedulerTests extends PHPUnit_TestCase { } } +/* function testGetItems() { $i1 = new ScheduleGroup(); $groupId1 = $i1->add('2008-01-01 12:00:00.000', $this->storedFile->getId()); @@ -123,5 +124,6 @@ class SchedulerTests extends PHPUnit_TestCase { $i1->remove(); $i2->remove(); } +*/ } diff --git a/application/views/helpers/SoundCloudLink.php b/application/views/helpers/SoundCloudLink.php deleted file mode 100644 index 9f4d321e5..000000000 --- a/application/views/helpers/SoundCloudLink.php +++ /dev/null @@ -1,22 +0,0 @@ -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/partialviews/header.phtml b/application/views/scripts/partialviews/header.phtml index 4905d0d10..136a11ed0 100644 --- a/application/views/scripts/partialviews/header.phtml +++ b/application/views/scripts/partialviews/header.phtml @@ -14,7 +14,7 @@
- 00:00 +
@@ -43,6 +43,6 @@
diff --git a/application/views/scripts/playlist/update.phtml b/application/views/scripts/playlist/update.phtml index 7c9c2a898..590f071cf 100644 --- a/application/views/scripts/playlist/update.phtml +++ b/application/views/scripts/playlist/update.phtml @@ -10,6 +10,7 @@
+
diff --git a/application/views/scripts/schedule/add-show-form.phtml b/application/views/scripts/schedule/add-show-form.phtml index 2a111448b..9d8613d7d 100644 --- a/application/views/scripts/schedule/add-show-form.phtml +++ b/application/views/scripts/schedule/add-show-form.phtml @@ -1,7 +1,10 @@
Close - +

What

diff --git a/application/views/scripts/user/add-user.phtml b/application/views/scripts/user/add-user.phtml index b37c032bc..4f836e0fa 100644 --- a/application/views/scripts/user/add-user.phtml +++ b/application/views/scripts/user/add-user.phtml @@ -7,7 +7,9 @@
- +
@@ -26,6 +28,7 @@
+ successMessage ?>
form ?>
diff --git a/build/schema.xml b/build/schema.xml index 36941da37..f00ad14fb 100644 --- a/build/schema.xml +++ b/build/schema.xml @@ -234,7 +234,7 @@
- + diff --git a/build/sql/schema.sql b/build/sql/schema.sql index ee2f9b109..71ff970ef 100644 --- a/build/sql/schema.sql +++ b/build/sql/schema.sql @@ -345,7 +345,7 @@ DROP TABLE "cc_schedule" CASCADE; CREATE TABLE "cc_schedule" ( "id" serial NOT NULL, - "playlist_id" INTEGER NOT NULL, + "playlist_id" INTEGER, "starts" TIMESTAMP NOT NULL, "ends" TIMESTAMP NOT NULL, "group_id" INTEGER, diff --git a/dev_tools/pf.sh b/dev_tools/pf.sh deleted file mode 100755 index 324a44a36..000000000 --- a/dev_tools/pf.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -su -l pypo -c "tail -F /etc/service/pypo-fetch/log/main/current" diff --git a/dev_tools/pp.sh b/dev_tools/pp.sh deleted file mode 100755 index f1aac3025..000000000 --- a/dev_tools/pp.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -su -l pypo -c "tail -F /etc/service/pypo-push/log/main/current" diff --git a/dev_tools/pr.sh b/dev_tools/pr.sh new file mode 100755 index 000000000..f5e741658 --- /dev/null +++ b/dev_tools/pr.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +su -l pypo -c "tail -F /etc/service/recorder/log/main/current" diff --git a/dev_tools/pypoless.sh b/dev_tools/pypoless.sh new file mode 100755 index 000000000..542702527 --- /dev/null +++ b/dev_tools/pypoless.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +su -l pypo -c "less /etc/service/pypo/log/main/current" diff --git a/dev_tools/pypotail.sh b/dev_tools/pypotail.sh new file mode 100755 index 000000000..e91cd3917 --- /dev/null +++ b/dev_tools/pypotail.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +su -l pypo -c "tail -F /etc/service/pypo/log/main/current" diff --git a/install/DoctrineMigrations/Version20110312121200.php b/install/DoctrineMigrations/Version20110312121200.php new file mode 100644 index 000000000..8bf20d856 --- /dev/null +++ b/install/DoctrineMigrations/Version20110312121200.php @@ -0,0 +1,19 @@ +dropTable("cc_backup"); + $schema->dropTable("cc_trans"); + } + + public function down(Schema $schema) + { + } +} diff --git a/install/airtime-install.php b/install/airtime-install.php index aea7ea529..e0c068d2d 100644 --- a/install/airtime-install.php +++ b/install/airtime-install.php @@ -39,7 +39,6 @@ AirtimeInstall::InstallPostgresScriptingLanguage(); echo "* Creating database tables".PHP_EOL; AirtimeInstall::CreateDatabaseTables(); -AirtimeInstall::MigrateTables(__DIR__); echo "* Storage directory setup".PHP_EOL; AirtimeInstall::SetupStorageDirectory($CC_CONFIG); @@ -47,11 +46,20 @@ AirtimeInstall::SetupStorageDirectory($CC_CONFIG); echo "* Giving Apache permission to access the storage directory".PHP_EOL; AirtimeInstall::ChangeDirOwnerToWebserver($CC_CONFIG["storageDir"]); +echo "* Creating /usr/bin symlinks".PHP_EOL; +AirtimeInstall::CreateSymlinks($CC_CONFIG["storageDir"]); + echo "* Importing sample audio clips".PHP_EOL; system(__DIR__."/../utils/airtime-import --copy ../audio_samples/ > /dev/null"); +echo "* Python eggs Setup".PHP_EOL; +AirtimeInstall::SetUpPythonEggs(); + echo PHP_EOL."*** Pypo Installation ***".PHP_EOL; -system("python ".__DIR__."/../pypo/install/pypo-install.py"); +system("python ".__DIR__."/../python_apps/pypo/install/pypo-install.py"); + +echo PHP_EOL."*** Recorder Installation ***".PHP_EOL; +system("python ".__DIR__."/../python_apps/show-recorder/install/recorder-install.py"); echo "******************************* Install Complete *******************************".PHP_EOL; diff --git a/install/airtime-uninstall.php b/install/airtime-uninstall.php index 181006efd..5b9a58c3a 100644 --- a/install/airtime-uninstall.php +++ b/install/airtime-uninstall.php @@ -20,6 +20,7 @@ require_once(dirname(__FILE__).'/installInit.php'); // Need to check that we are superuser before running this. AirtimeInstall::ExitIfNotRoot(); +AirtimeInstall::RemoveSymlinks(); echo "******************************* Uninstall Begin ********************************".PHP_EOL; //------------------------------------------------------------------------ @@ -38,7 +39,7 @@ $command = "sudo -u postgres dropdb {$CC_CONFIG['dsn']['database']} 2> /dev/null //------------------------------------------------------------------------ if ($dbDeleteFailed) { echo " * Couldn't delete the database, so deleting all the DB tables...".PHP_EOL; - AirtimeInstall::DbConnect(true); + AirtimeInstall::DbConnect(false); if (!PEAR::isError($CC_DBC)) { $sql = "select * from pg_tables where tableowner = 'airtime'"; @@ -81,7 +82,10 @@ if ($results == 0) { AirtimeInstall::DeleteFilesRecursive($CC_CONFIG['storageDir']); -$command = "python ".__DIR__."/../pypo/install/pypo-uninstall.py"; +$command = "python ".__DIR__."/../python_apps/pypo/install/pypo-uninstall.py"; +system($command); + +$command = "python ".__DIR__."/../python_apps/show-recorder/install/recorder-uninstall.py"; system($command); echo "****************************** Uninstall Complete ******************************".PHP_EOL; diff --git a/install/installInit.php b/install/installInit.php index 3f8930e12..c49314672 100644 --- a/install/installInit.php +++ b/install/installInit.php @@ -79,7 +79,8 @@ class AirtimeInstall { { $api_key = AirtimeInstall::GenerateRandomString(); AirtimeInstall::UpdateIniValue(__DIR__.'/../build/airtime.conf', 'api_key', $api_key); - AirtimeInstall::UpdateIniValue(__DIR__.'/../pypo/config.cfg', 'api_key', "'$api_key'"); + AirtimeInstall::UpdateIniValue(__DIR__.'/../python_apps/pypo/config.cfg', 'api_key', "'$api_key'"); + AirtimeInstall::UpdateIniValue(__DIR__.'/../python_apps/show-recorder/config.cfg', 'api_key', "'$api_key'"); } public static function ExitIfNotRoot() @@ -112,7 +113,7 @@ class AirtimeInstall { public static function SetupStorageDirectory($CC_CONFIG) { global $CC_CONFIG, $CC_DBC; - + echo PHP_EOL."*** Directory Setup ***".PHP_EOL; foreach (array('baseFilesDir', 'storageDir') as $d) { if ( !file_exists($CC_CONFIG[$d]) ) { @@ -141,7 +142,7 @@ class AirtimeInstall { // Create the database user $command = "sudo -u postgres psql postgres --command \"CREATE USER {$CC_CONFIG['dsn']['username']} " ." ENCRYPTED PASSWORD '{$CC_CONFIG['dsn']['password']}' LOGIN CREATEDB NOCREATEUSER;\" 2>/dev/null"; - + @exec($command, $output, $results); if ($results == 0) { echo "* Database user '{$CC_CONFIG['dsn']['username']}' created.".PHP_EOL; @@ -159,7 +160,7 @@ class AirtimeInstall { public static function CreateDatabase() { global $CC_CONFIG; - + $command = "sudo -u postgres createdb {$CC_CONFIG['dsn']['database']} --owner {$CC_CONFIG['dsn']['username']} 2> /dev/null"; @exec($command, $output, $results); if ($results == 0) { @@ -202,10 +203,33 @@ class AirtimeInstall { system($command); } + public static function SetUpPythonEggs() + { + //install poster streaming upload + $command = "sudo easy_install poster"; + @exec($command); + } + public static function DeleteFilesRecursive($p_path) { $command = "rm -rf $p_path"; exec($command); } -} \ No newline at end of file + public static function CreateSymlinks(){ + AirtimeInstall::RemoveSymlinks(); + + $dir = realpath(__DIR__."/../utils/airtime-import"); + exec("ln -s $dir /usr/bin/airtime-import"); + + $dir = realpath(__DIR__."/../utils/airtime-clean-storage"); + exec("ln -s $dir /usr/bin/airtime-clean-storage"); + } + + public static function RemoveSymlinks(){ + exec("rm -f /usr/bin/airtime-import"); + exec("rm -f /usr/bin/airtime-clean-storage"); + } + + +} diff --git a/library/php-amqplib/demo/amqp_airtime_consumer.php b/library/php-amqplib/demo/amqp_airtime_consumer.php new file mode 100644 index 000000000..bb5f8bcc8 --- /dev/null +++ b/library/php-amqplib/demo/amqp_airtime_consumer.php @@ -0,0 +1,54 @@ +#!/usr/bin/php + + */ + +require_once('../amqp.inc'); + +$HOST = 'localhost'; +$PORT = 5672; +$USER = 'guest'; +$PASS = 'guest'; +$VHOST = '/'; +$EXCHANGE = 'airtime-schedule'; +$QUEUE = 'airtime-schedule-msgs'; +$CONSUMER_TAG = 'airtime-consumer'; + +$conn = new AMQPConnection($HOST, $PORT, $USER, $PASS); +$ch = $conn->channel(); +$ch->access_request($VHOST, false, false, true, true); + +$ch->queue_declare($QUEUE); +$ch->exchange_declare($EXCHANGE, 'direct', false, false, false); +$ch->queue_bind($QUEUE, $EXCHANGE); + +function process_message($msg) { + global $ch, $CONSUMER_TAG; + + echo "\n--------\n"; + echo $msg->body; + echo "\n--------\n"; + + $ch->basic_ack($msg->delivery_info['delivery_tag']); + + // Cancel callback + if ($msg->body === 'quit') { + $ch->basic_cancel($CONSUMER_TAG); + } +} + +$ch->basic_consume($QUEUE, $CONSUMER_TAG, false, false, false, false, 'process_message'); + +// Loop as long as the channel has callbacks registered +echo "Waiting for messages...\n"; +while(count($ch->callbacks)) { + $ch->wait(); +} + +$ch->close(); +$conn->close(); +?> diff --git a/plugins/jquery.showinfo.js b/plugins/jquery.showinfo.js new file mode 100644 index 000000000..c31381a10 --- /dev/null +++ b/plugins/jquery.showinfo.js @@ -0,0 +1,267 @@ +(function($){ + $.fn.airtimeShowSchedule = function(options) { + + var defaults = { + updatePeriod: 20, //seconds + sourceDomain: "http://localhost/", //where to get show status from + }; + var options = $.extend(defaults, options); + + return this.each(function() { + var obj = $(this); + var sd; + + getServerData(); + + function updateWidget(){ + var currentShow = sd.getCurrentShow(); + var nextShows = sd.getNextShows(); + + var currentShowName = ""; + var nextShowName = "" + + if (currentShow.length > 0){ + currentShowName = currentShow[0].getName(); + } + + if (nextShows.length > 0){ + nextShowName = nextShows[0].getName(); + } + + tableString = ""; + tableString += "

On air today

"; + tableString += "
"+ + ""; + + var shows=currentShow.concat(nextShows); + + obj.empty(); + for (var i=0; i" + + "" + + "" + + ""; + } + + tableString += "
"+shows[i].getRange()+""+shows[i].getName()+" Listen
"; + + obj.append(tableString); + } + + function processData(data){ + sd = new ScheduleData(data); + updateWidget(); + } + + function getServerData(){ + $.ajax({ url: options.sourceDomain + "api/live-info/", dataType:"jsonp", success:function(data){ + processData(data); + }, error:function(jqXHR, textStatus, errorThrown){}}); + setTimeout(getServerData, defaults.updatePeriod*1000); + } + }); + }; +})(jQuery); + + +(function($){ + $.fn.airtimeLiveInfo = function(options) { + + var defaults = { + updatePeriod: 5, //seconds + sourceDomain: "http://localhost/", //where to get show status from + audioStreamSource: "" //where to get audio stream from + }; + var options = $.extend(defaults, options); + + return this.each(function() { + var obj = $(this); + var sd; + getServerData(); + + function updateWidget(){ + var currentShow = sd.getCurrentShow(); + var nextShows = sd.getNextShows(); + + var showStatus = "Offline"; + var currentShowName = ""; + var timeElapsed = ""; + var timeRemaining = ""; + + var nextShowName = ""; + var nextShowRange = ""; + + if (currentShow.length > 0){ + showStatus = "On Air Now"; + currentShowName = currentShow[0].getName(); + + timeElapsed = sd.getShowTimeElapsed(currentShow[0]); + timeRemaining = sd.getShowTimeRemaining(currentShow[0]); + } + + if (nextShows.length > 0){ + nextShowName = nextShows[0].getName(); + nextShowRange = nextShows[0].getRange(); + } + + obj.empty(); + obj.append("Listen WADR Live"); + obj.append("

"+showStatus+" >>

"); + obj.append("
    " + + "
  • Current: "+currentShowName+ + ""+timeElapsed+"" + + ""+timeRemaining+""+ + "
  • " + + "" + + "
"); + + //refresh the UI to update the elapsed/remaining time + setTimeout(updateWidget, 1000); + } + + function processData(data){ + sd = new ScheduleData(data); + updateWidget(); + } + + function getServerData(){ + $.ajax({ url: options.sourceDomain + "api/live-info/", dataType:"jsonp", success:function(data){ + processData(data); + }, error:function(jqXHR, textStatus, errorThrown){}}); + setTimeout(getServerData, defaults.updatePeriod*1000); + } + }); + }; +})(jQuery); + +/* ScheduleData class BEGIN */ +function ScheduleData(data){ + this.data = data; + this.estimatedSchedulePosixTime; + + this.currentShow = new Array(); + for (var i=0; i< data.currentShow.length; i++){ + this.currentShow[i] = new Show(data.currentShow[i]); + } + + this.nextShows = new Array(); + for (var i=0; i< data.nextShow.length; i++){ + this.nextShows[i] = new Show(data.nextShow[i]); + } + + + this.schedulePosixTime = convertDateToPosixTime(data.schedulerTime); + this.schedulePosixTime += parseInt(data.timezoneOffset)*1000; + var date = new Date(); + this.localRemoteTimeOffset = date.getTime() - this.schedulePosixTime; +} + + +ScheduleData.prototype.secondsTimer = function(){ + var date = new Date(); + this.estimatedSchedulePosixTime = date.getTime() - this.localRemoteTimeOffset; +} + +ScheduleData.prototype.getCurrentShow = function(){ + return this.currentShow; +} + +ScheduleData.prototype.getNextShows = function() { + return this.nextShows; +} + +ScheduleData.prototype.getShowTimeElapsed = function(show) { + this.secondsTimer(); + + var showStart = convertDateToPosixTime(show.getStartTimestamp()); + return convertToHHMMSS(this.estimatedSchedulePosixTime - showStart); +}; + +ScheduleData.prototype.getShowTimeRemaining = function(show) { + this.secondsTimer(); + + var showEnd = convertDateToPosixTime(show.getEndTimestamp()); + return convertToHHMMSS(showEnd - this.estimatedSchedulePosixTime); +}; +/* ScheduleData class END */ + +/* Show class BEGIN */ +function Show(showData){ + this.showData = showData; +} + +Show.prototype.getName = function(){ + return this.showData.name; +} +Show.prototype.getRange = function(){ + return getTime(this.showData.start_timestamp) + " - " + getTime(this.showData.end_timestamp); +} +Show.prototype.getStartTimestamp = function(){ + return this.showData.start_timestamp; +} +Show.prototype.getEndTimestamp = function(){ + return this.showData.end_timestamp; +} +/* Show class END */ + + +function getTime(timestamp) { + var time = timestamp.split(" ")[1].split(":"); + return time[0] + ":" + time[1]; +}; + +/* Takes an input parameter of milliseconds and converts these into + * the format HH:MM:SS */ +function convertToHHMMSS(timeInMS){ + var time = parseInt(timeInMS); + + var hours = parseInt(time / 3600000); + time -= 3600000*hours; + + var minutes = parseInt(time / 60000); + time -= 60000*minutes; + + var seconds = parseInt(time / 1000); + + hours = hours.toString(); + minutes = minutes.toString(); + seconds = seconds.toString(); + + if (hours.length == 1) + hours = "0" + hours; + if (minutes.length == 1) + minutes = "0" + minutes; + if (seconds.length == 1) + seconds = "0" + seconds; + if (hours == "00") + return minutes + ":" + seconds; + else + return hours + ":" + minutes + ":" + seconds; +} + +/* Takes in a string of format similar to 2011-02-07 02:59:57, + * and converts this to epoch/posix time. */ +function convertDateToPosixTime(s){ + var datetime = s.split(" "); + + var date = datetime[0].split("-"); + var time = datetime[1].split(":"); + + var year = date[0]; + var month = date[1]; + var day = date[2]; + var hour = time[0]; + var minute = time[1]; + var sec = 0; + var msec = 0; + + if (time[2].indexOf(".") != -1){ + var temp = time[2].split("."); + sec = temp[0]; + msec = temp[1]; + } else + sec = time[2]; + + return Date.UTC(year, month, day, hour, minute, sec, msec); +} diff --git a/public/css/images/cue_playlist.png b/public/css/images/cue_playlist.png new file mode 100644 index 000000000..b928ccfae Binary files /dev/null and b/public/css/images/cue_playlist.png differ diff --git a/public/css/images/icon_rebroadcast.png b/public/css/images/icon_rebroadcast.png new file mode 100644 index 000000000..3a6c19571 Binary files /dev/null and b/public/css/images/icon_rebroadcast.png differ diff --git a/public/css/images/icon_rebroadcast_m.png b/public/css/images/icon_rebroadcast_m.png new file mode 100644 index 000000000..585a7d31f Binary files /dev/null and b/public/css/images/icon_rebroadcast_m.png differ diff --git a/public/css/images/icon_record.png b/public/css/images/icon_record.png new file mode 100644 index 000000000..020ebe283 Binary files /dev/null and b/public/css/images/icon_record.png differ diff --git a/public/css/images/icon_record_m.png b/public/css/images/icon_record_m.png new file mode 100644 index 000000000..50b3ebe15 Binary files /dev/null and b/public/css/images/icon_record_m.png differ diff --git a/public/css/playlist_builder.css b/public/css/playlist_builder.css index dd1ad24b1..bd0ecc44f 100644 --- a/public/css/playlist_builder.css +++ b/public/css/playlist_builder.css @@ -15,7 +15,11 @@ #spl_sortable > li, #side_playlist > div, #spl_editor, -.spl_artist { +.spl_artist, +.spl_cue_in, +.spl_fade_in, +.spl_cue_out, +.spl_fade_out { clear: left; } @@ -35,8 +39,8 @@ #spl_sortable { list-style: none; padding:0; - height: 400px; - overflow-y: scroll; + height: 300px; + overflow: auto; width:100%; margin-top:0; } @@ -52,6 +56,10 @@ border: none; } +#spl_name { + +} + .ui-icon-closethick { margin-top: 7px; } @@ -72,10 +80,18 @@ font-size:12px; } +/*#spl_editor { + height: 50px; +}*/ + +#spl_editor > div > span { +/* display: inline-block; + width: 150px;*/ +} + .ui-icon-closethick, .ui-icon-play, .spl_fade_control, -.spl_playlength, .spl_text_input { cursor: pointer; } @@ -239,13 +255,12 @@ margin: 0; } -dd.edit-error { +.edit-error { color:#b80000; margin:0; padding-bottom:0; font-size:12px; display:none; - clear: left; } /*.edit-error:last-child { @@ -276,3 +291,26 @@ dd.edit-error { top: 3px; z-index: 3; } + +#spl_sortable li .spl_cue { + background-color: transparent; + float:right; + font-size: 9px; + height: 15px; + right: 35px; + width: 33px; + margin-top:2px; + cursor:pointer; +} +#spl_sortable li .spl_cue.ui-state-default { + background: transparent url(images/cue_playlist.png) no-repeat 0 0; + border:none; +} +#spl_sortable li .spl_cue.ui-state-default:hover { + background: transparent url(images/cue_playlist.png) no-repeat 0 -15px; + border:none; +} +#spl_sortable li .spl_cue.ui-state-active, #spl_sortable li .spl_cue.ui-state-active:hover { + background: transparent url(images/cue_playlist.png) no-repeat 0 -30px; + border:none; +} \ No newline at end of file diff --git a/public/css/styles.css b/public/css/styles.css index 4e5e20a0c..7d0dc8690 100644 --- a/public/css/styles.css +++ b/public/css/styles.css @@ -184,7 +184,7 @@ select { .progressbar .progress-show-error { background:#d40000 url(images/progressbar_show_error.png) repeat-x 0 0; } -.now-playing-info .lenght { +.now-playing-info .show-length { color:#c4c4c4; padding-left:6px; } @@ -196,6 +196,7 @@ select { .time-info-block { padding:0 14px 0 2px; background:url(images/masterpanel_spacer.png) no-repeat right 0; + min-width:105px; } .time-info-block ul { margin:0; @@ -825,7 +826,6 @@ div.ui-datepicker { #schedule_playlist_chosen li > h3 > span.ui-icon.ui-icon-triangle-1-e, #schedule_playlist_chosen li > h3 > span.ui-icon.ui-icon-triangle-1-s { float:left; - margin-right: 8px; } #schedule_playlist_chosen li > h3 > span.ui-icon.ui-icon-close { @@ -1219,6 +1219,16 @@ ul.errors li { margin-bottom:2px; border:1px solid #c83f3f; } + +div.success{ + color:#3B5323; + font-size:11px; + padding:2px 4px; + background:#93DB70; + margin-bottom:2px; + border:1px solid #488214; +} + .collapsible-header { border: 1px solid #8f8f8f; background-color: #cccccc; @@ -1474,9 +1484,35 @@ ul.errors li { margin:-4px 3px -3px 0; float:right; } - .time-flow { - float:right; - margin-right:4px; + float:right; + margin-right:4px; +} +.small-icon { + display:block; + width:20px; + height:10px; + float:right; + margin-left:3px; +} +.small-icon.recording { + background:url(images/icon_record.png) no-repeat 0 0; +} +.small-icon.rebroadcast { + background:url(images/icon_rebroadcast.png) no-repeat 0 0; } +.medium-icon { + display:block; + width:25px; + height:12px; + float:right; + margin-left:4px; +} +.medium-icon.recording { + background:url(images/icon_record_m.png) no-repeat 0 0; +} +.medium-icon.rebroadcast { + background:url(images/icon_rebroadcast_m.png) no-repeat 0 0; +} + diff --git a/public/js/airtime/library/spl.js b/public/js/airtime/library/spl.js index e32e83f62..89203600d 100644 --- a/public/js/airtime/library/spl.js +++ b/public/js/airtime/library/spl.js @@ -205,13 +205,15 @@ function openFadeEditor(event) { function openCueEditor(event) { event.stopPropagation(); - var pos, url, li; + var pos, url, li, icon; li = $(this).parent().parent().parent(); + icon = $(this); pos = li.attr("id").split("_").pop(); if(li.hasClass("ui-state-active")) { li.removeClass("ui-state-active"); + icon.attr("class", "spl_cue ui-state-default"); $("#cues_"+pos) .empty() @@ -220,6 +222,7 @@ function openCueEditor(event) { return; } + icon.attr("class", "spl_cue ui-state-default ui-state-active"); url = '/Playlist/set-cue'; highlightActive(li); @@ -253,7 +256,8 @@ function setSPLContent(json) { $("#spl_sortable .ui-icon-closethick").click(deleteSPLItem); $(".spl_fade_control").click(openFadeEditor); - $(".spl_playlength").click(openCueEditor); + //$(".spl_playlength").click(openCueEditor); + $(".spl_cue").click(openCueEditor); return false; } @@ -487,7 +491,8 @@ function setUpSPL() { $("#spl_sortable .ui-icon-closethick").click(deleteSPLItem); $(".spl_fade_control").click(openFadeEditor); - $(".spl_playlength").click(openCueEditor); + //$(".spl_playlength").click(openCueEditor); + $(".spl_cue").click(openCueEditor); $("#spl_sortable").droppable(); $("#spl_sortable" ).bind( "drop", addSPLItem); diff --git a/public/js/airtime/schedule/add-show.js b/public/js/airtime/schedule/add-show.js index 50ca4058a..329391086 100644 --- a/public/js/airtime/schedule/add-show.js +++ b/public/js/airtime/schedule/add-show.js @@ -182,7 +182,6 @@ function setAddShowEvents() { }); form.find("#add-show-submit") - .button() .click(function(event){ event.preventDefault(); diff --git a/public/js/airtime/schedule/full-calendar-functions.js b/public/js/airtime/schedule/full-calendar-functions.js index 33df795b7..ce01e4583 100644 --- a/public/js/airtime/schedule/full-calendar-functions.js +++ b/public/js/airtime/schedule/full-calendar-functions.js @@ -165,6 +165,25 @@ function eventRender(event, element, view) { } $(element).find(".fc-event-title").after(div); + } + + //add the record/rebroadcast icons if needed. + if((view.name === 'agendaDay' || view.name === 'agendaWeek') && event.record === 1) { + + $(element).find(".fc-event-time").after(''); + } + if(view.name === 'month' && event.record === 1) { + + $(element).find(".fc-event-title").after(''); + } + + if((view.name === 'agendaDay' || view.name === 'agendaWeek') && event.rebroadcast === 1) { + + $(element).find(".fc-event-time").after(''); + } + if(view.name === 'month' && event.rebroadcast === 1) { + + $(element).find(".fc-event-title").after(''); } if(event.backgroundColor !== "") { diff --git a/public/js/airtime/user/user.js b/public/js/airtime/user/user.js index 4add2d4b1..6a518fdc1 100644 --- a/public/js/airtime/user/user.js +++ b/public/js/airtime/user/user.js @@ -2,6 +2,7 @@ function populateForm(entries){ //$('#user_details').show(); $('.errors').remove(); + $('.success').remove(); $('#user_id').val(entries.id); $('#login').val(entries.login); diff --git a/public/js/playlist/playlist.js b/public/js/playlist/playlist.js index 82f277d1c..a72e94dc2 100644 --- a/public/js/playlist/playlist.js +++ b/public/js/playlist/playlist.js @@ -165,11 +165,12 @@ function updatePlaybar(){ /* Column 1 update */ $('#playlist').text("Current Show:"); + var recElem = $('.recording-show'); if (currentShow.length > 0){ $('#playlist').text(currentShow[0].name); - - var recElem = $('.recording-show'); - currentShow[0].record ? recElem.show(): recElem.hide(); + (currentShow[0].record == "1") ? recElem.show(): recElem.hide(); + } else { + recElem.hide(); } $('#show-length').empty(); diff --git a/pypo/api_clients/api_client_factory.py b/pypo/api_clients/api_client_factory.py deleted file mode 100644 index 4762b0fc7..000000000 --- a/pypo/api_clients/api_client_factory.py +++ /dev/null @@ -1,9 +0,0 @@ -import airtime_api_client -import obp_api_client - -def create_api_client(config): - if config["api_client"] == "airtime": - return campcaster_api_client.AirtimeApiClient(config) - elif config["api_client"] == "obp": - return obp_api_client.ObpApiClient(config) - diff --git a/pypo/debug.log b/pypo/debug.log deleted file mode 100644 index e69de29bb..000000000 diff --git a/pypo/error.log b/pypo/error.log deleted file mode 100644 index e69de29bb..000000000 diff --git a/pypo/install/pypo-daemontools-fetch.sh b/pypo/install/pypo-daemontools-fetch.sh deleted file mode 100644 index 6e7bed55d..000000000 --- a/pypo/install/pypo-daemontools-fetch.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/sh -pypo_user="pypo" -export HOME="/home/pypo/" -# Location of pypo_cli.py Python script -pypo_path="/opt/pypo/bin/" -pypo_script="pypo-cli.py" -echo "*** Daemontools: starting daemon" -cd ${pypo_path} -exec 2>&1 -# Note the -u when calling python! we need it to get unbuffered binary stdout and stderr -exec setuidgid ${pypo_user} \ - python -u ${pypo_path}${pypo_script} \ - -f -# EOF diff --git a/pypo/install/pypo-stop.py b/pypo/install/pypo-stop.py deleted file mode 100644 index a27032313..000000000 --- a/pypo/install/pypo-stop.py +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import os -import sys - -if os.geteuid() != 0: - print "Please run this as root." - sys.exit(1) - -try: - print "Stopping daemontool script pypo-fetch" - os.system("svc -dx /etc/service/pypo-fetch 2>/dev/null") - - print "Stopping daemontool script pypo-push" - os.system("svc -dx /etc/service/pypo-push 2>/dev/null") - - print "Stopping daemontool script pypo-liquidsoap" - os.system("svc -dx /etc/service/pypo-liquidsoap 2>/dev/null") - os.system("killall liquidsoap") - -except Exception, e: - print "exception:" + str(e) diff --git a/pypo/logging.cfg b/pypo/logging.cfg deleted file mode 100644 index ee3cd2bee..000000000 --- a/pypo/logging.cfg +++ /dev/null @@ -1,60 +0,0 @@ -[loggers] -keys=root - -[handlers] -keys=consoleHandler,fileHandlerERROR,fileHandlerDEBUG,nullHandler - -[formatters] -keys=simpleFormatter - -[logger_root] -level=DEBUG -handlers=consoleHandler,fileHandlerERROR,fileHandlerDEBUG - -[logger_libs] -handlers=nullHandler -level=DEBUG -qualname="process" -propagate=0 - - -[handler_consoleHandler] -class=StreamHandler -level=DEBUG -formatter=simpleFormatter -args=(sys.stdout,) - -[handler_fileHandlerERROR] -class=FileHandler -level=WARNING -formatter=simpleFormatter -args=("./error.log",) - -[handler_fileHandlerDEBUG] -class=FileHandler -level=DEBUG -formatter=simpleFormatter -args=("./debug.log",) - -[handler_nullHandler] -class=FileHandler -level=DEBUG -formatter=simpleFormatter -args=("/dev/null",) - - -[formatter_simpleFormatter] -format=%(asctime)s %(levelname)s - [%(filename)s : %(funcName)s() : line %(lineno)d] - %(message)s -datefmt= - - -## multitail color sheme -## pyml / python -# colorscheme:pyml:www.obp.net -# cs_re:blue:\[[^ ]*\] -# cs_re:red:CRITICAL:* -# cs_re:red,black,blink:ERROR:* -# cs_re:blue:NOTICE:* -# cs_re:cyan:INFO:* -# cs_re:green:DEBUG:* - diff --git a/pypo/scripts/silence.mp3 b/pypo/scripts/silence.mp3 deleted file mode 100644 index 374dfe286..000000000 Binary files a/pypo/scripts/silence.mp3 and /dev/null differ diff --git a/pypo/api_clients/__init__.py b/python_apps/api_clients/__init__.py similarity index 100% rename from pypo/api_clients/__init__.py rename to python_apps/api_clients/__init__.py diff --git a/pypo/api_clients/api_client.py b/python_apps/api_clients/api_client.py similarity index 91% rename from pypo/api_clients/api_client.py rename to python_apps/api_clients/api_client.py index 6994cf3fd..cd2a6e7d7 100644 --- a/pypo/api_clients/api_client.py +++ b/python_apps/api_clients/api_client.py @@ -13,6 +13,7 @@ import sys import time import urllib +import urllib2 import logging import json import os @@ -90,6 +91,12 @@ class ApiClientInterface: # You will be able to use this data in update_start_playing def get_liquidsoap_data(self, pkey, schedule): pass + + def get_shows_to_record(self): + pass + + def upload_recorded_show(self): + pass # Put here whatever tests you want to run to make sure your API is working def test(self): @@ -189,30 +196,10 @@ class AirTimeApiClient(ApiClientInterface): def get_schedule(self, start=None, end=None): logger = logging.getLogger() - - """ - calculate start/end time range (format: YYYY-DD-MM-hh-mm-ss,YYYY-DD-MM-hh-mm-ss) - (seconds are ignored, just here for consistency) - """ - tnow = time.localtime(time.time()) - if (not start): - tstart = time.localtime(time.time() - 3600 * int(self.config["cache_for"])) - start = "%04d-%02d-%02d-%02d-%02d" % (tstart[0], tstart[1], tstart[2], tstart[3], tstart[4]) - - if (not end): - tend = time.localtime(time.time() + 3600 * int(self.config["prepare_ahead"])) - end = "%04d-%02d-%02d-%02d-%02d" % (tend[0], tend[1], tend[2], tend[3], tend[4]) - - range = {} - range['start'] = start - range['end'] = end - + # Construct the URL export_url = self.config["base_url"] + self.config["api_base"] + self.config["export_url"] - # Insert the start and end times into the URL - export_url = export_url.replace('%%from%%', range['start']) - export_url = export_url.replace('%%to%%', range['end']) logger.info("Fetching schedule from %s", export_url) export_url = export_url.replace('%%api_key%%', self.config["api_key"]) @@ -225,24 +212,6 @@ class AirTimeApiClient(ApiClientInterface): except Exception, e: print e - #schedule = response["playlists"] - #scheduleKeys = sorted(schedule.iterkeys()) - # - ## Remove all playlists that have passed current time - #try: - # tnow = time.localtime(time.time()) - # str_tnow_s = "%04d-%02d-%02d-%02d-%02d-%02d" % (tnow[0], tnow[1], tnow[2], tnow[3], tnow[4], tnow[5]) - # toRemove = [] - # for pkey in scheduleKeys: - # if (str_tnow_s > schedule[pkey]['end']): - # toRemove.append(pkey) - # else: - # break - # for index in toRemove: - # del schedule[index] - #except Exception, e: - #response["playlists"] = schedule - return status, response @@ -316,6 +285,41 @@ class AirTimeApiClient(ApiClientInterface): except Exception, e: data["schedule_id"] = 0 return data + + def get_shows_to_record(self): + logger = logging.getLogger() + response = '' + try: + url = self.config["base_url"] + self.config["api_base"] + self.config["show_schedule_url"] + logger.debug(url) + url = url.replace("%%api_key%%", self.config["api_key"]) + + response = urllib.urlopen(url) + response = json.loads(response.read()) + logger.info("shows %s", response) + + except Exception, e: + logger.error("Exception: %s", e) + + return response[u'shows'] + + def upload_recorded_show(self, data, headers): + logger = logging.getLogger() + response = '' + try: + url = self.config["base_url"] + self.config["api_base"] + self.config["upload_file_url"] + logger.debug(url) + url = url.replace("%%api_key%%", self.config["api_key"]) + + request = urllib2.Request(url, data, headers) + response = urllib2.urlopen(request).read().strip() + + logger.info("uploaded show result %s", response) + + except Exception, e: + logger.error("Exception: %s", e) + + return response diff --git a/python_apps/eggs/poster-0.8.0-py2.6.egg b/python_apps/eggs/poster-0.8.0-py2.6.egg deleted file mode 100644 index 7af5c8f5e..000000000 Binary files a/python_apps/eggs/poster-0.8.0-py2.6.egg and /dev/null differ diff --git a/pypo/AUTHORS b/python_apps/pypo/AUTHORS similarity index 100% rename from pypo/AUTHORS rename to python_apps/pypo/AUTHORS diff --git a/pypo/LICENSE b/python_apps/pypo/LICENSE similarity index 100% rename from pypo/LICENSE rename to python_apps/pypo/LICENSE diff --git a/pypo/config.cfg b/python_apps/pypo/config.cfg similarity index 86% rename from pypo/config.cfg rename to python_apps/pypo/config.cfg index 638558fbf..2c0fce6f0 100644 --- a/pypo/config.cfg +++ b/python_apps/pypo/config.cfg @@ -26,6 +26,13 @@ base_url = 'http://localhost/' ls_host = '127.0.0.1' ls_port = '1234' +############################################ +# RabbitMQ settings # +############################################ +rabbitmq_host = 'localhost' +rabbitmq_user = 'guest' +rabbitmq_password = 'guest' + ############################################ # pypo preferences # ############################################ @@ -34,15 +41,13 @@ cache_for = 24 #how long to hold the cache, in hours # Poll interval in seconds. # +# This will rarely need to be changed because any schedule changes are +# automatically sent to pypo immediately. +# # This is how often the poll script downloads new schedules and files from the -# server. +# server in the event that no changes are made to the schedule. # -# For production use, this number depends on whether you plan on making any -# last-minute changes to your schedule. This number should be set to half of -# the time you expect to "lock-in" your schedule. So if your schedule is set -# 24 hours in advance, this can be set to poll every 12 hours. -# -poll_interval = 5 # in seconds. +poll_interval = 3600 # in seconds. # Push interval in seconds. @@ -52,7 +57,7 @@ poll_interval = 5 # in seconds. # # It's hard to imagine a situation where this should be more than 1 second. # -push_interval = 2 # in seconds +push_interval = 1 # in seconds # 'pre' or 'otf'. 'pre' cues while playlist preparation # while 'otf' (on the fly) cues while loading into ls @@ -80,7 +85,7 @@ version_url = 'version/api_key/%%api_key%%' # Schedule export path. # %%from%% - starting date/time in the form YYYY-MM-DD-hh-mm # %%to%% - starting date/time in the form YYYY-MM-DD-hh-mm -export_url = 'schedule/api_key/%%api_key%%/from/%%from%%/to/%%to%%' +export_url = 'schedule/api_key/%%api_key%%' # Update whether a schedule group has begun playing. update_item_url = 'notify-schedule-group-play/api_key/%%api_key%%/schedule_id/%%schedule_id%%' diff --git a/pypo/config.cfg.dist b/python_apps/pypo/config.cfg.dist similarity index 100% rename from pypo/config.cfg.dist rename to python_apps/pypo/config.cfg.dist diff --git a/pypo/dls/__init__.py b/python_apps/pypo/dls/__init__.py similarity index 100% rename from pypo/dls/__init__.py rename to python_apps/pypo/dls/__init__.py diff --git a/pypo/dls/dls_client.py b/python_apps/pypo/dls/dls_client.py similarity index 100% rename from pypo/dls/dls_client.py rename to python_apps/pypo/dls/dls_client.py diff --git a/pypo/install/pypo-daemontools-liquidsoap.sh b/python_apps/pypo/install/pypo-daemontools-liquidsoap.sh similarity index 61% rename from pypo/install/pypo-daemontools-liquidsoap.sh rename to python_apps/pypo/install/pypo-daemontools-liquidsoap.sh index 6f9061bfa..cd6177164 100644 --- a/pypo/install/pypo-daemontools-liquidsoap.sh +++ b/python_apps/pypo/install/pypo-daemontools-liquidsoap.sh @@ -1,10 +1,12 @@ #!/bin/sh ls_user="pypo" export HOME="/home/pypo/" +api_client_path="/opt/pypo/" ls_path="/opt/pypo/bin/liquidsoap/liquidsoap" ls_param="/opt/pypo/bin/scripts/ls_script.liq" echo "*** Daemontools: starting liquidsoap" exec 2>&1 -echo "exec sudo -u ${ls_user} ${ls_path} ${ls_param} " -cd /opt/pypo/bin/scripts && sudo -u ${ls_user} ${ls_path} ${ls_param} + +cd /opt/pypo/bin/scripts +sudo PYTHONPATH=${api_client_path} -u ${ls_user} ${ls_path} ${ls_param} # EOF diff --git a/pypo/install/pypo-daemontools-logger.sh b/python_apps/pypo/install/pypo-daemontools-logger.sh similarity index 100% rename from pypo/install/pypo-daemontools-logger.sh rename to python_apps/pypo/install/pypo-daemontools-logger.sh diff --git a/pypo/install/pypo-daemontools-push.sh b/python_apps/pypo/install/pypo-daemontools.sh similarity index 70% rename from pypo/install/pypo-daemontools-push.sh rename to python_apps/pypo/install/pypo-daemontools.sh index 4c5cc9f7c..1f276cc4e 100644 --- a/pypo/install/pypo-daemontools-push.sh +++ b/python_apps/pypo/install/pypo-daemontools.sh @@ -3,12 +3,16 @@ pypo_user="pypo" export HOME="/home/pypo/" # Location of pypo_cli.py Python script pypo_path="/opt/pypo/bin/" +api_client_path="/opt/pypo/" pypo_script="pypo-cli.py" echo "*** Daemontools: starting daemon" cd ${pypo_path} exec 2>&1 + +PYTHONPATH=${api_client_path}:$PYTHONPATH +export PYTHONPATH + # Note the -u when calling python! we need it to get unbuffered binary stdout and stderr exec setuidgid ${pypo_user} \ - python -u ${pypo_path}${pypo_script} \ - -p + python -u ${pypo_path}${pypo_script} # EOF diff --git a/pypo/install/pypo-install.py b/python_apps/pypo/install/pypo-install.py similarity index 71% rename from pypo/install/pypo-install.py rename to python_apps/pypo/install/pypo-install.py index 52746f009..1171dfe23 100644 --- a/pypo/install/pypo-install.py +++ b/python_apps/pypo/install/pypo-install.py @@ -36,14 +36,16 @@ def create_user(username): os.system("adduser --system --quiet --group --shell /bin/bash "+username) #set pypo password - p = os.popen('/usr/bin/passwd pypo', 'w') + p = os.popen('/usr/bin/passwd pypo 1>/dev/null 2>&1', 'w') p.write('pypo\n') p.write('pypo\n') p.close() else: print "User already exists." #add pypo to audio group - os.system("adduser " + username + " audio 2>&1 1>/dev/null") + os.system("adduser " + username + " audio 1>/dev/null 2>&1") + #add pypo to pulse-access group + #os.system("adduser " + username + " pulse-access 1>/dev/null 2>&1") def copy_dir(src_dir, dest_dir): if (os.path.exists(dest_dir)) and (dest_dir != "/"): @@ -63,7 +65,7 @@ def get_current_script_dir(): try: current_script_dir = get_current_script_dir() print "Checking and removing any existing pypo processes" - os.system("python %s/pypo-uninstall.py 2>&1 1>/dev/null"% current_script_dir) + os.system("python %s/pypo-uninstall.py 1>/dev/null 2>&1"% current_script_dir) time.sleep(5) # Create users @@ -79,14 +81,8 @@ try: create_path(BASE_PATH+"cache") create_path(BASE_PATH+"files") create_path(BASE_PATH+"tmp") - create_path(BASE_PATH+"files/basic") - create_path(BASE_PATH+"files/fallback") - create_path(BASE_PATH+"files/jingles") create_path(BASE_PATH+"archive") - - print "Copying pypo files" - shutil.copy("%s/../scripts/silence.mp3"%current_script_dir, BASE_PATH+"files/basic") - + if platform.architecture()[0] == '64bit': print "Installing 64-bit liquidsoap binary" shutil.copy("%s/../liquidsoap/liquidsoap64"%current_script_dir, "%s/../liquidsoap/liquidsoap"%current_script_dir) @@ -98,28 +94,21 @@ try: sys.exit(1) copy_dir("%s/.."%current_script_dir, BASE_PATH+"bin/") + copy_dir("%s/../../api_clients"%current_script_dir, BASE_PATH+"api_clients/") print "Setting permissions" os.system("chmod -R 755 "+BASE_PATH) os.system("chown -R pypo:pypo "+BASE_PATH) - print "Installing daemontool script pypo-fetch" - create_path("/etc/service/pypo-fetch") - create_path("/etc/service/pypo-fetch/log") - shutil.copy("%s/pypo-daemontools-fetch.sh"%current_script_dir, "/etc/service/pypo-fetch/run") - shutil.copy("%s/pypo-daemontools-logger.sh"%current_script_dir, "/etc/service/pypo-fetch/log/run") - os.system("chmod -R 755 /etc/service/pypo-fetch") - os.system("chown -R pypo:pypo /etc/service/pypo-fetch") + print "Installing pypo daemon" + create_path("/etc/service/pypo") + create_path("/etc/service/pypo/log") + shutil.copy("%s/pypo-daemontools.sh"%current_script_dir, "/etc/service/pypo/run") + shutil.copy("%s/pypo-daemontools-logger.sh"%current_script_dir, "/etc/service/pypo/log/run") + os.system("chmod -R 755 /etc/service/pypo") + os.system("chown -R pypo:pypo /etc/service/pypo") - print "Installing daemontool script pypo-push" - create_path("/etc/service/pypo-push") - create_path("/etc/service/pypo-push/log") - shutil.copy("%s/pypo-daemontools-push.sh"%current_script_dir, "/etc/service/pypo-push/run") - shutil.copy("%s/pypo-daemontools-logger.sh"%current_script_dir, "/etc/service/pypo-push/log/run") - os.system("chmod -R 755 /etc/service/pypo-push") - os.system("chown -R pypo:pypo /etc/service/pypo-push") - - print "Installing daemontool script pypo-liquidsoap" + print "Installing liquidsoap daemon" create_path("/etc/service/pypo-liquidsoap") create_path("/etc/service/pypo-liquidsoap/log") shutil.copy("%s/pypo-daemontools-liquidsoap.sh"%current_script_dir, "/etc/service/pypo-liquidsoap/run") @@ -134,16 +123,12 @@ try: found = True - p = Popen('svstat /etc/service/pypo-fetch', shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True) + p = Popen('svstat /etc/service/pypo', shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True) output = p.stdout.read() if (output.find("unable to open supervise/ok: file does not exist") >= 0): found = False print output - - p = Popen('svstat /etc/service/pypo-push', shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True) - output = p.stdout.read() - print output - + p = Popen('svstat /etc/service/pypo-liquidsoap', shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True) output = p.stdout.read() print output @@ -152,6 +137,7 @@ try: print "Pypo install has completed, but daemontools is not running, please make sure you have it installed and then reboot." except Exception, e: print "exception:" + str(e) + sys.exit(1) diff --git a/pypo/install/pypo-start.py b/python_apps/pypo/install/pypo-start.py similarity index 61% rename from pypo/install/pypo-start.py rename to python_apps/pypo/install/pypo-start.py index f23794af1..9d5811638 100644 --- a/pypo/install/pypo-start.py +++ b/python_apps/pypo/install/pypo-start.py @@ -9,12 +9,9 @@ if os.geteuid() != 0: sys.exit(1) try: - print "Starting daemontool script pypo-fetch" - os.system("svc -u /etc/service/pypo-fetch") - - print "Starting daemontool script pypo-push" - os.system("svc -u /etc/service/pypo-push") - + print "Starting daemontool script pypo" + os.system("svc -u /etc/service/pypo") + print "Starting daemontool script pypo-liquidsoap" os.system("svc -u /etc/service/pypo-liquidsoap") diff --git a/python_apps/pypo/install/pypo-stop.py b/python_apps/pypo/install/pypo-stop.py new file mode 100644 index 000000000..388c0bc4b --- /dev/null +++ b/python_apps/pypo/install/pypo-stop.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import os +import sys + +if os.geteuid() != 0: + print "Please run this as root." + sys.exit(1) + +try: + print "Stopping daemontool script pypo" + os.system("svc -dx /etc/service/pypo 1>/dev/null 2>&1") + + if os.path.exists("/etc/service/pypo-fetch"): + os.system("svc -dx /etc/service/pypo-fetch 1>/dev/null 2>&1") + if os.path.exists("/etc/service/pypo-push"): + os.system("svc -dx /etc/service/pypo-push 1>/dev/null 2>&1") + + print "Stopping daemontool script pypo-liquidsoap" + os.system("svc -dx /etc/service/pypo-liquidsoap 1>/dev/null 2>&1") + os.system("killall liquidsoap") + +except Exception, e: + print "exception:" + str(e) diff --git a/pypo/install/pypo-uninstall.py b/python_apps/pypo/install/pypo-uninstall.py similarity index 69% rename from pypo/install/pypo-uninstall.py rename to python_apps/pypo/install/pypo-uninstall.py index 0b225cc7e..cd22e9c4c 100644 --- a/pypo/install/pypo-uninstall.py +++ b/python_apps/pypo/install/pypo-uninstall.py @@ -15,13 +15,13 @@ def remove_path(path): os.system("rm -rf " + path) def remove_user(username): - os.system("killall -u %s 2>&1 1>/dev/null" % username) + os.system("killall -u %s 1>/dev/null 2>&1" % username) #allow all process to be completely closed before we attempt to delete user print "Waiting for processes to close..." time.sleep(5) - os.system("deluser --remove-home " + username + " 1>/dev/null") + os.system("deluser --remove-home " + username + " 1>/dev/null 2>&1") def get_current_script_dir(): current_script_dir = os.path.realpath(__file__) @@ -37,16 +37,19 @@ try: print "Removing pypo files" remove_path(BASE_PATH) - print "Removing daemontool script pypo-fetch" - remove_path("rm -rf /etc/service/pypo-fetch") - - print "Removing daemontool script pypo-push" - remove_path("rm -rf /etc/service/pypo-push") - + print "Removing daemontool script pypo" + remove_path("/etc/service/pypo") + + if os.path.exists("/etc/service/pypo-fetch"): + remove_path("/etc/service/pypo-fetch") + + if os.path.exists("/etc/service/pypo-push"): + remove_path("/etc/service/pypo-push") + print "Removing daemontool script pypo-liquidsoap" - remove_path("rm -rf /etc/service/pypo-liquidsoap") + remove_path("/etc/service/pypo-liquidsoap") remove_user("pypo") - print "Uninstall complete." + print "Pypo uninstall complete." except Exception, e: print "exception:" + str(e) diff --git a/pypo/liquidsoap/liquidsoap32 b/python_apps/pypo/liquidsoap/liquidsoap32 similarity index 100% rename from pypo/liquidsoap/liquidsoap32 rename to python_apps/pypo/liquidsoap/liquidsoap32 diff --git a/pypo/liquidsoap/liquidsoap64 b/python_apps/pypo/liquidsoap/liquidsoap64 similarity index 100% rename from pypo/liquidsoap/liquidsoap64 rename to python_apps/pypo/liquidsoap/liquidsoap64 diff --git a/pypo/logging-api-validator.cfg b/python_apps/pypo/logging-api-validator.cfg similarity index 100% rename from pypo/logging-api-validator.cfg rename to python_apps/pypo/logging-api-validator.cfg diff --git a/python_apps/pypo/logging.cfg b/python_apps/pypo/logging.cfg new file mode 100644 index 000000000..7b679d40c --- /dev/null +++ b/python_apps/pypo/logging.cfg @@ -0,0 +1,34 @@ +[loggers] +keys=root,fetch,push + +[handlers] +keys=consoleHandler + +[formatters] +keys=simpleFormatter + +[logger_root] +level=DEBUG +handlers=consoleHandler + +[logger_fetch] +level=DEBUG +handlers=consoleHandler +qualname=fetch +propagate=0 + +[logger_push] +level=DEBUG +handlers=consoleHandler +qualname=push +propagate=0 + +[handler_consoleHandler] +class=StreamHandler +level=DEBUG +formatter=simpleFormatter +args=(sys.stdout,) + +[formatter_simpleFormatter] +format=%(asctime)s %(levelname)s - [%(filename)s : %(funcName)s() : line %(lineno)d] - %(message)s +datefmt= diff --git a/pypo/pypo-api-validator.py b/python_apps/pypo/pypo-api-validator.py similarity index 100% rename from pypo/pypo-api-validator.py rename to python_apps/pypo/pypo-api-validator.py diff --git a/pypo/pypo-cli.py b/python_apps/pypo/pypo-cli.py similarity index 79% rename from pypo/pypo-cli.py rename to python_apps/pypo/pypo-cli.py index 3c0402908..208049d7b 100755 --- a/pypo/pypo-cli.py +++ b/python_apps/pypo/pypo-cli.py @@ -7,14 +7,13 @@ Python part of radio playout (pypo) The main functions are "fetch" (./pypo_cli.py -f) and "push" (./pypo_cli.py -p) """ -# python defaults (debian default) import time #import calendar - - #import traceback from optparse import * import sys +import os +import signal #import datetime import logging import logging.config @@ -27,11 +26,11 @@ import logging.config #import string #import operator #import inspect +from Queue import Queue from pypopush import PypoPush from pypofetch import PypoFetch -# additional modules (should be checked) from configobj import ConfigObj # custom imports @@ -53,7 +52,6 @@ parser.add_option("-v", "--compat", help="Check compatibility with server API ve parser.add_option("-t", "--test", help="Do a test to make sure everything is working properly.", default=False, action="store_true", dest="test") parser.add_option("-f", "--fetch-scheduler", help="Fetch the schedule from server. This is a polling process that runs forever.", default=False, action="store_true", dest="fetch_scheduler") parser.add_option("-p", "--push-scheduler", help="Push the schedule to Liquidsoap. This is a polling process that runs forever.", default=False, action="store_true", dest="push_scheduler") - parser.add_option("-b", "--cleanup", help="Cleanup", default=False, action="store_true", dest="cleanup") parser.add_option("-c", "--check", help="Check the cached schedule and exit", default=False, action="store_true", dest="check") @@ -66,10 +64,6 @@ logging.config.fileConfig("logging.cfg") # loading config file try: config = ConfigObj('config.cfg') - POLL_INTERVAL = float(config['poll_interval']) - PUSH_INTERVAL = float(config['push_interval']) - LS_HOST = config['ls_host'] - LS_PORT = config['ls_port'] except Exception, e: print 'Error loading config file: ', e sys.exit() @@ -121,55 +115,44 @@ class Global: for media in playlist['medias']: print media +def keyboardInterruptHandler(signum, frame): + print "\nKeyboard Interrupt\n" + sys.exit(); if __name__ == '__main__': print '###########################################' print '# *** pypo *** #' - print '# Liquidsoap + External Scheduler #' - print '# Playout System #' + print '# Liquidsoap Scheduled Playout System #' print '###########################################' + signal.signal(signal.SIGINT, keyboardInterruptHandler) + # initialize g = Global() g.selfcheck() logger = logging.getLogger() - loops = 0 if options.test: g.test_api() sys.exit() - - if options.fetch_scheduler: - pf = PypoFetch() - while True: - try: pf.fetch('scheduler') - except Exception, e: - print e - sys.exit() + q = Queue() - if (loops%2 == 0): - logger.info("heartbeat") - loops += 1 - time.sleep(POLL_INTERVAL) + pp = PypoPush(q) + pp.daemon = True + pp.start() - if options.push_scheduler: - pp = PypoPush() - while True: - try: pp.push('scheduler') - except Exception, e: - print 'PUSH ERROR!! WILL EXIT NOW:(' - print e - sys.exit() + pf = PypoFetch(q) + pf.daemon = True + pf.start() - if (loops%60 == 0): - logger.info("heartbeat") - - loops += 1 - time.sleep(PUSH_INTERVAL) + while True: time.sleep(3600) + #pp.join() + #pf.join() +""" if options.check: try: g.check_schedule() except Exception, e: @@ -179,4 +162,4 @@ if __name__ == '__main__': try: pf.cleanup('scheduler') except Exception, e: print e - sys.exit() +""" diff --git a/pypo/pypo-cue-in-validator.py b/python_apps/pypo/pypo-cue-in-validator.py similarity index 100% rename from pypo/pypo-cue-in-validator.py rename to python_apps/pypo/pypo-cue-in-validator.py diff --git a/pypo/pypo-dls.py b/python_apps/pypo/pypo-dls.py similarity index 100% rename from pypo/pypo-dls.py rename to python_apps/pypo/pypo-dls.py diff --git a/pypo/pypo-log.sh b/python_apps/pypo/pypo-log.sh similarity index 100% rename from pypo/pypo-log.sh rename to python_apps/pypo/pypo-log.sh diff --git a/pypo/pypo-notify.py b/python_apps/pypo/pypo-notify.py similarity index 100% rename from pypo/pypo-notify.py rename to python_apps/pypo/pypo-notify.py diff --git a/pypo/pypofetch.py b/python_apps/pypo/pypofetch.py similarity index 50% rename from pypo/pypofetch.py rename to python_apps/pypo/pypofetch.py index aaeda54f5..2958c6bd4 100644 --- a/pypo/pypofetch.py +++ b/python_apps/pypo/pypofetch.py @@ -9,146 +9,151 @@ import random import string import json import telnetlib +import math +from threading import Thread +from subprocess import Popen, PIPE + +# For RabbitMQ +from kombu.connection import BrokerConnection +from kombu.messaging import Exchange, Queue, Consumer, Producer from api_clients import api_client from util import CueFile from configobj import ConfigObj +# configure logging +logging.config.fileConfig("logging.cfg") + # loading config file try: config = ConfigObj('config.cfg') - POLL_INTERVAL = float(config['poll_interval']) - PUSH_INTERVAL = 0.5 - #PUSH_INTERVAL = float(config['push_interval']) LS_HOST = config['ls_host'] LS_PORT = config['ls_port'] + POLL_INTERVAL = int(config['poll_interval']) + except Exception, e: print 'Error loading config file: ', e sys.exit() -class PypoFetch: - def __init__(self): +# Yuk - using a global, i know! +SCHEDULE_PUSH_MSG = [] + +""" +Handle a message from RabbitMQ, put it into our yucky global var. +Hopefully there is a better way to do this. +""" +def handle_message(body, message): + logger = logging.getLogger('fetch') + global SCHEDULE_PUSH_MSG + logger.info("Received schedule from RabbitMQ: " + message.body) + SCHEDULE_PUSH_MSG = json.loads(message.body) + # ACK the message to take it off the queue + message.ack() + + +class PypoFetch(Thread): + def __init__(self, q): + Thread.__init__(self) + logger = logging.getLogger('fetch') self.api_client = api_client.api_client_factory(config) self.cue_file = CueFile() self.set_export_source('scheduler') + self.queue = q + + logger.info("Initializing RabbitMQ stuff") + schedule_exchange = Exchange("airtime-schedule", "direct", durable=True, auto_delete=True) + schedule_queue = Queue("pypo-fetch", exchange=schedule_exchange, key="foo") + self.connection = BrokerConnection(config["rabbitmq_host"], config["rabbitmq_user"], config["rabbitmq_password"], "/") + channel = self.connection.channel() + consumer = Consumer(channel, schedule_queue) + consumer.register_callback(handle_message) + consumer.consume() + + logger.info("PypoFetch: init complete") + def set_export_source(self, export_source): self.export_source = export_source self.cache_dir = config["cache_dir"] + self.export_source + '/' - self.schedule_file = self.cache_dir + 'schedule.pickle' - self.schedule_tracker_file = self.cache_dir + "schedule_tracker.pickle" + + def check_matching_timezones(self, server_timezone): + logger = logging.getLogger('fetch') + + process = Popen(["date", "+%z"], stdout=PIPE) + pypo_timezone = (process.communicate()[0]).strip(' \r\n\t') + + if server_timezone != pypo_timezone: + logger.error("Server and pypo timezone offsets do not match. Audio playback may not start when expected!") + logger.error("Server timezone offset: %s", server_timezone) + logger.error("Pypo timezone offset: %s", pypo_timezone) """ - Fetching part of pypo - - Reads the scheduled entries of a given range (actual time +/- "prepare_ahead" / "cache_for") - - Saves a serialized file of the schedule - - playlists are prepared. (brought to liquidsoap format) and, if not mounted via nsf, files are copied - to the cache dir (Folder-structure: cache/YYYY-MM-DD-hh-mm-ss) - - runs the cleanup routine, to get rid of unused cashed files + Process the schedule + - Reads the scheduled entries of a given range (actual time +/- "prepare_ahead" / "cache_for") + - Saves a serialized file of the schedule + - playlists are prepared. (brought to liquidsoap format) and, if not mounted via nsf, files are copied + to the cache dir (Folder-structure: cache/YYYY-MM-DD-hh-mm-ss) + - runs the cleanup routine, to get rid of unused cashed files """ - def fetch(self, export_source): - """ - wrapper script for fetching the whole schedule (in json) - """ - logger = logging.getLogger() + def process_schedule(self, schedule_data, export_source): + logger = logging.getLogger('fetch') + self.schedule = schedule_data["playlists"] - try: os.mkdir(self.cache_dir) - except Exception, e: pass - - # get schedule + self.check_matching_timezones(schedule_data["server_timezone"]) + + # Push stream metadata to liquidsoap + # TODO: THIS LIQUIDSOAP STUFF NEEDS TO BE MOVED TO PYPO-PUSH!!! + stream_metadata = schedule_data['stream_metadata'] try: - while self.get_schedule() != 1: - logger.warning("failed to read from export url") - time.sleep(1) + tn = telnetlib.Telnet(LS_HOST, LS_PORT) + #encode in latin-1 due to telnet protocol not supporting utf-8 + tn.write(('vars.stream_metadata_type %s\n' % stream_metadata['format']).encode('latin-1')) + tn.write(('vars.station_name %s\n' % stream_metadata['station_name']).encode('latin-1')) + tn.write('exit\n') + tn.read_all() + except Exception, e: + logger.error("Exception %s", e) + status = 0 + # Download all the media and put playlists in liquidsoap format + try: + playlists = self.prepare_playlists() except Exception, e: logger.error("%s", e) - # prepare the playlists - if config["cue_style"] == 'pre': - try: self.prepare_playlists_cue() - except Exception, e: logger.error("%s", e) - elif config["cue_style"] == 'otf': - try: self.prepare_playlists(self.export_source) - except Exception, e: logger.error("%s", e) + # Send the data to pypo-push + scheduled_data = dict() + scheduled_data['playlists'] = playlists + scheduled_data['schedule'] = self.schedule + scheduled_data['stream_metadata'] = schedule_data["stream_metadata"] + self.queue.put(scheduled_data) # cleanup try: self.cleanup(self.export_source) except Exception, e: logger.error("%s", e) - def get_schedule(self): - logger = logging.getLogger() - status, response = self.api_client.get_schedule() - - if status == 1: - logger.info("dump serialized schedule to %s", self.schedule_file) - schedule = response['playlists'] - stream_metadata = response['stream_metadata'] - try: - schedule_file = open(self.schedule_file, "w") - pickle.dump(schedule, schedule_file) - schedule_file.close() - - tn = telnetlib.Telnet(LS_HOST, LS_PORT) - - #encode in latin-1 due to telnet protocol not supporting utf-8 - tn.write(('vars.stream_metadata_type %s\n' % stream_metadata['format']).encode('latin-1')) - tn.write(('vars.station_name %s\n' % stream_metadata['station_name']).encode('latin-1')) - - tn.write('exit\n') - logger.debug(tn.read_all()) - - except Exception, e: - logger.error("Exception %s", e) - status = 0 - - return status - - #TODO this is a duplicate function!!! - def load_schedule(self): - logger = logging.getLogger() - schedule = None - - # create the file if it doesnt exist - if (not os.path.exists(self.schedule_file)): - logger.debug('creating file ' + self.schedule_file) - open(self.schedule_file, 'w').close() - else: - # load the schedule from cache - #logger.debug('loading schedule file '+self.schedule_file) - try: - schedule_file = open(self.schedule_file, "r") - schedule = pickle.load(schedule_file) - schedule_file.close() - - except Exception, e: - logger.error('%s', e) - - return schedule - """ - Alternative version of playout preparation. Every playlist entry is - pre-cued if neccessary (cue_in/cue_out != 0) and stored in the - playlist folder. - file is eg 2010-06-23-15-00-00/17_cue_10.132-123.321.mp3 + In this function every audio file is cut as necessary (cue_in/cue_out != 0) + and stored in a playlist folder. + file is e.g. 2010-06-23-15-00-00/17_cue_10.132-123.321.mp3 """ - def prepare_playlists_cue(self): - logger = logging.getLogger() + def prepare_playlists(self): + logger = logging.getLogger('fetch') - # Load schedule from disk - schedule = self.load_schedule() + schedule = self.schedule + playlists = dict() # Dont do anything if schedule is empty - if (not schedule): + if not schedule: logger.debug("Schedule is empty.") - return + return playlists scheduleKeys = sorted(schedule.iterkeys()) try: for pkey in scheduleKeys: - logger.info("found playlist at %s", pkey) + logger.info("Playlist starting at %s", pkey) playlist = schedule[pkey] # create playlist directory @@ -157,15 +162,15 @@ class PypoFetch: except Exception, e: pass - logger.debug('*****************************************') - logger.debug('pkey: ' + str(pkey)) - logger.debug('cached at : ' + self.cache_dir + str(pkey)) - logger.debug('subtype: ' + str(playlist['subtype'])) - logger.debug('played: ' + str(playlist['played'])) - logger.debug('schedule id: ' + str(playlist['schedule_id'])) - logger.debug('duration: ' + str(playlist['duration'])) - logger.debug('source id: ' + str(playlist['x_ident'])) - logger.debug('*****************************************') + #logger.debug('*****************************************') + #logger.debug('pkey: ' + str(pkey)) + #logger.debug('cached at : ' + self.cache_dir + str(pkey)) + #logger.debug('subtype: ' + str(playlist['subtype'])) + #logger.debug('played: ' + str(playlist['played'])) + #logger.debug('schedule id: ' + str(playlist['schedule_id'])) + #logger.debug('duration: ' + str(playlist['duration'])) + #logger.debug('source id: ' + str(playlist['x_ident'])) + #logger.debug('*****************************************') if int(playlist['played']) == 1: logger.info("playlist %s already played / sent to liquidsoap, so will ignore it", pkey) @@ -173,34 +178,32 @@ class PypoFetch: elif int(playlist['subtype']) > 0 and int(playlist['subtype']) < 5: ls_playlist = self.handle_media_file(playlist, pkey) - # write playlist file - plfile = open(self.cache_dir + str(pkey) + '/list.lsp', "w") - plfile.write(json.dumps(ls_playlist)) - plfile.close() - logger.info('ls playlist file written to %s', self.cache_dir + str(pkey) + '/list.lsp') - + playlists[pkey] = ls_playlist except Exception, e: logger.info("%s", e) + return playlists + + """ + Download and cache the media files. + This handles both remote and local files. + Returns an updated ls_playlist string. + """ def handle_media_file(self, playlist, pkey): - """ - This handles both remote and local files. - Returns an updated ls_playlist string. - """ ls_playlist = [] - logger = logging.getLogger() + logger = logging.getLogger('fetch') for media in playlist['medias']: logger.debug("Processing track %s", media['uri']) fileExt = os.path.splitext(media['uri'])[1] try: if str(media['cue_in']) == '0' and str(media['cue_out']) == '0': - logger.debug('No cue in/out detected for this file') + #logger.debug('No cue in/out detected for this file') dst = "%s%s/%s%s" % (self.cache_dir, str(pkey), str(media['id']), str(fileExt)) do_cue = False else: - logger.debug('Cue in/out detected') + #logger.debug('Cue in/out detected') dst = "%s%s/%s_cue_%s-%s%s" % \ (self.cache_dir, str(pkey), str(media['id']), str(float(media['cue_in']) / 1000), str(float(media['cue_out']) / 1000), str(fileExt)) do_cue = True @@ -225,7 +228,7 @@ class PypoFetch: % (str(media['export_source']), media['id'], 0, str(float(media['fade_in']) / 1000), \ str(float(media['fade_out']) / 1000), media['row_id'],dst) - logger.debug(pl_entry) + #logger.debug(pl_entry) """ Tracks are only added to the playlist if they are accessible @@ -239,7 +242,7 @@ class PypoFetch: entry['show_name'] = playlist['show_name'] ls_playlist.append(entry) - logger.debug("everything ok, adding %s to playlist", pl_entry) + #logger.debug("everything ok, adding %s to playlist", pl_entry) else: print 'zero-file: ' + dst + ' from ' + media['uri'] logger.warning("zero-size file - skipping %s. will not add it to playlist", dst) @@ -251,11 +254,15 @@ class PypoFetch: return ls_playlist + """ + Download a file from a remote server and store it in the cache. + """ def handle_remote_file(self, media, dst, do_cue): - logger = logging.getLogger() + logger = logging.getLogger('fetch') if do_cue == False: if os.path.isfile(dst): - logger.debug("file already in cache: %s", dst) + pass + #logger.debug("file already in cache: %s", dst) else: logger.debug("try to download %s", media['uri']) self.api_client.get_media(media['uri'], dst) @@ -296,12 +303,12 @@ class PypoFetch: logger.error("%s", e) + """ + Cleans up folders in cache_dir. Look for modification date older than "now - CACHE_FOR" + and deletes them. + """ def cleanup(self, export_source): - """ - Cleans up folders in cache_dir. Look for modification date older than "now - CACHE_FOR" - and deletes them. - """ - logger = logging.getLogger() + logger = logging.getLogger('fetch') offset = 3600 * int(config["cache_for"]) now = time.time() @@ -323,3 +330,41 @@ class PypoFetch: print e logger.error("%s", e) + + """ + Main loop of the thread: + Wait for schedule updates from RabbitMQ, but in case there arent any, + poll the server to get the upcoming schedule. + """ + def run(self): + logger = logging.getLogger('fetch') + + try: os.mkdir(self.cache_dir) + except Exception, e: pass + + # Bootstrap: since we are just starting up, we need to grab the + # most recent schedule. After that we can just wait for updates. + status, schedule_data = self.api_client.get_schedule() + if status == 1: + self.process_schedule(schedule_data, "scheduler") + logger.info("Bootstrap complete: got initial copy of the schedule") + + loops = 1 + while True: + logger.info("Loop #"+str(loops)) + try: + # Wait for messages from RabbitMQ. Timeout if we + # dont get any after POLL_INTERVAL. + self.connection.drain_events(timeout=POLL_INTERVAL) + # Hooray for globals! + schedule_data = SCHEDULE_PUSH_MSG + status = 1 + except: + # We didnt get a message for a while, so poll the server + # to get an updated schedule. + status, schedule_data = self.api_client.get_schedule() + + if status == 1: + self.process_schedule(schedule_data, "scheduler") + loops += 1 + diff --git a/pypo/pypopush.py b/python_apps/pypo/pypopush.py similarity index 63% rename from pypo/pypopush.py rename to python_apps/pypo/pypopush.py index 266443b8f..25bfddbf1 100644 --- a/pypo/pypopush.py +++ b/python_apps/pypo/pypopush.py @@ -7,29 +7,38 @@ import pickle import telnetlib import calendar import json +import math +from threading import Thread from api_clients import api_client from util import CueFile from configobj import ConfigObj +# configure logging +logging.config.fileConfig("logging.cfg") + # loading config file try: config = ConfigObj('config.cfg') - POLL_INTERVAL = float(config['poll_interval']) - PUSH_INTERVAL = 0.5 - #PUSH_INTERVAL = float(config['push_interval']) LS_HOST = config['ls_host'] LS_PORT = config['ls_port'] + PUSH_INTERVAL = 2 except Exception, e: - print 'Error loading config file: ', e + logger.error('Error loading config file %s', e) sys.exit() -class PypoPush: - def __init__(self): +class PypoPush(Thread): + def __init__(self, q): + Thread.__init__(self) self.api_client = api_client.api_client_factory(config) self.cue_file = CueFile() self.set_export_source('scheduler') + self.queue = q + + self.schedule = dict() + self.playlists = dict() + self.stream_metadata = dict() """ push_ahead2 MUST be < push_ahead. The difference in these two values @@ -42,51 +51,58 @@ class PypoPush: def set_export_source(self, export_source): self.export_source = export_source self.cache_dir = config["cache_dir"] + self.export_source + '/' - self.schedule_file = self.cache_dir + 'schedule.pickle' self.schedule_tracker_file = self.cache_dir + "schedule_tracker.pickle" """ - The Push Loop - the push loop periodically (minimal 1/2 of the playlist-grid) - checks if there is a playlist that should be scheduled at the current time. - If yes, the temporary liquidsoap playlist gets replaced with the corresponding one, + The Push Loop - the push loop periodically checks if there is a playlist + that should be scheduled at the current time. + If yes, the current liquidsoap playlist gets replaced with the corresponding one, then liquidsoap is asked (via telnet) to reload and immediately play it. """ def push(self, export_source): - logger = logging.getLogger() + logger = logging.getLogger('push') - self.schedule = self.load_schedule() - playedItems = self.load_schedule_tracker() + # get a new schedule from pypo-fetch + if not self.queue.empty(): + scheduled_data = self.queue.get() + logger.debug("Received data from pypo-fetch") + self.schedule = scheduled_data['schedule'] + self.playlists = scheduled_data['playlists'] + self.stream_metadata = scheduled_data['stream_metadata'] - tcoming = time.localtime(time.time() + self.push_ahead) - tcoming2 = time.localtime(time.time() + self.push_ahead2) + schedule = self.schedule + playlists = self.playlists - - str_tcoming_s = "%04d-%02d-%02d-%02d-%02d-%02d" % (tcoming[0], tcoming[1], tcoming[2], tcoming[3], tcoming[4], tcoming[5]) - str_tcoming2_s = "%04d-%02d-%02d-%02d-%02d-%02d" % (tcoming2[0], tcoming2[1], tcoming2[2], tcoming2[3], tcoming2[4], tcoming2[5]) - currently_on_air = False - if self.schedule == None: - logger.warn('Unable to loop schedule - maybe write in progress?') - logger.warn('Will try again in next loop.') + if schedule: + playedItems = self.load_schedule_tracker() - else: - for pkey in self.schedule: + timenow = time.time() + tcoming = time.localtime(timenow + self.push_ahead) + str_tcoming_s = "%04d-%02d-%02d-%02d-%02d-%02d" % (tcoming[0], tcoming[1], tcoming[2], tcoming[3], tcoming[4], tcoming[5]) + + tcoming2 = time.localtime(timenow + self.push_ahead2) + str_tcoming2_s = "%04d-%02d-%02d-%02d-%02d-%02d" % (tcoming2[0], tcoming2[1], tcoming2[2], tcoming2[3], tcoming2[4], tcoming2[5]) + + tnow = time.localtime(timenow) + str_tnow_s = "%04d-%02d-%02d-%02d-%02d-%02d" % (tnow[0], tnow[1], tnow[2], tnow[3], tnow[4], tnow[5]) + + for pkey in schedule: plstart = pkey[0:19] - start = self.schedule[pkey]['start'] - end = self.schedule[pkey]['end'] + start = schedule[pkey]['start'] + end = schedule[pkey]['end'] playedFlag = (pkey in playedItems) and playedItems[pkey].get("played", 0) if plstart == str_tcoming_s or (plstart < str_tcoming_s and plstart > str_tcoming2_s and not playedFlag): logger.debug('Preparing to push playlist scheduled at: %s', pkey) - playlist = self.schedule[pkey] + playlist = schedule[pkey] - ptype = playlist['subtype'] currently_on_air = True # We have a match, replace the current playlist and # force liquidsoap to refresh. - if (self.push_liquidsoap(pkey, self.schedule, ptype) == 1): + if (self.push_liquidsoap(pkey, schedule, playlists) == 1): logger.debug("Pushed to liquidsoap, updating 'played' status.") # Marked the current playlist as 'played' in the schedule tracker # so it is not called again in the next push loop. @@ -100,39 +116,28 @@ class PypoPush: # Call API to update schedule states logger.debug("Doing callback to server to update 'played' status.") - self.api_client.notify_scheduled_item_start_playing(pkey, self.schedule) - - if self.schedule != None: - tnow = time.localtime(time.time()) - str_tnow_s = "%04d-%02d-%02d-%02d-%02d-%02d" % (tnow[0], tnow[1], tnow[2], tnow[3], tnow[4], tnow[5]) - for pkey in self.schedule: - start = self.schedule[pkey]['start'] - end = self.schedule[pkey]['end'] + self.api_client.notify_scheduled_item_start_playing(pkey, schedule) + + start = schedule[pkey]['start'] + end = schedule[pkey]['end'] if start <= str_tnow_s and str_tnow_s < end: currently_on_air = True - + else: + pass + #logger.debug('Empty schedule') + if not currently_on_air: tn = telnetlib.Telnet(LS_HOST, LS_PORT) - tn.write('source.skip\n'.encode('latin-1')) + tn.write('source.skip\n') tn.write('exit\n') tn.read_all() - #logger.info('source.skip') - #logger.debug(tn.read_all()) - def push_liquidsoap(self, pkey, schedule, ptype): - logger = logging.getLogger() - src = self.cache_dir + str(pkey) + '/list.lsp' + def push_liquidsoap(self, pkey, schedule, playlists): + logger = logging.getLogger('push') try: - if True == os.access(src, os.R_OK): - logger.debug('OK - Can read playlist file') - - pl_file = open(src, "r") - file_content = pl_file.read() - pl_file.close() - logger.debug('file content: %s' % (file_content)) - playlist = json.loads(file_content) + playlist = playlists[pkey] #strptime returns struct_time in local time #mktime takes a time_struct and returns a floating point @@ -180,43 +185,24 @@ class PypoPush: except Exception, e: logger.error('%s', e) status = 0 - return status - - def load_schedule(self): - logger = logging.getLogger() - schedule = None - - # create the file if it doesnt exist - if (not os.path.exists(self.schedule_file)): - logger.debug('creating file ' + self.schedule_file) - open(self.schedule_file, 'w').close() - else: - # load the schedule from cache - #logger.debug('loading schedule file '+self.schedule_file) - try: - schedule_file = open(self.schedule_file, "r") - schedule = pickle.load(schedule_file) - schedule_file.close() - - except Exception, e: - logger.error('%s', e) - - return schedule - - def load_schedule_tracker(self): - logger = logging.getLogger() + logger = logging.getLogger('push') + #logger.debug('load_schedule_tracker') playedItems = dict() # create the file if it doesnt exist if (not os.path.exists(self.schedule_tracker_file)): - logger.debug('creating file ' + self.schedule_tracker_file) - schedule_tracker = open(self.schedule_tracker_file, 'w') - pickle.dump(playedItems, schedule_tracker) - schedule_tracker.close() + try: + logger.debug('creating file ' + self.schedule_tracker_file) + schedule_tracker = open(self.schedule_tracker_file, 'w') + pickle.dump(playedItems, schedule_tracker) + schedule_tracker.close() + except Exception, e: + logger.error('Error creating schedule tracker file: %s', e) else: + #logger.debug('schedule tracker file exists, opening: ' + self.schedule_tracker_file) try: schedule_tracker = open(self.schedule_tracker_file, "r") playedItems = pickle.load(schedule_tracker) @@ -226,3 +212,18 @@ class PypoPush: return playedItems + def run(self): + loops = 0 + heartbeat_period = math.floor(30/PUSH_INTERVAL) + logger = logging.getLogger('push') + + while True: + if loops % heartbeat_period == 0: + logger.info("heartbeat") + loops = 0 + try: self.push('scheduler') + except Exception, e: + logger.error('Pypo Push Error, exiting: %s', e) + sys.exit() + time.sleep(PUSH_INTERVAL) + loops += 1 diff --git a/pypo/scripts/library/Makefile b/python_apps/pypo/scripts/library/Makefile similarity index 100% rename from pypo/scripts/library/Makefile rename to python_apps/pypo/scripts/library/Makefile diff --git a/pypo/scripts/library/ask-liquidsoap.pl b/python_apps/pypo/scripts/library/ask-liquidsoap.pl similarity index 100% rename from pypo/scripts/library/ask-liquidsoap.pl rename to python_apps/pypo/scripts/library/ask-liquidsoap.pl diff --git a/pypo/scripts/library/ask-liquidsoap.rb b/python_apps/pypo/scripts/library/ask-liquidsoap.rb similarity index 100% rename from pypo/scripts/library/ask-liquidsoap.rb rename to python_apps/pypo/scripts/library/ask-liquidsoap.rb diff --git a/pypo/scripts/library/external-todo.liq b/python_apps/pypo/scripts/library/external-todo.liq similarity index 100% rename from pypo/scripts/library/external-todo.liq rename to python_apps/pypo/scripts/library/external-todo.liq diff --git a/pypo/scripts/library/externals.liq b/python_apps/pypo/scripts/library/externals.liq similarity index 100% rename from pypo/scripts/library/externals.liq rename to python_apps/pypo/scripts/library/externals.liq diff --git a/pypo/scripts/library/extract-replaygain b/python_apps/pypo/scripts/library/extract-replaygain similarity index 100% rename from pypo/scripts/library/extract-replaygain rename to python_apps/pypo/scripts/library/extract-replaygain diff --git a/pypo/scripts/library/interactive.screen b/python_apps/pypo/scripts/library/interactive.screen similarity index 100% rename from pypo/scripts/library/interactive.screen rename to python_apps/pypo/scripts/library/interactive.screen diff --git a/pypo/scripts/library/lastfm.liq b/python_apps/pypo/scripts/library/lastfm.liq similarity index 100% rename from pypo/scripts/library/lastfm.liq rename to python_apps/pypo/scripts/library/lastfm.liq diff --git a/pypo/scripts/library/liquidsoap.gentoo.initd b/python_apps/pypo/scripts/library/liquidsoap.gentoo.initd similarity index 100% rename from pypo/scripts/library/liquidsoap.gentoo.initd rename to python_apps/pypo/scripts/library/liquidsoap.gentoo.initd diff --git a/pypo/scripts/library/liquidsoap.gentoo.initd.in b/python_apps/pypo/scripts/library/liquidsoap.gentoo.initd.in similarity index 100% rename from pypo/scripts/library/liquidsoap.gentoo.initd.in rename to python_apps/pypo/scripts/library/liquidsoap.gentoo.initd.in diff --git a/pypo/scripts/library/liquidsoap.initd b/python_apps/pypo/scripts/library/liquidsoap.initd similarity index 100% rename from pypo/scripts/library/liquidsoap.initd rename to python_apps/pypo/scripts/library/liquidsoap.initd diff --git a/pypo/scripts/library/liquidsoap.initd.in b/python_apps/pypo/scripts/library/liquidsoap.initd.in similarity index 100% rename from pypo/scripts/library/liquidsoap.initd.in rename to python_apps/pypo/scripts/library/liquidsoap.initd.in diff --git a/pypo/scripts/library/liquidsoap.logrotate b/python_apps/pypo/scripts/library/liquidsoap.logrotate similarity index 100% rename from pypo/scripts/library/liquidsoap.logrotate rename to python_apps/pypo/scripts/library/liquidsoap.logrotate diff --git a/pypo/scripts/library/liquidsoap.logrotate.in b/python_apps/pypo/scripts/library/liquidsoap.logrotate.in similarity index 100% rename from pypo/scripts/library/liquidsoap.logrotate.in rename to python_apps/pypo/scripts/library/liquidsoap.logrotate.in diff --git a/pypo/scripts/library/liquidtts b/python_apps/pypo/scripts/library/liquidtts similarity index 100% rename from pypo/scripts/library/liquidtts rename to python_apps/pypo/scripts/library/liquidtts diff --git a/pypo/scripts/library/liquidtts.in b/python_apps/pypo/scripts/library/liquidtts.in similarity index 100% rename from pypo/scripts/library/liquidtts.in rename to python_apps/pypo/scripts/library/liquidtts.in diff --git a/pypo/scripts/library/pervasives.liq b/python_apps/pypo/scripts/library/pervasives.liq similarity index 100% rename from pypo/scripts/library/pervasives.liq rename to python_apps/pypo/scripts/library/pervasives.liq diff --git a/pypo/scripts/library/shoutcast.liq b/python_apps/pypo/scripts/library/shoutcast.liq similarity index 100% rename from pypo/scripts/library/shoutcast.liq rename to python_apps/pypo/scripts/library/shoutcast.liq diff --git a/pypo/scripts/library/test.liq b/python_apps/pypo/scripts/library/test.liq similarity index 100% rename from pypo/scripts/library/test.liq rename to python_apps/pypo/scripts/library/test.liq diff --git a/pypo/scripts/library/typing.liq b/python_apps/pypo/scripts/library/typing.liq similarity index 100% rename from pypo/scripts/library/typing.liq rename to python_apps/pypo/scripts/library/typing.liq diff --git a/pypo/scripts/library/utils.liq b/python_apps/pypo/scripts/library/utils.liq similarity index 100% rename from pypo/scripts/library/utils.liq rename to python_apps/pypo/scripts/library/utils.liq diff --git a/pypo/scripts/ls_config.liq b/python_apps/pypo/scripts/ls_config.liq similarity index 97% rename from pypo/scripts/ls_config.liq rename to python_apps/pypo/scripts/ls_config.liq index ef1caa198..20b1f4215 100644 --- a/pypo/scripts/ls_config.liq +++ b/python_apps/pypo/scripts/ls_config.liq @@ -44,5 +44,5 @@ icecast_description = "Airtime Radio!" icecast_genre = "genre" output_sound_device = false -output_icecast_vorbis = true +output_icecast_vorbis = false output_icecast_mp3 = true diff --git a/pypo/scripts/ls_lib.liq b/python_apps/pypo/scripts/ls_lib.liq similarity index 80% rename from pypo/scripts/ls_lib.liq rename to python_apps/pypo/scripts/ls_lib.liq index 27ed94f06..5bd611878 100644 --- a/pypo/scripts/ls_lib.liq +++ b/python_apps/pypo/scripts/ls_lib.liq @@ -1,14 +1,14 @@ def notify(m) - system("./notify.sh --data='#{!pypo_data}' --media-id=#{m['schedule_table_id']}") - print("./notify.sh --data='#{!pypo_data}' --media-id=#{m['schedule_table_id']}") + system("/opt/pypo/bin/scripts/notify.sh --data='#{!pypo_data}' --media-id=#{m['schedule_table_id']}") + print("/opt/pypo/bin/scripts/notify.sh --data='#{!pypo_data}' --media-id=#{m['schedule_table_id']}") end # A function applied to each metadata chunk def append_title(m) = if !stream_metadata_type == 1 then - [("artist","#{!show_name} - #{m['title']}")] - #elsif !stream_metadata_type == 2 then - # [("artist", ""), ("title", !show_name)] + [("artist","#{!show_name} - #{m['artist']}")] + #####elsif !stream_metadata_type == 2 then + ##### [("artist", ""), ("title", !show_name)] elsif !stream_metadata_type == 2 then [("artist",!station_name), ("title", !show_name)] else diff --git a/pypo/scripts/ls_script.liq b/python_apps/pypo/scripts/ls_script.liq similarity index 90% rename from pypo/scripts/ls_script.liq rename to python_apps/pypo/scripts/ls_script.liq index a35495839..5fec857ea 100644 --- a/pypo/scripts/ls_script.liq +++ b/python_apps/pypo/scripts/ls_script.liq @@ -24,7 +24,7 @@ server.register(namespace="vars", "show_name", fun (s) -> begin show_name := s s server.register(namespace="vars", "station_name", fun (s) -> begin station_name := s s end) -default = single(conservative=true, "/opt/pypo/files/basic/silence.mp3") +default = amplify(0.00001, noise()) default = rewrite_metadata([("artist","Airtime"), ("title", "offline")],default) s = fallback(track_sensitive=false, [queue, default]) @@ -69,6 +69,9 @@ if output_icecast_mp3 then end if output_icecast_vorbis then + #remove metadata from ogg source and merge tracks to fix bug + #with vlc and mplayer disconnecting at the end of every track + ogg_s = add(normalize=false, [amplify(0.00001, noise()),s]) out_vorbis = output.icecast(%vorbis, host = icecast_host, port = icecast_port, password = icecast_pass, mount = mount_point_vorbis, @@ -78,5 +81,5 @@ if output_icecast_vorbis then url = icecast_url, description = icecast_description, genre = icecast_genre, - s) + ogg_s) end diff --git a/pypo/scripts/notify.sh b/python_apps/pypo/scripts/notify.sh similarity index 55% rename from pypo/scripts/notify.sh rename to python_apps/pypo/scripts/notify.sh index 6298f13dd..07a70f5d0 100755 --- a/pypo/scripts/notify.sh +++ b/python_apps/pypo/scripts/notify.sh @@ -4,4 +4,10 @@ # needed here to keep dirs/configs clean # # and maybe to set user-rights # ############################################ -cd ../ && ./pypo-notify.py $1 $2 $3 $4 $5 $6 $7 $8 & + +# Absolute path to this script +SCRIPT=`readlink -f $0` +# Absolute path this script is in +SCRIPTPATH=`dirname $SCRIPT` + +cd ${SCRIPTPATH}/../ && ./pypo-notify.py $1 $2 $3 $4 $5 $6 $7 $8 & diff --git a/pypo/scripts/old_files/README b/python_apps/pypo/scripts/old_files/README similarity index 100% rename from pypo/scripts/old_files/README rename to python_apps/pypo/scripts/old_files/README diff --git a/pypo/scripts/old_files/cue_file.py b/python_apps/pypo/scripts/old_files/cue_file.py similarity index 100% rename from pypo/scripts/old_files/cue_file.py rename to python_apps/pypo/scripts/old_files/cue_file.py diff --git a/pypo/scripts/old_files/include_daypart.liq b/python_apps/pypo/scripts/old_files/include_daypart.liq similarity index 100% rename from pypo/scripts/old_files/include_daypart.liq rename to python_apps/pypo/scripts/old_files/include_daypart.liq diff --git a/pypo/scripts/old_files/include_dynamic_vars.liq b/python_apps/pypo/scripts/old_files/include_dynamic_vars.liq similarity index 100% rename from pypo/scripts/old_files/include_dynamic_vars.liq rename to python_apps/pypo/scripts/old_files/include_dynamic_vars.liq diff --git a/pypo/scripts/old_files/include_live_in.liq b/python_apps/pypo/scripts/old_files/include_live_in.liq similarity index 100% rename from pypo/scripts/old_files/include_live_in.liq rename to python_apps/pypo/scripts/old_files/include_live_in.liq diff --git a/pypo/scripts/old_files/include_notify.liq b/python_apps/pypo/scripts/old_files/include_notify.liq similarity index 100% rename from pypo/scripts/old_files/include_notify.liq rename to python_apps/pypo/scripts/old_files/include_notify.liq diff --git a/pypo/scripts/old_files/include_scheduler.liq b/python_apps/pypo/scripts/old_files/include_scheduler.liq similarity index 100% rename from pypo/scripts/old_files/include_scheduler.liq rename to python_apps/pypo/scripts/old_files/include_scheduler.liq diff --git a/pypo/scripts/old_files/library.liq b/python_apps/pypo/scripts/old_files/library.liq similarity index 100% rename from pypo/scripts/old_files/library.liq rename to python_apps/pypo/scripts/old_files/library.liq diff --git a/pypo/scripts/old_files/log_run.sh b/python_apps/pypo/scripts/old_files/log_run.sh similarity index 100% rename from pypo/scripts/old_files/log_run.sh rename to python_apps/pypo/scripts/old_files/log_run.sh diff --git a/pypo/scripts/old_files/ls_config.liq.dist b/python_apps/pypo/scripts/old_files/ls_config.liq.dist similarity index 100% rename from pypo/scripts/old_files/ls_config.liq.dist rename to python_apps/pypo/scripts/old_files/ls_config.liq.dist diff --git a/pypo/scripts/old_files/ls_cue.liq b/python_apps/pypo/scripts/old_files/ls_cue.liq similarity index 100% rename from pypo/scripts/old_files/ls_cue.liq rename to python_apps/pypo/scripts/old_files/ls_cue.liq diff --git a/pypo/scripts/old_files/ls_run.sh b/python_apps/pypo/scripts/old_files/ls_run.sh similarity index 100% rename from pypo/scripts/old_files/ls_run.sh rename to python_apps/pypo/scripts/old_files/ls_run.sh diff --git a/pypo/scripts/old_files/ls_script.liq b/python_apps/pypo/scripts/old_files/ls_script.liq similarity index 100% rename from pypo/scripts/old_files/ls_script.liq rename to python_apps/pypo/scripts/old_files/ls_script.liq diff --git a/pypo/scripts/old_files/silence-playlist.lsp b/python_apps/pypo/scripts/old_files/silence-playlist.lsp similarity index 100% rename from pypo/scripts/old_files/silence-playlist.lsp rename to python_apps/pypo/scripts/old_files/silence-playlist.lsp diff --git a/pypo/test/airtime-schedule-insert.php b/python_apps/pypo/test/airtime-schedule-insert.php similarity index 98% rename from pypo/test/airtime-schedule-insert.php rename to python_apps/pypo/test/airtime-schedule-insert.php index 6d3e37951..bc229dbd4 100644 --- a/pypo/test/airtime-schedule-insert.php +++ b/python_apps/pypo/test/airtime-schedule-insert.php @@ -79,7 +79,7 @@ $endTime = date("Y-m-d H:i:s", time()+(60*60)); echo "Removing everything from the scheduler between $startTime and $endTime..."; // Scheduler: remove any playlists for the next hour -Schedule::RemoveItemsInRange($startTime, $endTime); +//Schedule::RemoveItemsInRange($startTime, $endTime); // Check for succcess $scheduleClear = Schedule::isScheduleEmptyInRange($startTime, "01:00:00"); if (!$scheduleClear) { diff --git a/pypo/util/__init__.py b/python_apps/pypo/util/__init__.py similarity index 100% rename from pypo/util/__init__.py rename to python_apps/pypo/util/__init__.py diff --git a/pypo/util/cue_file.py b/python_apps/pypo/util/cue_file.py similarity index 97% rename from pypo/util/cue_file.py rename to python_apps/pypo/util/cue_file.py index 0d5f6cab5..8b3a158be 100755 --- a/pypo/util/cue_file.py +++ b/python_apps/pypo/util/cue_file.py @@ -64,7 +64,7 @@ class CueFile(): print command os.system(command + ' > /dev/null 2>&1') - command = 'lame -b 32 %s %s' % (dst + '.tmp.mp3', dst); + command = 'lame -b 128 %s %s' % (dst + '.tmp.mp3', dst); logger.info("command: %s", command) print command os.system(command + ' > /dev/null 2>&1') diff --git a/pypo/util/status.py b/python_apps/pypo/util/status.py similarity index 100% rename from pypo/util/status.py rename to python_apps/pypo/util/status.py diff --git a/python_apps/show-recorder/config.cfg b/python_apps/show-recorder/config.cfg index 274d9d786..ec812b8cf 100644 --- a/python_apps/show-recorder/config.cfg +++ b/python_apps/show-recorder/config.cfg @@ -1,9 +1,22 @@ +api_client = "airtime" + # Hostname -base_url = 'http://campcaster.dev/' - -show_schedule_url = 'Recorder/get-show-schedule/format/json' - -upload_file_url = 'Plupload/upload-recorded/format/json' +base_url = 'http://localhost/' # base path to store recordered shows at -base_recorded_files = '/home/naomi/Music/' +base_recorded_files = '/home/pypo/Music/' + +# Value needed to access the API +api_key = 'AAA' + +# Path to the base of the API +api_base = 'api/' + +# URL to get the version number of the server API +version_url = 'version/api_key/%%api_key%%' + +# URL to get the schedule of shows set to record +show_schedule_url = 'recorded-shows/format/json/api_key/%%api_key%%' + +# URL to upload the recorded show's file to Airtime +upload_file_url = 'upload-recorded/format/json/api_key/%%api_key%%' diff --git a/python_apps/show-recorder/install/recorder-daemontools-logger.sh b/python_apps/show-recorder/install/recorder-daemontools-logger.sh new file mode 100644 index 000000000..9673575db --- /dev/null +++ b/python_apps/show-recorder/install/recorder-daemontools-logger.sh @@ -0,0 +1,2 @@ +#!/bin/sh +exec setuidgid pypo multilog t ./main diff --git a/python_apps/show-recorder/install/recorder-daemontools.sh b/python_apps/show-recorder/install/recorder-daemontools.sh new file mode 100644 index 000000000..c1e0df863 --- /dev/null +++ b/python_apps/show-recorder/install/recorder-daemontools.sh @@ -0,0 +1,18 @@ +#!/bin/sh +recorder_user="pypo" +export HOME="/home/pypo/" +export TERM=xterm + +# Location of pypo_cli.py Python script +recorder_path="/opt/recorder/bin/" +recorder_script="testrecordscript.py" + +api_client_path="/opt/pypo/" +cd ${recorder_path} + +echo "*** Daemontools: starting daemon" +exec 2>&1 +# Note the -u when calling python! we need it to get unbuffered binary stdout and stderr + +sudo PYTHONPATH=${api_client_path} -u ${recorder_user} python -u ${recorder_path}${recorder_script} +# EOF diff --git a/python_apps/show-recorder/install/recorder-install.py b/python_apps/show-recorder/install/recorder-install.py new file mode 100644 index 000000000..1b8186391 --- /dev/null +++ b/python_apps/show-recorder/install/recorder-install.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import time +import os +import traceback +from optparse import * +import sys +import time +import datetime +import logging +import logging.config +import shutil +import string +import platform +from subprocess import Popen, PIPE, STDOUT + +if os.geteuid() != 0: + print "Please run this as root." + sys.exit(1) + +BASE_PATH = '/opt/recorder/' + +def create_path(path): + if not (os.path.exists(path)): + print "Creating directory " + path + os.makedirs(path) + +def create_user(username): + print "Checking for user "+username + p = Popen('id '+username, shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True) + output = p.stdout.read() + if (output[0:3] != "uid"): + # Make the pypo user + print "Creating user "+username + os.system("adduser --system --quiet --group --shell /bin/bash "+username) + + #set pypo password + p = os.popen('/usr/bin/passwd pypo 1>/dev/null 2>&1', 'w') + p.write('pypo\n') + p.write('pypo\n') + p.close() + else: + print "User already exists." + #add pypo to audio group + os.system("adduser " + username + " audio 1>/dev/null 2>&1") + +def copy_dir(src_dir, dest_dir): + if (os.path.exists(dest_dir)) and (dest_dir != "/"): + print "Removing old directory "+dest_dir + shutil.rmtree(dest_dir) + if not (os.path.exists(dest_dir)): + print "Copying directory "+src_dir+" to "+dest_dir + shutil.copytree(src_dir, dest_dir) + +def get_current_script_dir(): + current_script_dir = os.path.realpath(__file__) + index = current_script_dir.rindex('/') + print current_script_dir[0:index] + return current_script_dir[0:index] + + +try: + current_script_dir = get_current_script_dir() + print "Checking and removing any existing recorder processes" + os.system("python %s/recorder-uninstall.py 1>/dev/null 2>&1"% current_script_dir) + time.sleep(5) + + # Create users + create_user("pypo") + + print "Creating home directory" + create_path("/home/pypo") + os.system("chmod -R 755 /home/pypo") + os.system("chown -R pypo:pypo /home/pypo") + + print "Creating home directory" + create_path("/home/pypo/Music") + os.system("chmod -R 755 /home/pypo/Music") + os.system("chown -R pypo:pypo /home/pypo/Music") + + print "Creating log directories" + create_path("/var/log/recorder") + os.system("chmod -R 755 /var/log/recorder") + os.system("chown -R pypo:pypo /var/log/recorder") + + create_path(BASE_PATH) + create_path(BASE_PATH+"bin") + create_path(BASE_PATH+"cache") + create_path(BASE_PATH+"files") + create_path(BASE_PATH+"tmp") + create_path(BASE_PATH+"archive") + + copy_dir("%s/.."%current_script_dir, BASE_PATH+"bin/") + + print "Setting permissions" + os.system("chmod -R 755 "+BASE_PATH) + os.system("chown -R pypo:pypo "+BASE_PATH) + + print "Installing recorder daemon" + create_path("/etc/service/recorder") + create_path("/etc/service/recorder/log") + shutil.copy("%s/recorder-daemontools.sh"%current_script_dir, "/etc/service/recorder/run") + shutil.copy("%s/recorder-daemontools-logger.sh"%current_script_dir, "/etc/service/recorder/log/run") + os.system("chmod -R 755 /etc/service/recorder") + os.system("chown -R pypo:pypo /etc/service/recorder") + + print "Waiting for processes to start..." + time.sleep(5) + os.system("python %s/recorder-start.py" % (get_current_script_dir())) + time.sleep(2) + + found = True + + p = Popen('svstat /etc/service/recorder', shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True) + output = p.stdout.read() + if (output.find("unable to open supervise/ok: file does not exist") >= 0): + found = False + print output + + if not found: + print "Recorder install has completed, but daemontools is not running, please make sure you have it installed and then reboot." +except Exception, e: + print "exception:" + str(e) + sys.exit(1) + + + diff --git a/python_apps/show-recorder/install/recorder-start.py b/python_apps/show-recorder/install/recorder-start.py new file mode 100644 index 000000000..084b2b1ad --- /dev/null +++ b/python_apps/show-recorder/install/recorder-start.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import os +import sys + +if os.geteuid() != 0: + print "Please run this as root." + sys.exit(1) + +try: + print "Starting daemontool script recorder" + os.system("svc -u /etc/service/recorder") + +except Exception, e: + print "exception:" + str(e) diff --git a/python_apps/show-recorder/install/recorder-stop.py b/python_apps/show-recorder/install/recorder-stop.py new file mode 100644 index 000000000..c8eb45562 --- /dev/null +++ b/python_apps/show-recorder/install/recorder-stop.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import os +import sys + +if os.geteuid() != 0: + print "Please run this as root." + sys.exit(1) + +try: + print "Stopping daemontool script recorder" + os.system("svc -dx /etc/service/recorder 1>/dev/null 2>&1") + +except Exception, e: + print "exception:" + str(e) diff --git a/python_apps/show-recorder/install/recorder-uninstall.py b/python_apps/show-recorder/install/recorder-uninstall.py new file mode 100644 index 000000000..f8ab96432 --- /dev/null +++ b/python_apps/show-recorder/install/recorder-uninstall.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import os +import sys +import time + +if os.geteuid() != 0: + print "Please run this as root." + sys.exit(1) + +BASE_PATH = '/opt/recorder/' + +def remove_path(path): + os.system("rm -rf " + path) + +def remove_user(username): + os.system("killall -u %s 1>/dev/null 2>&1" % username) + + #allow all process to be completely closed before we attempt to delete user + print "Waiting for processes to close..." + time.sleep(5) + + os.system("deluser --remove-home " + username + " 1>/dev/null 2>&1") + +def get_current_script_dir(): + current_script_dir = os.path.realpath(__file__) + index = current_script_dir.rindex('/') + return current_script_dir[0:index] + +try: + os.system("python %s/recorder-stop.py" % get_current_script_dir()) + + print "Removing log directories" + remove_path("/var/log/recorder") + + print "Removing recorder files" + remove_path(BASE_PATH) + + print "Removing daemontool script recorder" + remove_path("rm -rf /etc/service/recorder") + + remove_user("pypo") + print "Uninstall complete." +except Exception, e: + print "exception:" + str(e) diff --git a/python_apps/show-recorder/logging.cfg b/python_apps/show-recorder/logging.cfg new file mode 100644 index 000000000..251fce8d7 --- /dev/null +++ b/python_apps/show-recorder/logging.cfg @@ -0,0 +1,22 @@ +[loggers] +keys=root + +[handlers] +keys=consoleHandler + +[formatters] +keys=simpleFormatter + +[logger_root] +level=DEBUG +handlers=consoleHandler + +[handler_consoleHandler] +class=StreamHandler +level=DEBUG +formatter=simpleFormatter +args=(sys.stdout,) + +[formatter_simpleFormatter] +format=%(asctime)s %(levelname)s - [%(filename)s : %(funcName)s() : line %(lineno)d] - %(message)s +datefmt= diff --git a/python_apps/show-recorder/testrecordscript.py b/python_apps/show-recorder/testrecordscript.py index b5cda7409..c7fb2691d 100644 --- a/python_apps/show-recorder/testrecordscript.py +++ b/python_apps/show-recorder/testrecordscript.py @@ -1,10 +1,12 @@ #!/usr/local/bin/python import urllib import logging +import logging.config import json import time import datetime import os +import sys from configobj import ConfigObj @@ -13,6 +15,20 @@ from poster.streaminghttp import register_openers import urllib2 from subprocess import call +from threading import Thread + +# For RabbitMQ +from kombu.connection import BrokerConnection +from kombu.messaging import Exchange, Queue, Consumer, Producer + +from api_clients import api_client + +# configure logging +try: + logging.config.fileConfig("logging.cfg") +except Exception, e: + print 'Error configuring logging: ', e + sys.exit() # loading config file try: @@ -21,95 +37,127 @@ 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) - - command = "ecasound -i alsa -o %s -t:%s" % (filepath, filelength) - - call(command, shell=True) - - 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])) + 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 +class ShowRecorder(Thread): + + def __init__ (self, show_instance, filelength, filename, filetype): + Thread.__init__(self) + self.api_client = api_client.api_client_factory(config) + self.filelength = filelength + self.filename = filename + self.filetype = filetype + self.show_instance = show_instance + + def record_show(self): + + length = str(self.filelength)+".0" + filename = self.filename.replace(" ", "-") + filepath = "%s%s.%s" % (config["base_recorded_files"], filename, self.filetype) + + command = "ecasound -i alsa -o %s -t:%s" % (filepath, length) + args = command.split(" ") + + print "starting record" + + code = call(args) + + print "finishing record, return code %s" % (code) + + return code, filepath + + def upload_file(self, filepath): + + filename = os.path.split(filepath)[1] + + # Register the streaming http handlers with urllib2 + register_openers() + + # headers contains the necessary Content-Type and Content-Length + # datagen is a generator object that yields the encoded parameters + datagen, headers = multipart_encode({"file": open(filepath, "rb"), 'name': filename, 'show_instance': self.show_instance}) + + self.api_client.upload_recorded_show(datagen, headers) + + def run(self): + code, filepath = self.record_show() + + if code == 0: + self.upload_file(filepath) + else: + print "problem recording show" + + +class Record(): + + def __init__(self): + self.api_client = api_client.api_client_factory(config) + self.shows_to_record = {} + + def process_shows(self, shows): + + self.shows_to_record = {} - shows_to_record[show[u'starts']] = time_delta + for show in shows: + show_starts = getDateTimeObj(show[u'starts']) + show_end = getDateTimeObj(show[u'ends']) + time_delta = show_end - show_starts + + self.shows_to_record[show[u'starts']] = [time_delta, show[u'instance_id'], show[u'name']] -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) + def check_record(self): + + tnow = datetime.datetime.now() + sorted_show_keys = sorted(self.shows_to_record.keys()) + + start_time = sorted_show_keys[0] + next_show = getDateTimeObj(start_time) - delta = next_show - tnow + print next_show + print 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) - upload_file(filepath) - + delta = next_show - tnow + min_delta = datetime.timedelta(seconds=60) -def get_shows(): + if delta <= min_delta: + print "sleeping %s seconds until show" % (delta.seconds) + time.sleep(delta.seconds) + + show_length = self.shows_to_record[start_time][0] + show_instance = self.shows_to_record[start_time][1] + show_name = self.shows_to_record[start_time][2] + filename = show_name+"-"+start_time - url = config["base_url"] + config["show_schedule_url"] - response = urllib.urlopen(url) - data = response.read() - print data + show = ShowRecorder(show_instance, show_length.seconds, filename, filetype="mp3") + show.start() + + #remove show from shows to record. + del self.shows_to_record[start_time] + - response_json = json.loads(data) - shows = response_json[u'shows'] - print shows + def get_shows(self): - if len(shows): - process_shows(shows) - check_record() + shows = self.api_client.get_shows_to_record() -def upload_file(filepath): - - filename = os.path.split(filepath)[1] - - # Register the streaming http handlers with urllib2 - register_openers() - - # headers contains the necessary Content-Type and Content-Length - # datagen is a generator object that yields the encoded parameters - datagen, headers = multipart_encode({"file": open(filepath, "rb"), 'name': filename}) - - url = config["base_url"] + config["upload_file_url"] - - req = urllib2.Request(url, datagen, headers) - response = urllib2.urlopen(req).read().strip() - print response + if len(shows): + self.process_shows(shows) + self.check_record() if __name__ == '__main__': + recorder = Record() + while True: - get_shows() - time.sleep(30) + recorder.get_shows() + time.sleep(5) diff --git a/python_apps/show-recorder/testsoundcloud.py b/python_apps/show-recorder/testsoundcloud.py deleted file mode 100644 index 0ba6fd20d..000000000 --- a/python_apps/show-recorder/testsoundcloud.py +++ /dev/null @@ -1,59 +0,0 @@ -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 deleted file mode 100644 index ae92c1d26..000000000 --- a/python_apps/soundcloud-api/AUTHORS +++ /dev/null @@ -1,5 +0,0 @@ -Authors -------- - -Diez B. Roggisch, deets@web.de - diff --git a/python_apps/soundcloud-api/ChangeLog b/python_apps/soundcloud-api/ChangeLog deleted file mode 100644 index 9b5bb4679..000000000 --- a/python_apps/soundcloud-api/ChangeLog +++ /dev/null @@ -1,9 +0,0 @@ -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 deleted file mode 100644 index 3b473dbfc..000000000 --- a/python_apps/soundcloud-api/LICENSE +++ /dev/null @@ -1,458 +0,0 @@ - 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 deleted file mode 100644 index 729a8faac..000000000 --- a/python_apps/soundcloud-api/README +++ /dev/null @@ -1,45 +0,0 @@ -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 deleted file mode 100644 index c04723c5b..000000000 --- a/python_apps/soundcloud-api/bootstrap.py +++ /dev/null @@ -1,58 +0,0 @@ -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 deleted file mode 100644 index 8f3e21555..000000000 --- a/python_apps/soundcloud-api/docs/api/api-objects.txt +++ /dev/null @@ -1,333 +0,0 @@ -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 deleted file mode 100644 index b2473382f..000000000 --- a/python_apps/soundcloud-api/docs/api/class-tree.html +++ /dev/null @@ -1,216 +0,0 @@ - - - - - Class Hierarchy - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  - - - - -
[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 deleted file mode 100644 index 26b43c524..000000000 Binary files a/python_apps/soundcloud-api/docs/api/crarr.png and /dev/null differ diff --git a/python_apps/soundcloud-api/docs/api/epydoc.css b/python_apps/soundcloud-api/docs/api/epydoc.css deleted file mode 100644 index 86d417068..000000000 --- a/python_apps/soundcloud-api/docs/api/epydoc.css +++ /dev/null @@ -1,322 +0,0 @@ - - -/* 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 deleted file mode 100644 index e787dbcf4..000000000 --- a/python_apps/soundcloud-api/docs/api/epydoc.js +++ /dev/null @@ -1,293 +0,0 @@ -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 deleted file mode 100644 index 2dbca8db5..000000000 --- a/python_apps/soundcloud-api/docs/api/exceptions.AssertionError-class.html +++ /dev/null @@ -1,299 +0,0 @@ - - - - - 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 deleted file mode 100644 index 6d0191e9d..000000000 --- a/python_apps/soundcloud-api/docs/api/frames.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - SoundCloud API - - - - - - - - - diff --git a/python_apps/soundcloud-api/docs/api/help.html b/python_apps/soundcloud-api/docs/api/help.html deleted file mode 100644 index ff7d397f2..000000000 --- a/python_apps/soundcloud-api/docs/api/help.html +++ /dev/null @@ -1,278 +0,0 @@ - - - - - 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 deleted file mode 100644 index 94e006634..000000000 --- a/python_apps/soundcloud-api/docs/api/identifier-index.html +++ /dev/null @@ -1,892 +0,0 @@ - - - - - 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 deleted file mode 100644 index 6d0191e9d..000000000 --- a/python_apps/soundcloud-api/docs/api/index.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - SoundCloud API - - - - - - - - - diff --git a/python_apps/soundcloud-api/docs/api/module-tree.html b/python_apps/soundcloud-api/docs/api/module-tree.html deleted file mode 100644 index bf467d991..000000000 --- a/python_apps/soundcloud-api/docs/api/module-tree.html +++ /dev/null @@ -1,130 +0,0 @@ - - - - - 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 deleted file mode 100644 index 8ac436448..000000000 --- a/python_apps/soundcloud-api/docs/api/redirect.html +++ /dev/null @@ -1,38 +0,0 @@ -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 deleted file mode 100644 index e63c5dac9..000000000 --- a/python_apps/soundcloud-api/docs/api/scapi-module.html +++ /dev/null @@ -1,444 +0,0 @@ - - - - - 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 deleted file mode 100644 index 73d83533b..000000000 --- a/python_apps/soundcloud-api/docs/api/scapi-pysrc.html +++ /dev/null @@ -1,1263 +0,0 @@ - - - - - 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 deleted file mode 100644 index 7e4a213ac..000000000 --- a/python_apps/soundcloud-api/docs/api/scapi.ApiConnector-class.html +++ /dev/null @@ -1,544 +0,0 @@ - - - - - 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 deleted file mode 100644 index f64bc0d2b..000000000 --- a/python_apps/soundcloud-api/docs/api/scapi.Asset-class.html +++ /dev/null @@ -1,258 +0,0 @@ - - - - - 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 deleted file mode 100644 index 4f2c4c950..000000000 --- a/python_apps/soundcloud-api/docs/api/scapi.Comment-class.html +++ /dev/null @@ -1,258 +0,0 @@ - - - - - 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 deleted file mode 100644 index 40095b067..000000000 --- a/python_apps/soundcloud-api/docs/api/scapi.Event-class.html +++ /dev/null @@ -1,258 +0,0 @@ - - - - - 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 deleted file mode 100644 index eb30ab842..000000000 --- a/python_apps/soundcloud-api/docs/api/scapi.Group-class.html +++ /dev/null @@ -1,258 +0,0 @@ - - - - - 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 deleted file mode 100644 index 476502352..000000000 --- a/python_apps/soundcloud-api/docs/api/scapi.InvalidMethodException-class.html +++ /dev/null @@ -1,297 +0,0 @@ - - - - - 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 deleted file mode 100644 index 5bcb71794..000000000 --- a/python_apps/soundcloud-api/docs/api/scapi.NoResultFromRequest-class.html +++ /dev/null @@ -1,195 +0,0 @@ - - - - - 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 deleted file mode 100644 index 557f05831..000000000 --- a/python_apps/soundcloud-api/docs/api/scapi.Playlist-class.html +++ /dev/null @@ -1,258 +0,0 @@ - - - - - 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 deleted file mode 100644 index 855d8fd1e..000000000 --- a/python_apps/soundcloud-api/docs/api/scapi.RESTBase-class.html +++ /dev/null @@ -1,895 +0,0 @@ - - - - - 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 deleted file mode 100644 index 7fa8dedee..000000000 --- a/python_apps/soundcloud-api/docs/api/scapi.SCRedirectHandler-class.html +++ /dev/null @@ -1,319 +0,0 @@ - - - - - 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 deleted file mode 100644 index 0b008e17f..000000000 --- a/python_apps/soundcloud-api/docs/api/scapi.Scope-class.html +++ /dev/null @@ -1,682 +0,0 @@ - - - - - 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 deleted file mode 100644 index e8257566a..000000000 --- a/python_apps/soundcloud-api/docs/api/scapi.Track-class.html +++ /dev/null @@ -1,264 +0,0 @@ - - - - - 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 deleted file mode 100644 index 94f9ee872..000000000 --- a/python_apps/soundcloud-api/docs/api/scapi.UnknownContentType-class.html +++ /dev/null @@ -1,337 +0,0 @@ - - - - - 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 deleted file mode 100644 index 19b1053a9..000000000 --- a/python_apps/soundcloud-api/docs/api/scapi.User-class.html +++ /dev/null @@ -1,264 +0,0 @@ - - - - - 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 deleted file mode 100644 index 2f6f5fb8e..000000000 --- a/python_apps/soundcloud-api/docs/api/scapi.authentication-module.html +++ /dev/null @@ -1,228 +0,0 @@ - - - - - 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 deleted file mode 100644 index 65492b3c9..000000000 --- a/python_apps/soundcloud-api/docs/api/scapi.authentication-pysrc.html +++ /dev/null @@ -1,348 +0,0 @@ - - - - - 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 deleted file mode 100644 index 437ef8444..000000000 --- a/python_apps/soundcloud-api/docs/api/scapi.authentication.BasicAuthenticator-class.html +++ /dev/null @@ -1,267 +0,0 @@ - - - - - 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 deleted file mode 100644 index de4541aa2..000000000 --- a/python_apps/soundcloud-api/docs/api/scapi.authentication.OAuthAuthenticator-class.html +++ /dev/null @@ -1,337 +0,0 @@ - - - - - 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 deleted file mode 100644 index 82e3a5ee0..000000000 --- a/python_apps/soundcloud-api/docs/api/scapi.authentication.OAuthSignatureMethod_HMAC_SHA1-class.html +++ /dev/null @@ -1,294 +0,0 @@ - - - - - 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 deleted file mode 100644 index 41aa49291..000000000 --- a/python_apps/soundcloud-api/docs/api/scapi.config-module.html +++ /dev/null @@ -1,114 +0,0 @@ - - - - - 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 deleted file mode 100644 index 27eedf60c..000000000 --- a/python_apps/soundcloud-api/docs/api/scapi.config-pysrc.html +++ /dev/null @@ -1,122 +0,0 @@ - - - - - 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 deleted file mode 100644 index 9ac8f0d48..000000000 --- a/python_apps/soundcloud-api/docs/api/scapi.json-module.html +++ /dev/null @@ -1,218 +0,0 @@ - - - - - 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 deleted file mode 100644 index cde009caf..000000000 --- a/python_apps/soundcloud-api/docs/api/scapi.json-pysrc.html +++ /dev/null @@ -1,433 +0,0 @@ - - - - - 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 deleted file mode 100644 index bad1801ae..000000000 --- a/python_apps/soundcloud-api/docs/api/scapi.json.JsonReader-class.html +++ /dev/null @@ -1,544 +0,0 @@ - - - - - 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 deleted file mode 100644 index c376a942f..000000000 --- a/python_apps/soundcloud-api/docs/api/scapi.json.JsonWriter-class.html +++ /dev/null @@ -1,233 +0,0 @@ - - - - - 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 deleted file mode 100644 index acbf8e4c3..000000000 --- a/python_apps/soundcloud-api/docs/api/scapi.json.ReadException-class.html +++ /dev/null @@ -1,196 +0,0 @@ - - - - - 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 deleted file mode 100644 index b97e08ff6..000000000 --- a/python_apps/soundcloud-api/docs/api/scapi.json.WriteException-class.html +++ /dev/null @@ -1,196 +0,0 @@ - - - - - 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 deleted file mode 100644 index ead736d6f..000000000 --- a/python_apps/soundcloud-api/docs/api/scapi.json._StringGenerator-class.html +++ /dev/null @@ -1,291 +0,0 @@ - - - - - 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 deleted file mode 100644 index 41a0acbfc..000000000 --- a/python_apps/soundcloud-api/docs/api/scapi.tests-module.html +++ /dev/null @@ -1,140 +0,0 @@ - - - - - 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 deleted file mode 100644 index a967c5152..000000000 --- a/python_apps/soundcloud-api/docs/api/scapi.tests-pysrc.html +++ /dev/null @@ -1,122 +0,0 @@ - - - - - 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 deleted file mode 100644 index cff686d84..000000000 --- a/python_apps/soundcloud-api/docs/api/scapi.tests.scapi_tests-module.html +++ /dev/null @@ -1,172 +0,0 @@ - - - - - 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 deleted file mode 100644 index bca835c59..000000000 --- a/python_apps/soundcloud-api/docs/api/scapi.tests.scapi_tests-pysrc.html +++ /dev/null @@ -1,760 +0,0 @@ - - - - - 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 -
184 - def test_access_token_acquisition(self): -
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 -
289 - def test_setting_comments_the_way_shawn_says_its_correct(self): -
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 -
297 - def test_contact_add_and_removal(self): -
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 -
431 - def test_track_creation_with_email_sharers(self): -
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 -
441 - def test_track_creation_with_artwork(self): -
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 -
530 - def test_track_creation_with_updated_artwork(self): -
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 deleted file mode 100644 index 0c5a5a449..000000000 --- a/python_apps/soundcloud-api/docs/api/scapi.tests.scapi_tests.SCAPITests-class.html +++ /dev/null @@ -1,1025 +0,0 @@ - - - - - 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 deleted file mode 100644 index e4780ff79..000000000 --- a/python_apps/soundcloud-api/docs/api/scapi.tests.test_connect-module.html +++ /dev/null @@ -1,586 +0,0 @@ - - - - - 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 deleted file mode 100644 index 3c0a41c84..000000000 --- a/python_apps/soundcloud-api/docs/api/scapi.tests.test_connect-pysrc.html +++ /dev/null @@ -1,627 +0,0 @@ - - - - - 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 -
129 -def test_access_token_acquisition(): -
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 -
162 -def test_track_creation(): -
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 -
175 -def test_scoped_track_creation(): -
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 -
212 -def test_setting_permissions(): -
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 -
223 -def test_setting_comments(): -
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 -
232 -def test_setting_comments_the_way_shawn_says_its_correct(): -
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 -
239 -def test_contact_add_and_removal(): -
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 -
305 -def test_me_having_stress(): -
306 sca = ROOT -307 for _ in xrange(20): -308 setup() -309 sca.me() -
310 -
311 -def test_non_global_api(): -
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 deleted file mode 100644 index 6bf493f9d..000000000 --- a/python_apps/soundcloud-api/docs/api/scapi.tests.test_oauth-module.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - - 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 deleted file mode 100644 index 6ecf3c628..000000000 --- a/python_apps/soundcloud-api/docs/api/scapi.tests.test_oauth-pysrc.html +++ /dev/null @@ -1,182 +0,0 @@ - - - - - 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   
-
17 -def test_base64_connect(): -
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 -
25 -def test_oauth_connect(): -
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 deleted file mode 100644 index 2603fb0b1..000000000 --- a/python_apps/soundcloud-api/docs/api/scapi.util-module.html +++ /dev/null @@ -1,173 +0,0 @@ - - - - - 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 deleted file mode 100644 index 1a018ed72..000000000 --- a/python_apps/soundcloud-api/docs/api/scapi.util-pysrc.html +++ /dev/null @@ -1,171 +0,0 @@ - - - - - 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 deleted file mode 100644 index fe7b460e9..000000000 --- a/python_apps/soundcloud-api/docs/api/scapi.util.MultiDict-class.html +++ /dev/null @@ -1,247 +0,0 @@ - - - - - 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 deleted file mode 100644 index 3ced23f23..000000000 --- a/python_apps/soundcloud-api/docs/api/toc-everything.html +++ /dev/null @@ -1,151 +0,0 @@ - - - - - Everything - - - - - -

Everything

-
-

All Classes

- exceptions.AssertionError
- scapi.ApiConnector
- scapi.Comment
scapi.Event
scapi.Group
- scapi.InvalidMethodException
-
- scapi.NoResultFromRequest
- scapi.Playlist
- scapi.RESTBase
-
- scapi.SCRedirectHandler
-
- scapi.Scope
- scapi.Track
- scapi.UnknownContentType
- scapi.User
- scapi.authentication.BasicAuthenticator
-
- scapi.authentication.OAuthAuthenticator
-
- scapi.authentication.OAuthSignatureMethod_HMAC_SHA1
- scapi.json.JsonReader
scapi.json.JsonWriter
scapi.json.ReadException
scapi.json.WriteException
- scapi.json._StringGenerator
- scapi.tests.scapi_tests.SCAPITests
- scapi.util.MultiDict
-

All Functions

- scapi.json.read
scapi.json.write
- scapi.register_classes
- 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
- scapi.util.escape
-

All Variables

- scapi.ACCESS_TOKEN_URL
scapi.AUTHORIZATION_URL
scapi.PROXY
scapi.REQUEST_TOKEN_URL
scapi.USE_PROXY
- scapi.authentication.USE_DOUBLE_ESCAPE_HACK
-
- scapi.authentication.logger
-
- scapi.logger
- 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_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
- 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 deleted file mode 100644 index b82804b3f..000000000 --- a/python_apps/soundcloud-api/docs/api/toc-scapi-module.html +++ /dev/null @@ -1,70 +0,0 @@ - - - - - scapi - - - - - -

Module scapi

-
-

Classes

-
- ApiConnector
- Comment
Event
Group
- InvalidMethodException
-
- NoResultFromRequest
- Playlist
- RESTBase
-
- SCRedirectHandler
-
- Scope
- Track
- UnknownContentType
- User

Functions

-
- register_classes
-

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 deleted file mode 100644 index e86f597b8..000000000 --- a/python_apps/soundcloud-api/docs/api/toc-scapi.authentication-module.html +++ /dev/null @@ -1,46 +0,0 @@ - - - - - authentication - - - - - -

Module authentication

-
-

Classes

-
- BasicAuthenticator
-
- OAuthAuthenticator
-
- OAuthSignatureMethod_HMAC_SHA1
-

Variables

-
- USE_DOUBLE_ESCAPE_HACK
-
- 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 deleted file mode 100644 index bc4635ec9..000000000 --- a/python_apps/soundcloud-api/docs/api/toc-scapi.config-module.html +++ /dev/null @@ -1,29 +0,0 @@ - - - - - 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 deleted file mode 100644 index 49c7fa3f6..000000000 --- a/python_apps/soundcloud-api/docs/api/toc-scapi.json-module.html +++ /dev/null @@ -1,40 +0,0 @@ - - - - - json - - - - - -

Module json

-
-

Classes

- JsonReader
JsonWriter
ReadException
WriteException
- _StringGenerator
-

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 deleted file mode 100644 index a4494cd7b..000000000 --- a/python_apps/soundcloud-api/docs/api/toc-scapi.multidict-module.html +++ /dev/null @@ -1,29 +0,0 @@ - - - - - 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 deleted file mode 100644 index 4c66376d1..000000000 --- a/python_apps/soundcloud-api/docs/api/toc-scapi.tests-module.html +++ /dev/null @@ -1,29 +0,0 @@ - - - - - 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 deleted file mode 100644 index ecc870d8c..000000000 --- a/python_apps/soundcloud-api/docs/api/toc-scapi.tests.scapi_tests-module.html +++ /dev/null @@ -1,34 +0,0 @@ - - - - - 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 deleted file mode 100644 index 791b5ba85..000000000 --- a/python_apps/soundcloud-api/docs/api/toc-scapi.tests.test_connect-module.html +++ /dev/null @@ -1,68 +0,0 @@ - - - - - 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
- 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 deleted file mode 100644 index 0d504cee4..000000000 --- a/python_apps/soundcloud-api/docs/api/toc-scapi.tests.test_oauth-module.html +++ /dev/null @@ -1,41 +0,0 @@ - - - - - test_oauth - - - - - -

Module test_oauth

-
-

Functions

- test_base64_connect
test_oauth_connect

Variables

- CONSUMER
CONSUMER_SECRET
SECRET
TOKEN
- _logger
- 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 deleted file mode 100644 index c94b46b34..000000000 --- a/python_apps/soundcloud-api/docs/api/toc-scapi.util-module.html +++ /dev/null @@ -1,37 +0,0 @@ - - - - - util - - - - - -

Module util

-
-

Classes

-
- MultiDict
-

Functions

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

Table of Contents

-
- Everything -
-

Modules

- scapi
- scapi.authentication
- scapi.config
scapi.json
scapi.multidict
scapi.tests
scapi.tests.scapi_tests
scapi.tests.test_connect
scapi.tests.test_oauth
- scapi.util
-
- [hide private] - - - - diff --git a/python_apps/soundcloud-api/oauth/__init__.py b/python_apps/soundcloud-api/oauth/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/python_apps/soundcloud-api/oauth/example/client.py b/python_apps/soundcloud-api/oauth/example/client.py deleted file mode 100644 index 3f995c1a5..000000000 --- a/python_apps/soundcloud-api/oauth/example/client.py +++ /dev/null @@ -1,157 +0,0 @@ -''' -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 deleted file mode 100644 index 91fb71538..000000000 --- a/python_apps/soundcloud-api/oauth/example/server.py +++ /dev/null @@ -1,167 +0,0 @@ -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 deleted file mode 100644 index 274bbd25b..000000000 --- a/python_apps/soundcloud-api/oauth/oauth.py +++ /dev/null @@ -1,505 +0,0 @@ -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 deleted file mode 100644 index 34b12943f..000000000 --- a/python_apps/soundcloud-api/scapi/MultipartPostHandler.py +++ /dev/null @@ -1,135 +0,0 @@ -#!/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 deleted file mode 100644 index ae737cb81..000000000 --- a/python_apps/soundcloud-api/scapi/__init__.py +++ /dev/null @@ -1,1012 +0,0 @@ -## 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 deleted file mode 100644 index 52c527704..000000000 --- a/python_apps/soundcloud-api/scapi/authentication.py +++ /dev/null @@ -1,195 +0,0 @@ -## 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 deleted file mode 100644 index 139597f9c..000000000 --- a/python_apps/soundcloud-api/scapi/config.py +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/python_apps/soundcloud-api/scapi/json.py b/python_apps/soundcloud-api/scapi/json.py deleted file mode 100644 index a28a13e39..000000000 --- a/python_apps/soundcloud-api/scapi/json.py +++ /dev/null @@ -1,310 +0,0 @@ -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 deleted file mode 100644 index e69de29bb..000000000 diff --git a/python_apps/soundcloud-api/scapi/tests/knaster.mp3 b/python_apps/soundcloud-api/scapi/tests/knaster.mp3 deleted file mode 100644 index 138749d65..000000000 Binary files a/python_apps/soundcloud-api/scapi/tests/knaster.mp3 and /dev/null differ diff --git a/python_apps/soundcloud-api/scapi/tests/scapi_tests.py b/python_apps/soundcloud-api/scapi/tests/scapi_tests.py deleted file mode 100644 index 71ea339dc..000000000 --- a/python_apps/soundcloud-api/scapi/tests/scapi_tests.py +++ /dev/null @@ -1,563 +0,0 @@ -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 deleted file mode 100644 index a5ca93879..000000000 Binary files a/python_apps/soundcloud-api/scapi/tests/spam.jpg and /dev/null differ diff --git a/python_apps/soundcloud-api/scapi/tests/test_connect.py b/python_apps/soundcloud-api/scapi/tests/test_connect.py deleted file mode 100644 index 3e045e6c1..000000000 --- a/python_apps/soundcloud-api/scapi/tests/test_connect.py +++ /dev/null @@ -1,334 +0,0 @@ -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 deleted file mode 100644 index d283c0048..000000000 --- a/python_apps/soundcloud-api/scapi/tests/test_oauth.py +++ /dev/null @@ -1,36 +0,0 @@ -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 deleted file mode 100644 index 00fa66897..000000000 --- a/python_apps/soundcloud-api/scapi/util.py +++ /dev/null @@ -1,53 +0,0 @@ -## 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 deleted file mode 100644 index dc057a603..000000000 --- a/python_apps/soundcloud-api/setup.py +++ /dev/null @@ -1,22 +0,0 @@ -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 deleted file mode 100644 index c9d1422b3..000000000 --- a/python_apps/soundcloud-api/test.ini +++ /dev/null @@ -1,33 +0,0 @@ -[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/utils/CleanStor.sh b/utils/airtime-clean-storage similarity index 95% rename from utils/CleanStor.sh rename to utils/airtime-clean-storage index 2b862ec6e..b96bb7d21 100755 --- a/utils/CleanStor.sh +++ b/utils/airtime-clean-storage @@ -23,4 +23,4 @@ #------------------------------------------------------------------------------- # This script cleans audio files in the Airtime storageServer. -php -q CleanStor.php "$@" || exit 1 +php -q airtime-clean-storage.php "$@" || exit 1 diff --git a/utils/CleanStor.php b/utils/airtime-clean-storage.php similarity index 96% rename from utils/CleanStor.php rename to utils/airtime-clean-storage.php index 1b4ed9f97..d1a063788 100644 --- a/utils/CleanStor.php +++ b/utils/airtime-clean-storage.php @@ -18,7 +18,7 @@ function printUsage() { global $CC_CONFIG; echo "Usage:\n"; - echo " ./CleanStor [OPTION] \n"; + echo " ./airtime-clean-storage [OPTION] \n"; echo "\n"; echo "Options:\n"; echo " -c, --clean Removes all broken links from the storage server\n"; @@ -26,7 +26,7 @@ function printUsage() { echo "\n"; echo " -e, --empty Removes all files from the storage server \n"; echo " and empties all relevant information from the database.\n\n"; - echo "Storage server: ". $CC_CONFIG["storageDir"] ."\n\n\n"; + echo "Storage server: ". realpath($CC_CONFIG["storageDir"]) ."\n\n\n"; } function airtime_clean_files($p_path) { diff --git a/utils/airtime-import b/utils/airtime-import index 5098cc01d..4c8d8fdc1 100755 --- a/utils/airtime-import +++ b/utils/airtime-import @@ -29,15 +29,16 @@ #------------------------------------------------------------------------------- # Determine directories, files #------------------------------------------------------------------------------- -reldir=`dirname $0` -phpdir=$reldir -filelistpathname=. + +# Absolute path to this script +SCRIPT=`readlink -f $0` +# Absolute path this script is in +SCRIPTPATH=`dirname $SCRIPT` #------------------------------------------------------------------------------- # Do import #------------------------------------------------------------------------------- invokePwd=$PWD -#echo $invokePwd -cd $phpdir +cd $SCRIPTPATH php -q airtime-import.php --dir "$invokePwd" "$@" || exit 1 diff --git a/utils/airtime-import.php b/utils/airtime-import.php index 9e0696728..168bf0765 100644 --- a/utils/airtime-import.php +++ b/utils/airtime-import.php @@ -56,7 +56,7 @@ function printUsage() echo " -h, --help Print this message and exit.\n"; echo "\n"; echo "Files will be imported to directory:\n"; - echo " ". $CC_CONFIG["storageDir"] ."\n"; + echo " ". realpath($CC_CONFIG["storageDir"]) ."\n"; echo "\n"; } diff --git a/utils/resetStorage.sh b/utils/resetStorage.sh deleted file mode 100755 index 9bb49fcee..000000000 --- a/utils/resetStorage.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------------------- -# Copyright (c) 2010 Sourcefabric O.P.S. -# -# This file is part of the Airtime project. -# http://airtime.sourcefabric.org/ -# To report bugs, send an e-mail to contact@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 call locstor.resetStorage XMLRPC method -#------------------------------------------------------------------------------- - -reldir=`dirname $0`/.. -WWW_ROOT=`cd $reldir/var/install; php -q getWwwRoot.php` || exit $? -echo "# storageServer root URL: $WWW_ROOT" - -#$reldir/var/xmlrpc/xr_cli_test.py -s $WWW_ROOT/xmlrpc/xrLocStor.php \ -# resetStorage || exit $? - -cd $reldir/var/xmlrpc -php -q xr_cli_test.php -s $WWW_ROOT/xmlrpc/xrLocStor.php \ - resetStorage 1 1 || exit $? - -echo "# resetStorage: OK" -exit 0