Merge branch 'master' of dev.sourcefabric.org:airtime
This commit is contained in:
commit
fc35a730b9
|
@ -1,774 +0,0 @@
|
|||
<?php
|
||||
/*
|
||||
* Format of search criteria: hash, with following structure:<br>
|
||||
* <ul>
|
||||
* <li>filetype - string, type of searched files,
|
||||
* meaningful values: 'audioclip', 'webstream', 'playlist', 'all'</li>
|
||||
* <li>operator - string, type of conditions join
|
||||
* (any condition matches / all conditions match),
|
||||
* meaningful values: 'and', 'or', ''
|
||||
* (may be empty or ommited only with less then 2 items in
|
||||
* "conditions" field)
|
||||
* </li>
|
||||
* <li>orderby : string - metadata category for sorting (optional)
|
||||
* or array of strings for multicolumn orderby
|
||||
* [default: dc:creator, dc:source, dc:title]
|
||||
* </li>
|
||||
* <li>desc : boolean - flag for descending order (optional)
|
||||
* or array of boolean for multicolumn orderby
|
||||
* (it corresponds to elements of orderby field)
|
||||
* [default: all ascending]
|
||||
* </li>
|
||||
* <li>conditions - array of hashes with structure:
|
||||
* <ul>
|
||||
* <li>cat - string, metadata category name</li>
|
||||
* <li>op - string, operator - meaningful values:
|
||||
* 'full', 'partial', 'prefix', '=', '<',
|
||||
* '<=', '>', '>='</li>
|
||||
* <li>val - string, search value</li>
|
||||
* </ul>
|
||||
* </li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* Format of search/browse results: hash, with following structure:<br>
|
||||
* <ul>
|
||||
* <li>results : array of gunids have found</li>
|
||||
* <li>cnt : integer - number of matching items</li>
|
||||
* </ul>
|
||||
*
|
||||
*/
|
||||
define('GBERR_DENY', 40);
|
||||
define('GBERR_FILEIO', 41);
|
||||
define('GBERR_FILENEX', 42);
|
||||
define('GBERR_FOBJNEX', 43);
|
||||
define('GBERR_WRTYPE', 44);
|
||||
define('GBERR_NONE', 45);
|
||||
define('GBERR_AOBJNEX', 46);
|
||||
define('GBERR_NOTF', 47);
|
||||
define('GBERR_SESS', 48);
|
||||
define('GBERR_PREF', 49);
|
||||
define('GBERR_TOKEN', 50);
|
||||
define('GBERR_PUT', 51);
|
||||
define('GBERR_LOCK', 52);
|
||||
define('GBERR_GUNID', 53);
|
||||
define('GBERR_BGERR', 54);
|
||||
define('GBERR_NOTIMPL', 69);
|
||||
|
||||
require_once(dirname(__FILE__)."/Alib.php");
|
||||
require_once(dirname(__FILE__)."/StoredFile.php");
|
||||
require_once(dirname(__FILE__)."/Playlist.php");
|
||||
|
||||
/**
|
||||
* Core of Airtime file storage module
|
||||
*
|
||||
* @package Airtime
|
||||
* @subpackage StorageServer
|
||||
* @copyright 2010 Sourcefabric O.P.S.
|
||||
* @license http://www.gnu.org/licenses/gpl.txt
|
||||
* @see Alib
|
||||
*/
|
||||
class BasicStor {
|
||||
public $storId;
|
||||
private $fileTypes;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->filetypes = array(
|
||||
'all'=>NULL,
|
||||
'audioclip'=>'audioclip',
|
||||
'webstream'=>'webstream',
|
||||
'playlist'=>'playlist',
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/* ----------------------------------------------------- put, access etc. */
|
||||
/**
|
||||
* Check validity of access/put token
|
||||
*
|
||||
* @param string $token
|
||||
* Access/put token
|
||||
* @param string $type
|
||||
* 'put'|'access'|'download'
|
||||
* @return boolean
|
||||
*/
|
||||
public static function bsCheckToken($token, $type='put')
|
||||
{
|
||||
global $CC_CONFIG, $CC_DBC;
|
||||
$cnt = $CC_DBC->getOne("
|
||||
SELECT count(token) FROM ".$CC_CONFIG['accessTable']."
|
||||
WHERE token=x'{$token}'::bigint AND type='$type'
|
||||
");
|
||||
if (PEAR::isError($cnt)) {
|
||||
return FALSE;
|
||||
}
|
||||
return ($cnt == 1);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create and return access link to real file
|
||||
*
|
||||
* @param string $realFname
|
||||
* Local filepath to accessed file
|
||||
* (NULL for only increase access counter, no symlink)
|
||||
* @param string $ext
|
||||
* Useful filename extension for accessed file
|
||||
* @param int $gunid
|
||||
* Global unique id
|
||||
* (NULL for special files such exported playlists)
|
||||
* @param string $type
|
||||
* 'access'|'download'
|
||||
* @param int $parent
|
||||
* parent token (recursive access/release)
|
||||
* @param int $owner
|
||||
* Local user id - owner of token
|
||||
* @return array
|
||||
* array with: seekable filehandle, access token
|
||||
*/
|
||||
public static function bsAccess($realFname, $ext, $gunid, $type='access',
|
||||
$parent='0', $owner=NULL)
|
||||
{
|
||||
global $CC_CONFIG, $CC_DBC;
|
||||
if (!is_null($gunid)) {
|
||||
$gunid = StoredFile::NormalizeGunid($gunid);
|
||||
}
|
||||
$token = StoredFile::CreateGunid();
|
||||
if (!is_null($realFname)) {
|
||||
$linkFname = $CC_CONFIG['accessDir']."/$token.$ext";
|
||||
//broken links are ignored by the player, do not worry about it here
|
||||
/* if (!is_file($realFname) && !is_link($realFname)) {
|
||||
return PEAR::raiseError(
|
||||
"BasicStor::bsAccess: real file not found ($realFname)",
|
||||
GBERR_FILEIO);
|
||||
}
|
||||
*/
|
||||
if (! @symlink($realFname, $linkFname)) {
|
||||
return PEAR::raiseError(
|
||||
"BasicStor::bsAccess: symlink create failed ($linkFname)",
|
||||
GBERR_FILEIO);
|
||||
}
|
||||
} else {
|
||||
$linkFname = NULL;
|
||||
}
|
||||
$escapedExt = pg_escape_string($ext);
|
||||
$escapedType = pg_escape_string($type);
|
||||
$CC_DBC->query("BEGIN");
|
||||
$gunidSql = (is_null($gunid) ? "NULL" : "x'{$gunid}'::bigint" );
|
||||
$ownerSql = (is_null($owner) ? "NULL" : "$owner" );
|
||||
$res = $CC_DBC->query("
|
||||
INSERT INTO ".$CC_CONFIG['accessTable']."
|
||||
(gunid, token, ext, type, parent, owner, ts)
|
||||
VALUES
|
||||
($gunidSql, x'$token'::bigint,
|
||||
'$escapedExt', '$escapedType', x'{$parent}'::bigint, $ownerSql, now())
|
||||
");
|
||||
if (PEAR::isError($res)) {
|
||||
$CC_DBC->query("ROLLBACK");
|
||||
return $res;
|
||||
}
|
||||
if (!is_null($gunid)) {
|
||||
$res = $CC_DBC->query("
|
||||
UPDATE ".$CC_CONFIG['filesTable']."
|
||||
SET currentlyAccessing=currentlyAccessing+1, mtime=now()
|
||||
WHERE gunid=x'{$gunid}'::bigint
|
||||
");
|
||||
}
|
||||
if (PEAR::isError($res)) {
|
||||
$CC_DBC->query("ROLLBACK");
|
||||
return $res;
|
||||
}
|
||||
$res = $CC_DBC->query("COMMIT");
|
||||
if (PEAR::isError($res)) {
|
||||
return $res;
|
||||
}
|
||||
return array('fname'=>$linkFname, 'token'=>$token);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Release access link to real file
|
||||
*
|
||||
* @param string $token
|
||||
* Access token
|
||||
* @param string $type
|
||||
* 'access'|'download'
|
||||
* @return array
|
||||
* gunid: string, global unique ID or real pathname of special file
|
||||
* owner: int, local subject id of token owner
|
||||
* realFname: string, real local pathname of accessed file
|
||||
*/
|
||||
public static function bsRelease($token, $type='access')
|
||||
{
|
||||
global $CC_CONFIG, $CC_DBC;
|
||||
if (!BasicStor::bsCheckToken($token, $type)) {
|
||||
return PEAR::raiseError(
|
||||
"BasicStor::bsRelease: invalid token ($token)"
|
||||
);
|
||||
}
|
||||
$acc = $CC_DBC->getRow("
|
||||
SELECT to_hex(gunid)as gunid, ext, owner FROM ".$CC_CONFIG['accessTable']."
|
||||
WHERE token=x'{$token}'::bigint AND type='$type'
|
||||
");
|
||||
if (PEAR::isError($acc)) {
|
||||
return $acc;
|
||||
}
|
||||
$ext = $acc['ext'];
|
||||
$owner = $acc['owner'];
|
||||
$linkFname = $CC_CONFIG['accessDir']."/$token.$ext";
|
||||
$realFname = readlink($linkFname);
|
||||
if (file_exists($linkFname)) {
|
||||
if(! @unlink($linkFname)){
|
||||
return PEAR::raiseError(
|
||||
"BasicStor::bsRelease: unlink failed ($linkFname)",
|
||||
GBERR_FILEIO);
|
||||
}
|
||||
}
|
||||
$CC_DBC->query("BEGIN");
|
||||
if (!is_null($acc['gunid'])) {
|
||||
$gunid = StoredFile::NormalizeGunid($acc['gunid']);
|
||||
$res = $CC_DBC->query("
|
||||
UPDATE ".$CC_CONFIG['filesTable']."
|
||||
SET currentlyAccessing=currentlyAccessing-1, mtime=now()
|
||||
WHERE gunid=x'{$gunid}'::bigint AND currentlyAccessing>0
|
||||
");
|
||||
if (PEAR::isError($res)) {
|
||||
$CC_DBC->query("ROLLBACK");
|
||||
return $res;
|
||||
}
|
||||
}
|
||||
$res = $CC_DBC->query("
|
||||
DELETE FROM ".$CC_CONFIG['accessTable']." WHERE token=x'$token'::bigint
|
||||
");
|
||||
if (PEAR::isError($res)) {
|
||||
$CC_DBC->query("ROLLBACK");
|
||||
return $res;
|
||||
}
|
||||
$res = $CC_DBC->query("COMMIT");
|
||||
if (PEAR::isError($res)) {
|
||||
return $res;
|
||||
}
|
||||
$res = array(
|
||||
'gunid' => (isset($gunid) ? $gunid : NULL ),
|
||||
'realFname' => $realFname,
|
||||
'owner' => $owner,
|
||||
);
|
||||
return $res;
|
||||
}
|
||||
|
||||
|
||||
/* ----------------------------------------------------- metadata methods */
|
||||
|
||||
/**
|
||||
* Method returning array with where-parts of sql queries
|
||||
*
|
||||
* @param array $conditions
|
||||
* See 'conditions' field in search criteria format
|
||||
* definition in class documentation
|
||||
* @return array
|
||||
* array of strings - WHERE-parts of SQL queries
|
||||
*/
|
||||
private function _makeWhereArr($conditions)
|
||||
{
|
||||
$ops = array('full'=>"='%s'", 'partial'=>"ILIKE '%%%s%%'",
|
||||
'prefix'=>"ILIKE '%s%%'", '<'=>"< '%s'", '='=>"= '%s'",
|
||||
'>'=>"> '%s'", '<='=>"<= '%s'", '>='=>">= '%s'"
|
||||
);
|
||||
$whereArr = array();
|
||||
if (is_array($conditions)) {
|
||||
foreach ($conditions as $cond) {
|
||||
$columnName = StoredFile::xmlCategoryToDbColumn($cond['cat']);
|
||||
$op = strtolower($cond['op']);
|
||||
$value = $cond['val'];
|
||||
if (!empty($value)) {
|
||||
$splittedQn = XML_Util::splitQualifiedName($catQn);
|
||||
$catNs = $splittedQn['namespace'];
|
||||
$cat = $splittedQn['localPart'];
|
||||
$opVal = sprintf($ops[$op], pg_escape_string($value));
|
||||
// retype for timestamp value
|
||||
if ($cat == 'mtime') {
|
||||
switch ($op) {
|
||||
case 'partial':
|
||||
case 'prefix':
|
||||
break;
|
||||
default:
|
||||
$retype = "::timestamp with time zone";
|
||||
$opVal = "$retype $opVal$retype";
|
||||
}
|
||||
}
|
||||
$sqlCond = " {$columnName} {$opVal}\n";
|
||||
$whereArr[] = $sqlCond;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $whereArr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search in local metadata database.
|
||||
*
|
||||
* @param array $criteria
|
||||
* has the following structure:<br>
|
||||
* <ul>
|
||||
* <li>filetype - string, type of searched files,
|
||||
* meaningful values: 'audioclip', 'webstream', 'playlist', 'all'</li>
|
||||
* <li>operator - string, type of conditions join
|
||||
* (any condition matches / all conditions match),
|
||||
* meaningful values: 'and', 'or', ''
|
||||
* (may be empty or ommited only with less then 2 items in
|
||||
* "conditions" field)
|
||||
* </li>
|
||||
* <li>orderby : string - metadata category for sorting (optional)
|
||||
* or array of strings for multicolumn orderby
|
||||
* [default: dc:creator, dc:source, dc:title]
|
||||
* </li>
|
||||
* <li>desc : boolean - flag for descending order (optional)
|
||||
* or array of boolean for multicolumn orderby
|
||||
* (it corresponds to elements of orderby field)
|
||||
* [default: all ascending]
|
||||
* </li>
|
||||
* <li>conditions - array of hashes with structure:
|
||||
* <ul>
|
||||
* <li>cat - string, metadata category name</li>
|
||||
* <li>op - string, operator - meaningful values:
|
||||
* 'full', 'partial', 'prefix', '=', '<',
|
||||
* '<=', '>', '>='</li>
|
||||
* <li>val - string, search value</li>
|
||||
* </ul>
|
||||
* </li>
|
||||
* </ul>
|
||||
* @param int $limit
|
||||
* limit for result arrays (0 means unlimited)
|
||||
* @param int $offset
|
||||
* starting point (0 means without offset)
|
||||
* @return array
|
||||
* array of hashes, fields:
|
||||
* cnt : integer - number of matching gunids
|
||||
* of files have been found
|
||||
* results : array of hashes:
|
||||
* gunid: string
|
||||
* type: string - audioclip | playlist | webstream
|
||||
* title: string - dc:title from metadata
|
||||
* creator: string - dc:creator from metadata
|
||||
* source: string - dc:source from metadata
|
||||
* length: string - dcterms:extent in extent format
|
||||
*/
|
||||
public function bsLocalSearch($criteria, $limit=0, $offset=0)
|
||||
{
|
||||
global $CC_CONFIG, $CC_DBC;
|
||||
|
||||
// Input values
|
||||
$filetype = (isset($criteria['filetype']) ? $criteria['filetype'] : 'all');
|
||||
$filetype = strtolower($filetype);
|
||||
if (!array_key_exists($filetype, $this->filetypes)) {
|
||||
return PEAR::raiseError(__FILE__.":".__LINE__.': unknown filetype in search criteria');
|
||||
}
|
||||
$filetype = $this->filetypes[$filetype];
|
||||
$operator = (isset($criteria['operator']) ? $criteria['operator'] : 'and');
|
||||
$operator = strtolower($operator);
|
||||
$conditions = (isset($criteria['conditions']) ? $criteria['conditions'] : array());
|
||||
|
||||
// Create the WHERE clause - this is the actual search part
|
||||
$whereArr = $this->_makeWhereArr($conditions);
|
||||
|
||||
// Metadata values to fetch
|
||||
$metadataNames = array('dc:creator', 'dc:source', 'ls:track_num', 'dc:title', 'dcterms:extent');
|
||||
|
||||
// Order by clause
|
||||
$orderby = TRUE;
|
||||
$orderByAllowedValues = array('dc:creator', 'dc:source', 'dc:title', 'dcterms:extent', "ls:track_num");
|
||||
$orderByDefaults = array('dc:creator', 'dc:source', 'dc:title');
|
||||
if ((!isset($criteria['orderby']))
|
||||
|| (is_array($criteria['orderby']) && (count($criteria['orderby'])==0))) {
|
||||
// default ORDER BY
|
||||
// PaulB: track number removed because it doesnt work yet because
|
||||
// if track_num is not an integer (e.g. bad metadata like "1/20",
|
||||
// or if the field is blank) the SQL statement gives an error.
|
||||
//$orderbyQns = array('dc:creator', 'dc:source', 'ls:track_num', 'dc:title');
|
||||
$orderbyQns = $orderByDefaults;
|
||||
} else {
|
||||
// ORDER BY clause is given in the parameters.
|
||||
|
||||
// Convert the parameter to an array if it isnt already.
|
||||
$orderbyQns = $criteria['orderby'];
|
||||
if (!is_array($orderbyQns)) {
|
||||
$orderbyQns = array($orderbyQns);
|
||||
}
|
||||
|
||||
// Check that it has valid ORDER BY values, if not, revert
|
||||
// to the default ORDER BY values.
|
||||
foreach ($orderbyQns as $metadataTag) {
|
||||
if (!in_array($metadataTag, $orderByAllowedValues)) {
|
||||
$orderbyQns = $orderByDefaults;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$descA = (isset($criteria['desc']) ? $criteria['desc'] : NULL);
|
||||
if (!is_array($descA)) {
|
||||
$descA = array($descA);
|
||||
}
|
||||
|
||||
$orderBySql = array();
|
||||
// $dataName contains the names of the metadata columns we want to
|
||||
// fetch. It is indexed numerically starting from 1, and the value
|
||||
// in the array is the qualified name with ":" replaced with "_".
|
||||
// e.g. "dc:creator" becomes "dc_creator".
|
||||
foreach ($orderbyQns as $xmlTag) {
|
||||
$columnName = StoredFile::xmlCategoryToDbColumn($xmlTag);
|
||||
$orderBySql[] = $columnName;
|
||||
}
|
||||
|
||||
// Build WHERE clause
|
||||
$whereClause = "";
|
||||
if (!is_null($filetype)) {
|
||||
$whereClause .= "WHERE (ftype='$filetype')";
|
||||
}
|
||||
else {
|
||||
$whereClause .= "WHERE (ftype is NOT NULL)";
|
||||
}
|
||||
if (count($whereArr) != 0) {
|
||||
if ($operator == 'and') {
|
||||
$whereClause .= " AND ((".join(") AND (", $whereArr)."))";
|
||||
} else {
|
||||
$whereClause .= " AND ((".join(") OR (", $whereArr)."))";
|
||||
}
|
||||
}
|
||||
|
||||
// Final query
|
||||
|
||||
//"dcterms:extent" => "length",
|
||||
//"dc:title" => "track_title",
|
||||
//"dc:creator" => "artist_name",
|
||||
//dc:description
|
||||
|
||||
$plSelect = "SELECT ";
|
||||
$fileSelect = "SELECT ";
|
||||
$_SESSION["br"] = "";
|
||||
foreach (Metadata::GetMapMetadataXmlToDb() as $key => $val){
|
||||
$_SESSION["br"] .= "key: ".$key." value:".$val.", ";
|
||||
if($key === "dc:title"){
|
||||
$plSelect .= "name AS ".$val.", ";
|
||||
$fileSelect .= $val.", ";
|
||||
}
|
||||
else if ($key === "dc:creator"){
|
||||
$plSelect .= "creator AS ".$val.", ";
|
||||
$fileSelect .= $val.", ";
|
||||
}
|
||||
else if ($key === "dcterms:extent"){
|
||||
$plSelect .= "length, ";
|
||||
$fileSelect .= "length, ";
|
||||
}
|
||||
else if ($key === "dc:description"){
|
||||
$plSelect .= "text(description) AS ".$val.", ";
|
||||
$fileSelect .= $val.", ";
|
||||
}
|
||||
else {
|
||||
$plSelect .= "NULL AS ".$val.", ";
|
||||
$fileSelect .= $val.", ";
|
||||
}
|
||||
}
|
||||
|
||||
$sql = "SELECT * FROM ((".$plSelect."PL.id, 'playlist' AS ftype
|
||||
FROM ".$CC_CONFIG["playListTable"]." AS PL
|
||||
LEFT JOIN ".$CC_CONFIG['playListTimeView']." PLT ON PL.id = PLT.id)
|
||||
|
||||
UNION
|
||||
|
||||
(".$fileSelect."id, ftype FROM ".$CC_CONFIG["filesTable"]." AS FILES)) AS RESULTS ";
|
||||
|
||||
$sql .= $whereClause;
|
||||
|
||||
if ($orderby) {
|
||||
$sql .= " ORDER BY ".join(",", $orderBySql);
|
||||
}
|
||||
|
||||
$_SESSION["debugsql"] = $sql;
|
||||
|
||||
$res = $CC_DBC->getAll($sql);
|
||||
if (PEAR::isError($res)) {
|
||||
return $res;
|
||||
}
|
||||
if (!is_array($res)) {
|
||||
$res = array();
|
||||
}
|
||||
|
||||
$count = count($res);
|
||||
$_SESSION["br"] .= " COUNT: ".$count;
|
||||
|
||||
$res = array_slice($res, $offset != 0 ? $offset : 0, $limit != 0 ? $limit : 10);
|
||||
|
||||
$eres = array();
|
||||
foreach ($res as $it) {
|
||||
$eres[] = array(
|
||||
'id' => $it['id'],
|
||||
'type' => strtolower($it['ftype']),
|
||||
'title' => $it['track_title'],
|
||||
'creator' => $it['artist_name'],
|
||||
'duration' => $it['length'],
|
||||
'source' => $it['album_title'],
|
||||
'track_num' => $it['track_number'],
|
||||
);
|
||||
}
|
||||
return array('results'=>$eres, 'cnt'=>$count);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return values of specified metadata category
|
||||
*
|
||||
* @param string $category
|
||||
* metadata category name with or without namespace prefix (dc:title, author)
|
||||
* @param int $limit
|
||||
* limit for result arrays (0 means unlimited)
|
||||
* @param int $offset
|
||||
* starting point (0 means without offset)
|
||||
* @param array $criteria
|
||||
* see bsLocalSearch method
|
||||
* @return array
|
||||
* hash, fields:
|
||||
* results : array with found values
|
||||
* cnt : integer - number of matching values
|
||||
*/
|
||||
public function bsBrowseCategory($category, $limit=0, $offset=0, $criteria=NULL)
|
||||
{
|
||||
global $CC_CONFIG, $CC_DBC;
|
||||
|
||||
$pl_cat = array(
|
||||
"dcterms:extent" => "length",
|
||||
"dc:title" => "name",
|
||||
"dc:creator" => "creator",
|
||||
"dc:description" => "description"
|
||||
);
|
||||
|
||||
$category = strtolower($category);
|
||||
$columnName = StoredFile::xmlCategoryToDbColumn($category);
|
||||
if (is_null($columnName)) {
|
||||
return new PEAR_Error(__FILE__.":".__LINE__." -- could not map XML category to DB column.");
|
||||
}
|
||||
$sql = "SELECT DISTINCT $columnName FROM ".$CC_CONFIG["filesTable"];
|
||||
$limitPart = ($limit != 0 ? " LIMIT $limit" : '' ).
|
||||
($offset != 0 ? " OFFSET $offset" : '' );
|
||||
$countRowsSql = "SELECT COUNT(DISTINCT $columnName) FROM ".$CC_CONFIG["filesTable"];
|
||||
|
||||
//$_SESSION["br"] = "in Browse Category: ".$category;
|
||||
$cnt = $CC_DBC->GetOne($countRowsSql);
|
||||
if (PEAR::isError($cnt)) {
|
||||
return $cnt;
|
||||
}
|
||||
$res = $CC_DBC->getCol($sql.$limitPart);
|
||||
if (PEAR::isError($res)) {
|
||||
return $res;
|
||||
}
|
||||
if (!is_array($res)) {
|
||||
$res = array();
|
||||
}
|
||||
|
||||
if (array_key_exists($category, $pl_cat) && $category !== "dcterms:extent") {
|
||||
$columnName = $pl_cat[$category];
|
||||
|
||||
$sql = "SELECT DISTINCT $columnName FROM ".$CC_CONFIG["playListTable"];
|
||||
$limitPart = ($limit != 0 ? " LIMIT $limit" : '' ).
|
||||
($offset != 0 ? " OFFSET $offset" : '' );
|
||||
$countRowsSql = "SELECT COUNT(DISTINCT $columnName) FROM ".$CC_CONFIG["playListTable"];
|
||||
|
||||
$pl_cnt = $CC_DBC->GetOne($countRowsSql);
|
||||
if (PEAR::isError($cnt)) {
|
||||
return $cnt;
|
||||
}
|
||||
$pl_res = $CC_DBC->getCol($sql.$limitPart);
|
||||
if (PEAR::isError($res)) {
|
||||
return $pl_res;
|
||||
}
|
||||
if (!is_array($pl_res)) {
|
||||
$pl_res = array();
|
||||
}
|
||||
|
||||
$res = array_merge($res, $pl_res);
|
||||
$res = array_slice($res, 0, $limit);
|
||||
$cnt = $cnt + $pl_cnt;
|
||||
}
|
||||
else if ($category === "dcterms:extent") {
|
||||
$columnName = $pl_cat[$category];
|
||||
|
||||
$limitPart = ($limit != 0 ? " LIMIT $limit" : '' ).
|
||||
($offset != 0 ? " OFFSET $offset" : '' );
|
||||
|
||||
$sql = "SELECT DISTINCT length AS $columnName FROM ".$CC_CONFIG["playListTimeView"];
|
||||
|
||||
$countRowsSql = "SELECT COUNT(DISTINCT length) FROM ".$CC_CONFIG["playListTimeView"];
|
||||
|
||||
$pl_cnt = $CC_DBC->GetOne($countRowsSql);
|
||||
if (PEAR::isError($cnt)) {
|
||||
return $cnt;
|
||||
}
|
||||
$pl_res = $CC_DBC->getCol($sql.$limitPart);
|
||||
if (PEAR::isError($res)) {
|
||||
return $pl_res;
|
||||
}
|
||||
if (!is_array($pl_res)) {
|
||||
$pl_res = array();
|
||||
}
|
||||
|
||||
$res = array_merge($res, $pl_res);
|
||||
$res = array_slice($res, 0, $limit);
|
||||
$cnt = $cnt + $pl_cnt;
|
||||
}
|
||||
|
||||
return array('results'=>$res, 'cnt'=>$cnt);
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------------------------------- methods4playlists */
|
||||
|
||||
/* ================================================== "protected" methods */
|
||||
/**
|
||||
* Check authorization - auxiliary method
|
||||
*
|
||||
* @param array $acts
|
||||
* Array of actions
|
||||
* @param array $pars
|
||||
* Array of parameters - e.g. ids
|
||||
* @param string $sessid
|
||||
* Session id
|
||||
* @return true|PEAR_Error
|
||||
*/
|
||||
public static function Authorize($acts, $pars, $sessid='')
|
||||
{
|
||||
$userid = Alib::GetSessUserId($sessid);
|
||||
if (PEAR::isError($userid)) {
|
||||
return $userid;
|
||||
}
|
||||
if (is_null($userid)) {
|
||||
return PEAR::raiseError(
|
||||
"BasicStor::Authorize: invalid session", GBERR_DENY);
|
||||
}
|
||||
if (!is_array($pars)) {
|
||||
$pars = array($pars);
|
||||
}
|
||||
if (!is_array($acts)) {
|
||||
$acts = array($acts);
|
||||
}
|
||||
$perm = true;
|
||||
if ($perm) {
|
||||
return TRUE;
|
||||
}
|
||||
$adesc = "[".join(',',$acts)."]";
|
||||
return PEAR::raiseError(
|
||||
"BasicStor::$adesc: access denied", GBERR_DENY);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set playlist edit flag
|
||||
*
|
||||
* @param string $p_playlistId
|
||||
* Playlist unique ID
|
||||
* @param boolean $p_val
|
||||
* Set/clear of edit flag
|
||||
* @param string $p_sessid
|
||||
* Session id
|
||||
* @param int $p_subjid
|
||||
* Subject id (if sessid is not specified)
|
||||
* @return boolean
|
||||
* previous state
|
||||
*/
|
||||
public function setEditFlag($p_playlistId, $p_val=TRUE, $p_sessid=NULL, $p_subjid=NULL)
|
||||
{
|
||||
if (!is_null($p_sessid)) {
|
||||
$p_subjid = Alib::GetSessUserId($p_sessid);
|
||||
if (PEAR::isError($p_subjid)) {
|
||||
return $p_subjid;
|
||||
}
|
||||
}
|
||||
$pl = Playlist::Recall($p_playlistId);
|
||||
if (is_null($pl) || PEAR::isError($pl)) {
|
||||
return $pl;
|
||||
}
|
||||
$state = $pl->getState();
|
||||
if ($p_val) {
|
||||
$r = $pl->setState('edited', $p_subjid);
|
||||
} else {
|
||||
$r = $pl->setState('ready', 'NULL');
|
||||
}
|
||||
if (PEAR::isError($r)) {
|
||||
return $r;
|
||||
}
|
||||
return ($state == 'edited');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if playlist is marked as edited
|
||||
*
|
||||
* @param string $p_playlistId
|
||||
* Playlist global unique ID
|
||||
* @return FALSE|int
|
||||
* ID of user editing it
|
||||
*/
|
||||
public function isEdited($p_playlistId)
|
||||
{
|
||||
$pl = Playlist::Recall($p_playlistId);
|
||||
if (is_null($pl) || PEAR::isError($pl)) {
|
||||
return $pl;
|
||||
}
|
||||
if (!$pl->isEdited($p_playlistId)) {
|
||||
return FALSE;
|
||||
}
|
||||
return $pl->isEditedBy($p_playlistId);
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------------------- redefined "protected" methods */
|
||||
/* ========================================================= misc methods */
|
||||
/**
|
||||
* Write string to file
|
||||
*
|
||||
* @param string $str
|
||||
* string to be written to file
|
||||
* @param string $fname
|
||||
* pathname to file
|
||||
* @return TRUE|raiseError
|
||||
*/
|
||||
private static function WriteStringToFile($p_str, $p_fname)
|
||||
{
|
||||
$fp = @fopen($p_fname, "w");
|
||||
if ($fp === FALSE) {
|
||||
return PEAR::raiseError(
|
||||
"BasicStor::WriteStringToFile: cannot open file ($p_fname)"
|
||||
);
|
||||
}
|
||||
fwrite($fp, $p_str);
|
||||
fclose($fp);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
/* =============================================== test and debug methods */
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
*/
|
||||
public function debug($va)
|
||||
{
|
||||
echo"<pre>\n";
|
||||
print_r($va);
|
||||
}
|
||||
|
||||
/**
|
||||
* Aux logging for debug
|
||||
*
|
||||
* @param string $msg - log message
|
||||
*/
|
||||
public function debugLog($msg)
|
||||
{
|
||||
global $CC_CONFIG, $CC_DBC;
|
||||
$fp = fopen($CC_CONFIG['storageDir']."/log", "a") or die("Can't write to log\n");
|
||||
fputs($fp, date("H:i:s").">$msg<\n");
|
||||
fclose($fp);
|
||||
}
|
||||
|
||||
} // class BasicStor
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
<?php
|
||||
require_once("StoredFile.php");
|
||||
require_once("BasicStor.php");
|
||||
|
||||
class ScheduleGroup {
|
||||
|
||||
|
@ -320,7 +319,7 @@ class Schedule {
|
|||
|
||||
return $res;
|
||||
}
|
||||
|
||||
|
||||
public static function GetTotalShowTime($instance_id) {
|
||||
global $CC_CONFIG, $CC_DBC;
|
||||
|
||||
|
@ -335,32 +334,9 @@ class Schedule {
|
|||
return $res;
|
||||
}
|
||||
|
||||
public static function getPercentScheduledInRange($s_datetime, $e_datetime) {
|
||||
|
||||
$time = Schedule::getTimeScheduledInRange($s_datetime, $e_datetime);
|
||||
|
||||
$con = Propel::getConnection(CcSchedulePeer::DATABASE_NAME);
|
||||
|
||||
$sql = "SELECT EXTRACT(EPOCH FROM TIMESTAMP WITH TIME ZONE '{$s_datetime}')";
|
||||
$r = $con->query($sql);
|
||||
$s_epoch = $r->fetchColumn(0);
|
||||
|
||||
$sql = "SELECT EXTRACT(EPOCH FROM TIMESTAMP WITH TIME ZONE '{$e_datetime}')";
|
||||
$r = $con->query($sql);
|
||||
$e_epoch = $r->fetchColumn(0);
|
||||
|
||||
$sql = "SELECT EXTRACT(EPOCH FROM INTERVAL '{$time}')";
|
||||
$r = $con->query($sql);
|
||||
$i_epoch = $r->fetchColumn(0);
|
||||
|
||||
$percent = ceil(($i_epoch / ($e_epoch - $s_epoch)) * 100);
|
||||
|
||||
return $percent;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
|
@ -690,7 +666,7 @@ class Schedule {
|
|||
public static function ExportRangeAsJson($p_fromDateTime, $p_toDateTime)
|
||||
{
|
||||
global $CC_CONFIG, $CC_DBC;
|
||||
|
||||
|
||||
$range_start = Schedule::PypoTimeToCcTime($p_fromDateTime);
|
||||
$range_end = Schedule::PypoTimeToCcTime($p_toDateTime);
|
||||
|
||||
|
|
|
@ -435,7 +435,7 @@ class Show {
|
|||
$event[$key] = $value;
|
||||
}
|
||||
|
||||
$percent = Schedule::getPercentScheduledInRange($show["starts"], $show["ends"]);
|
||||
$percent = Schedule::GetPercentScheduled($show["instance_id"], $show["starts"], $show["ends"]);
|
||||
$event["percent"] = $percent;
|
||||
|
||||
return $event;
|
||||
|
@ -491,10 +491,9 @@ class ShowInstance {
|
|||
global $CC_DBC;
|
||||
|
||||
$sql = "UPDATE cc_schedule
|
||||
SET starts = (starts + interval '{$deltaDay} days' + interval '{$deltaHours}:{$deltaMin}'),
|
||||
SET starts = (starts + interval '{$deltaDay} days' + interval '{$deltaHours}:{$deltaMin}'),
|
||||
ends = (ends + interval '{$deltaDay} days' + interval '{$deltaHours}:{$deltaMin}')
|
||||
WHERE (starts >= '{$this->getShowStart()}')
|
||||
AND (ends <= '{$this->getShowEnd()}')";
|
||||
WHERE instance_id = '{$this->_instanceId}'";
|
||||
|
||||
$CC_DBC->query($sql);
|
||||
}
|
||||
|
@ -560,17 +559,8 @@ class ShowInstance {
|
|||
return "Should not overlap shows";
|
||||
}
|
||||
}
|
||||
//have to check if any scheduled content still fits.
|
||||
else{
|
||||
$scheduledTime = $this->getTimeScheduled();
|
||||
$sql = "SELECT (timestamp '{$new_ends}' - timestamp '{$starts}') >= interval '{$scheduledTime}'";
|
||||
$scheduledContentFits = $CC_DBC->GetOne($sql);
|
||||
|
||||
if($scheduledContentFits != "t") {
|
||||
return "Must remove some scheduled content.";
|
||||
}
|
||||
}
|
||||
|
||||
//with overbooking no longer need to check already scheduled content still fits.
|
||||
|
||||
$this->setShowEnd($new_ends);
|
||||
}
|
||||
|
||||
|
@ -639,6 +629,7 @@ class ShowInstance {
|
|||
}
|
||||
|
||||
public function getTimeScheduled() {
|
||||
|
||||
$instance_id = $this->getShowInstanceId();
|
||||
$time = Schedule::GetTotalShowTime($instance_id);
|
||||
|
||||
|
@ -656,23 +647,16 @@ class ShowInstance {
|
|||
return $time;
|
||||
}
|
||||
|
||||
public function getPercentScheduledInRange(){
|
||||
public function getPercentScheduled() {
|
||||
|
||||
$start_timestamp = $this->getShowStart();
|
||||
$end_timestamp = $this->getShowEnd();
|
||||
|
||||
return Schedule::getPercentScheduledInRange($start_timestamp, $end_timestamp);
|
||||
}
|
||||
|
||||
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();
|
||||
|
@ -686,9 +670,9 @@ class ShowInstance {
|
|||
|
||||
public function searchPlaylistsForShow($datatables){
|
||||
|
||||
$length = $this->getTimeUnScheduled();
|
||||
$time_remaining = $this->getTimeUnScheduled();
|
||||
|
||||
return StoredFile::searchPlaylistsForSchedule($length, $datatables);
|
||||
return StoredFile::searchPlaylistsForSchedule($time_remaining, $datatables);
|
||||
}
|
||||
|
||||
public function getShowListContent() {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<?php
|
||||
require_once("Playlist.php");
|
||||
require_once(dirname(__FILE__)."/../../library/getid3/var/getid3.php");
|
||||
require_once("BasicStor.php");
|
||||
require_once("Schedule.php");
|
||||
|
||||
class Metadata {
|
||||
|
@ -572,7 +571,6 @@ class StoredFile {
|
|||
}
|
||||
|
||||
|
||||
/* ========= 'factory' methods - should be called to construct StoredFile */
|
||||
/**
|
||||
* Create instance of StoredFile object and insert new file
|
||||
*
|
||||
|
@ -759,32 +757,6 @@ class StoredFile {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create instance of StoreFile object and recall existing file
|
||||
* by access token.
|
||||
*
|
||||
* @param string $p_token
|
||||
* access token
|
||||
* @return StoredFile
|
||||
*/
|
||||
public static function RecallByToken($p_token)
|
||||
{
|
||||
global $CC_CONFIG, $CC_DBC;
|
||||
$sql = "SELECT gunid"
|
||||
." FROM ".$CC_CONFIG['accessTable']
|
||||
." WHERE token=x'$p_token'::bigint";
|
||||
$gunid = $CC_DBC->getOne($sql);
|
||||
if (PEAR::isError($gunid)) {
|
||||
return $gunid;
|
||||
}
|
||||
if (is_null($gunid)) {
|
||||
return PEAR::raiseError(
|
||||
"StoredFile::RecallByToken: invalid token ($p_token)", GBERR_AOBJNEX);
|
||||
}
|
||||
return StoredFile::Recall(null, $gunid);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generate the location to store the file.
|
||||
* It creates the subdirectory if needed.
|
||||
|
@ -793,7 +765,6 @@ class StoredFile {
|
|||
{
|
||||
global $CC_CONFIG, $CC_DBC;
|
||||
$resDir = $CC_CONFIG['storageDir']."/".substr($this->gunid, 0, 3);
|
||||
// see Transport::_getResDir too for resDir name create code
|
||||
if (!is_dir($resDir)) {
|
||||
mkdir($resDir, 02775);
|
||||
chmod($resDir, 02775);
|
||||
|
@ -838,7 +809,6 @@ class StoredFile {
|
|||
} else {
|
||||
$dstFile = $p_localFilePath;
|
||||
$r = TRUE;
|
||||
//$r = @symlink($p_localFilePath, $dstFile);
|
||||
}
|
||||
$this->filepath = $dstFile;
|
||||
$sqlPath = pg_escape_string($this->filepath);
|
||||
|
@ -935,72 +905,6 @@ class StoredFile {
|
|||
return $storedFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increase access counter, create access token, insert access record.
|
||||
*
|
||||
* @param int $parent
|
||||
* parent token
|
||||
* @return array
|
||||
* array with: access URL, access token
|
||||
*/
|
||||
public function accessRawMediaData($p_parent='0')
|
||||
{
|
||||
$realFname = $this->getRealFilePath();
|
||||
$ext = $this->getFileExtension();
|
||||
$res = BasicStor::bsAccess($realFname, $ext, $this->gunid, 'access', $p_parent);
|
||||
if (PEAR::isError($res)) {
|
||||
return $res;
|
||||
}
|
||||
$resultArray =
|
||||
array('url'=>"file://{$res['fname']}", 'token'=>$res['token']);
|
||||
return $resultArray;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Decrease access couter, delete access record.
|
||||
*
|
||||
* @param string $p_token
|
||||
* access token
|
||||
* @return boolean
|
||||
*/
|
||||
public function releaseRawMediaData($p_token)
|
||||
{
|
||||
$res = BasicStor::bsRelease($p_token);
|
||||
if (PEAR::isError($res)) {
|
||||
return $res;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Replace media file only with new binary file
|
||||
*
|
||||
* @param string $p_localFilePath
|
||||
* local path to media file
|
||||
* @return TRUE|PEAR_Error
|
||||
*/
|
||||
public function setRawMediaData($p_localFilePath)
|
||||
{
|
||||
$res = $this->replaceFile($p_localFilePath);
|
||||
if (PEAR::isError($res)) {
|
||||
return $res;
|
||||
}
|
||||
$mime = $this->getMime();
|
||||
if ($mime !== FALSE) {
|
||||
$res = $this->setMime($mime);
|
||||
if (PEAR::isError($res)) {
|
||||
return $res;
|
||||
}
|
||||
}
|
||||
// $r = $this->md->regenerateXmlFile();
|
||||
// if (PEAR::isError($r)) {
|
||||
// return $r;
|
||||
// }
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
private static function NormalizeExtent($v)
|
||||
{
|
||||
|
@ -1654,10 +1558,12 @@ class StoredFile {
|
|||
}
|
||||
|
||||
|
||||
public static function searchPlaylistsForSchedule($p_length, $datatables) {
|
||||
public static function searchPlaylistsForSchedule($time_remaining, $datatables)
|
||||
{
|
||||
$fromTable = "cc_playlist AS pl LEFT JOIN cc_playlisttimes AS plt USING(id) LEFT JOIN cc_subjs AS sub ON pl.editedby = sub.id";
|
||||
$datatables["optWhere"][] = "INTERVAL '{$p_length}' > INTERVAL '00:00:00'";
|
||||
$datatables["optWhere"][] = "INTERVAL '{$time_remaining}' > INTERVAL '00:00:00'";
|
||||
$datatables["optWhere"][] = "plt.length > INTERVAL '00:00:00'";
|
||||
|
||||
return StoredFile::searchFiles($fromTable, $datatables);
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,10 @@
|
|||
*
|
||||
*/
|
||||
|
||||
function scheduleRefetchEvents() {
|
||||
$("#schedule_calendar").fullCalendar( 'refetchEvents' );
|
||||
}
|
||||
|
||||
function openAddShowForm() {
|
||||
|
||||
if(($("#add-show-form").length == 1) && ($("#add-show-form").css('display')=='none')) {
|
||||
|
@ -221,6 +225,8 @@ function eventResize( event, dayDelta, minuteDelta, revertFunc, jsEvent, ui, vie
|
|||
alert(json.error);
|
||||
revertFunc();
|
||||
}
|
||||
|
||||
scheduleRefetchEvents();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -204,10 +204,6 @@ function buildEditDialog(json){
|
|||
|
||||
}
|
||||
|
||||
function scheduleRefetchEvents() {
|
||||
$("#schedule_calendar").fullCalendar( 'refetchEvents' );
|
||||
}
|
||||
|
||||
$(window).load(function() {
|
||||
var mainHeight = document.documentElement.clientHeight - 200 - 50;
|
||||
|
||||
|
|
|
@ -4,10 +4,14 @@ import logging
|
|||
import json
|
||||
import time
|
||||
import datetime
|
||||
import os
|
||||
|
||||
from eci import *
|
||||
from configobj import ConfigObj
|
||||
import subprocess
|
||||
|
||||
from poster.encode import multipart_encode
|
||||
from poster.streaminghttp import register_openers
|
||||
import urllib2
|
||||
|
||||
# loading config file
|
||||
try:
|
||||
|
@ -73,11 +77,6 @@ def check_record():
|
|||
start_time = sorted_show_keys[0]
|
||||
next_show = getDateTimeObj(start_time)
|
||||
|
||||
#print tnow, next_show
|
||||
|
||||
#tnow = getDateTimeObj("2011-03-04 16:00:00")
|
||||
#next_show = getDateTimeObj("2011-03-04 16:00:01")
|
||||
|
||||
delta = next_show - tnow
|
||||
|
||||
if delta <= datetime.timedelta(seconds=60):
|
||||
|
@ -85,20 +84,15 @@ def check_record():
|
|||
|
||||
show_length = shows_to_record[start_time]
|
||||
filepath = record_show(show_length.seconds, start_time)
|
||||
#filepath = record_show(10, "2011-03-04 16:00:00")
|
||||
|
||||
command = "%s -c %s" %("../../utils/airtime-import", filepath)
|
||||
subprocess.call([command],shell=True)
|
||||
upload_file(filepath)
|
||||
|
||||
|
||||
def get_shows():
|
||||
|
||||
url = config["base_url"] + config["show_schedule_url"]
|
||||
#url = url.replace("%%from%%", "2011-03-13 20:00:00")
|
||||
#url = url.replace("%%to%%", "2011-04-17 21:00:00")
|
||||
|
||||
response = urllib.urlopen(url)
|
||||
data = response.read()
|
||||
|
||||
response_json = json.loads(data)
|
||||
shows = response_json[u'shows']
|
||||
print shows
|
||||
|
@ -107,22 +101,16 @@ def get_shows():
|
|||
process_shows(shows)
|
||||
check_record()
|
||||
|
||||
def upload_file():
|
||||
def upload_file(filepath):
|
||||
|
||||
from poster.encode import multipart_encode
|
||||
from poster.streaminghttp import register_openers
|
||||
import urllib2
|
||||
filename = os.path.split(filepath)[1]
|
||||
|
||||
# Register the streaming http handlers with urllib2
|
||||
register_openers()
|
||||
|
||||
# Start the multipart/form-data encoding of the file "DSC0001.jpg"
|
||||
# "image1" is the name of the parameter, which is normally set
|
||||
# via the "name" parameter of the HTML <input> tag.
|
||||
|
||||
# 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("/home/naomi/Music/testoutput.mp3", "rb"), 'name': 'crazy.mp3'})
|
||||
datagen, headers = multipart_encode({"file": open(filepath, "rb"), 'name': filename})
|
||||
|
||||
url = config["base_url"] + config["upload_file_url"]
|
||||
|
||||
|
@ -133,11 +121,11 @@ def upload_file():
|
|||
|
||||
if __name__ == '__main__':
|
||||
|
||||
# while True:
|
||||
# get_shows()
|
||||
# time.sleep(30)
|
||||
while True:
|
||||
get_shows()
|
||||
time.sleep(30)
|
||||
|
||||
upload_file()
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue