Merge branch 'devel' of dev.sourcefabric.org:airtime into cc-2528-use-virtualenv-for-installing

Conflicts:
	python_apps/media-monitor/airtime-media-monitor
This commit is contained in:
James 2011-07-19 15:09:55 -04:00
commit 107c100cc4
35 changed files with 842 additions and 290 deletions

View File

@ -1,2 +1,2 @@
PRODUCT_ID=Airtime
PRODUCT_RELEASE=1.9.0-beta4
PRODUCT_RELEASE=1.9.0-RC1

View File

@ -1,6 +1,6 @@
<?php
define('AIRTIME_VERSION', '1.9.0-beta4');
define('AIRTIME_VERSION', '1.9.0-RC1');
define('AIRTIME_COPYRIGHT_DATE', '2010-2011');
define('AIRTIME_REST_VERSION', '1.1');

View File

@ -341,7 +341,7 @@ class ApiController extends Zend_Controller_Action
$file = StoredFile::Recall($file_id);
//$show_instance = $this->_getParam('show_instance');
$show_name = "";
$show_name = null;
try {
$show_inst = new ShowInstance($show_instance_id);
@ -359,10 +359,17 @@ class ApiController extends Zend_Controller_Action
$showCanceled = true;
}
$tmpTitle = !(empty($show_name))?$show_name."-":"";
$tmpTitle .= $file->getName();
if (isset($show_name)) {
$tmpTitle = "$show_name-$show_start_time";
$tmpTitle = str_replace(" ", "-", $tmpTitle);
}
else {
$tmpTitle = $file->getName();
}
$file->setMetadataValue('MDATA_KEY_TITLE', $tmpTitle);
$file->setMetadataValue('MDATA_KEY_CREATOR', "Airtime Show Recorder");
$file->setMetadataValue('MDATA_KEY_TRACKNUMBER', null);
if (!$showCanceled && Application_Model_Preference::GetDoSoundCloudUpload())
{

View File

@ -63,6 +63,9 @@ class LibraryController extends Zend_Controller_Action
$paramsPop = str_replace('#id#', $id, $params);
$paramsPop = str_replace('#type#', $type, $paramsPop);
$userInfo = Zend_Auth::getInstance()->getStorage()->read();
$user = new User($userInfo->id);
$pl_sess = $this->pl_sess;
if($type === "au") {
@ -85,10 +88,13 @@ class LibraryController extends Zend_Controller_Action
$menu[] = array('action' => array('type' => 'gourl', 'url' => $url),
'title' => 'Download');
if ($user->isAdmin()) {
$menu[] = array('action' => array('type' => 'fn',
'callback' => "window['confirmDeleteAudioClip']('$paramsPop')"),
'title' => 'Delete');
}
}
else if($type === "pl") {
if(!isset($pl_sess->id) || $pl_sess->id !== $id) {
@ -121,6 +127,10 @@ class LibraryController extends Zend_Controller_Action
public function deleteAction()
{
$id = $this->_getParam('id');
$userInfo = Zend_Auth::getInstance()->getStorage()->read();
$user = new User($userInfo->id);
if ($user->isAdmin()) {
if (!is_null($id)) {
$file = StoredFile::Recall($id);
@ -141,6 +151,7 @@ class LibraryController extends Zend_Controller_Action
return;
}
else {
$res = settype($res, "integer");
$data = array("filepath" => $file->getFilePath(), "delete" => $res);
RabbitMq::SendMessageToMediaMonitor("file_delete", $data);
}
@ -148,6 +159,7 @@ class LibraryController extends Zend_Controller_Action
$this->view->id = $id;
}
}
public function contentsAction()
{

View File

@ -25,9 +25,9 @@ class NowplayingController extends Zend_Controller_Action
$userInfo = Zend_Auth::getInstance()->getStorage()->read();
$user = new User($userInfo->id);
if ($request->isPost()) {
$form = new Application_Form_RegisterAirtime();
if ($request->isPost()) {
$values = $request->getPost();
if ($values["Publicise"] != 1){
Application_Model_Preference::SetSupportFeedback($values["SupportFeedback"]);
@ -64,6 +64,10 @@ class NowplayingController extends Zend_Controller_Action
//popup if previous page was login
if($refer_sses->referrer == 'login' && Application_Model_Nowplaying::ShouldShowPopUp()
&& !Application_Model_Preference::GetSupportFeedback() && $user->isAdmin()){
$form = new Application_Form_RegisterAirtime();
$logo = Application_Model_Preference::GetStationLogo();
if($logo){
$this->view->logoImg = $logo;

View File

@ -123,9 +123,10 @@ class PreferenceController extends Zend_Controller_Action
$chosen = $this->getRequest()->getParam("dir");
$element = $this->getRequest()->getParam("element");
$watched_dirs_form = new Application_Form_WatchedDirPreferences();
$watched_dirs_form->populate(array('storageFolder' => $chosen));
$res = MusicDir::setStorDir($chosen);
if($res['code'] != 0){
$watched_dirs_form->populate(array('storageFolder' => $chosen));
$watched_dirs_form->getElement($element)->setErrors(array($res['error']));
}
@ -137,9 +138,10 @@ class PreferenceController extends Zend_Controller_Action
$chosen = $this->getRequest()->getParam("dir");
$element = $this->getRequest()->getParam("element");
$watched_dirs_form = new Application_Form_WatchedDirPreferences();
$watched_dirs_form->populate(array('watchedFolder' => $chosen));
$res = MusicDir::addWatchedDir($chosen);
if($res['code'] != 0){
$watched_dirs_form->populate(array('watchedFolder' => $chosen));
$watched_dirs_form->getElement($element)->setErrors(array($res['error']));
}

View File

@ -1,5 +1,7 @@
<?php
class NestedDirectoryException extends Exception { }
class MusicDir {
/**
@ -40,7 +42,75 @@ class MusicDir {
public function remove()
{
global $CC_DBC;
$music_dir_id = $this->getId();
$sql = "SELECT DISTINCT s.instance_id from cc_music_dirs as md LEFT JOIN cc_files as f on f.directory = md.id
RIGHT JOIN cc_schedule as s on s.file_id = f.id WHERE md.id = $music_dir_id";
$show_instances = $CC_DBC->GetAll($sql);
$this->_dir->delete();
foreach ($show_instances as $show_instance_row) {
$temp_show = new ShowInstance($show_instance_row["instance_id"]);
$temp_show->updateScheduledTime();
}
RabbitMq::PushSchedule();
}
/**
* Checks if p_dir1 is the ancestor of p_dir2. Returns
* true if it is the ancestor, false otherwise. Note that
* /home/user is considered the ancestor of /home/user
*
* @param string $p_dir1
* The potential ancestor directory.
* @param string $p_dir2
* The potential descendent directory.
* @return boolean
* Returns true if it is the ancestor, false otherwise.
*/
private static function isAncestorDir($p_dir1, $p_dir2){
if (strlen($p_dir1) > strlen($p_dir2)){
return false;
}
return substr($p_dir2, 0, strlen($p_dir1)) == $p_dir1;
}
/**
* Checks whether the path provided is a valid path. A valid path
* is defined as not being nested within an existing watched directory,
* or vice-versa. Throws a NestedDirectoryException if invalid.
*
* @param string $p_path
* The path we want to validate
* @return void
*/
public static function isPathValid($p_path){
$dirs = self::getWatchedDirs();
$dirs[] = self::getStorDir();
foreach ($dirs as $dirObj){
$dir = $dirObj->getDirectory();
$diff = strlen($dir) - strlen($p_path);
if ($diff == 0){
if ($dir == $p_path){
throw new NestedDirectoryException("'$p_path' is already watched.");
}
} else if ($diff > 0){
if (self::isAncestorDir($p_path, $dir)){
throw new NestedDirectoryException("'$p_path' contains nested watched directory: '$dir'");
}
} else { /* diff < 0*/
if (self::isAncestorDir($dir, $p_path)){
throw new NestedDirectoryException("'$p_path' is nested within existing watched directory: '$dir'");
}
}
}
}
public static function addDir($p_path, $p_type)
@ -51,13 +121,20 @@ class MusicDir {
$dir = new CcMusicDirs();
$dir->setType($p_type);
$p_path = realpath($p_path)."/";
$temp = $dir->setDirectory($p_path);
try{
try {
/* isPathValid() checks if path is a substring or a superstring of an
* existing dir and if not, throws NestedDirectoryException */
self::isPathValid($p_path);
$dir->setDirectory($p_path);
$dir->save();
return array("code"=>0);
}
catch(Exception $e){
//echo $e->getMessage();
} catch (NestedDirectoryException $nde){
$msg = $nde->getMessage();
return array("code"=>1, "error"=>"$msg");
} catch(Exception $e){
return array("code"=>1, "error"=>"'$p_path' is already set as the current storage dir or in the watched folders list");
}

View File

@ -56,7 +56,7 @@ class ScheduleGroup {
// Check if there are any conflicts with existing entries
$metadata = $track->getMetadata();
$length = trim($metadata["length"]);
$length = $metadata['MDATA_KEY_DURATION'];
if (empty($length)) {
return new PEAR_Error("Length is empty.");
}

View File

@ -318,6 +318,13 @@ class StoredFile {
Playlist::DeleteFileFromAllPlaylists($this->getId());
$this->_file->delete();
if (isset($res)) {
return $res;
}
else {
return false;
}
}
/**
@ -341,19 +348,8 @@ class StoredFile {
return PEAR::raiseError('Cannot delete a file that is scheduled in the future.');
}
$storageDir = MusicDir::getStorDir()->getDirectory();
$dirCompare = substr($this->getFilePath(), 0, strlen($storageDir));
//return PEAR::raiseError("({$storageDir} , {$dirCompare})");
// Only delete the file from filesystem if it has been copied to the storage directory
if ($dirCompare === $storageDir) {
return true;
}
else {
return false;
}
}
/**
* Returns true if media file exists

View File

@ -35,7 +35,7 @@ function deleteAudioClip(json) {
//callbacks called by jjmenu
function confirmDeleteAudioClip(params){
if(confirm('Are you sure you want to delete?')){
if(confirm('The file will be deleted from disk, are you sure you want to delete?')){
var url = '/Library/delete' + params;
$.ajax({
url: url,

View File

@ -33,7 +33,7 @@ function setWatchedDirEvents() {
//knownPaths: [{text:'Desktop', image:'desktop.png', path:'/home'}],
knownPaths: [],
imageUrl: 'img/icons/',
systemImageUrl: 'img/browser/',
systemImageUrl: '/css/img/',
handlerUrl: '/Preference/server-browse/format/json',
title: 'Choose Folder to Watch',
basePath: '',

View File

@ -3,8 +3,9 @@
namespace DoctrineMigrations;
/*
update cc_files table to include to "directory" column as well as add foreign key relation to
cc_music_dirs table.
1) update cc_files table to include to "directory" column
2) create a foreign key relationship from cc_files to cc_music_dirs
3) create a foreign key relationship from cc_schedule to cc_files
*/
use Doctrine\DBAL\Migrations\AbstractMigration,
@ -14,21 +15,22 @@ class Version20110711161043 extends AbstractMigration
{
public function up(Schema $schema)
{
//CREATE the default value of "/srv/airtime/stor", this can be updated later in the upgrade script.
/* 1) update cc_files table to include to "directory" column */
$this->_addSql("INSERT INTO cc_music_dirs (type, directory) VALUES ('stor', '/srv/airtime/stor');");
$this->_addSql("INSERT INTO cc_music_dirs (type, directory) VALUES ('upgrade', '');");
$cc_music_dirs = $schema->getTable('cc_music_dirs');
//start cc_files modifications
/* 2) create a foreign key relationship from cc_files to cc_music_dirs */
$cc_files = $schema->getTable('cc_files');
$cc_files->addColumn('directory', 'integer', array('default'=> 2));
$cc_files->addNamedForeignKeyConstraint('cc_music_dirs_folder_fkey', $cc_music_dirs, array('directory'), array('id'), array('onDelete' => 'CASCADE'));
//end cc_files modifications
/* 3) create a foreign key relationship from cc_schedule to cc_files */
$cc_schedule = $schema->getTable('cc_schedule');
$cc_schedule->addNamedForeignKeyConstraint('cc_files_folder_fkey', $cc_files, array('file_id'), array('id'), array('onDelete' => 'CASCADE'));
}
public function down(Schema $schema)

View File

@ -19,6 +19,9 @@ python ${SCRIPTPATH}/../python_apps/create-pypo-user.py
php ${SCRIPTPATH}/include/airtime-install.php $@
echo -e "\n*** API Client Installation ***"
python ${SCRIPTPATH}/../python_apps/api_clients/install/api_client_install.py
echo -e "\n*** Pypo Installation ***"
python ${SCRIPTPATH}/../python_apps/pypo/install/pypo-install.py

View File

@ -23,6 +23,10 @@ python ${SCRIPTPATH}/../python_apps/show-recorder/install/recorder-uninstall.py
echo -e "\n*** Uninstalling Media Monitor ***"
python ${SCRIPTPATH}/../python_apps/media-monitor/install/media-monitor-uninstall.py
echo -e "\n*** Uninstalling API Client ***"
python ${SCRIPTPATH}/../python_apps/api_clients/install/api_client_uninstall.py
echo -e "\n*** Removing Pypo User ***"
python ${SCRIPTPATH}/../python_apps/remove-pypo-user.py

View File

@ -27,6 +27,7 @@ class AirtimeIni
const CONF_FILE_AIRTIME = "/etc/airtime/airtime.conf";
const CONF_FILE_PYPO = "/etc/airtime/pypo.cfg";
const CONF_FILE_RECORDER = "/etc/airtime/recorder.cfg";
const CONF_FILE_API_CLIENT = "/etc/airtime/api_client.cfg";
const CONF_FILE_LIQUIDSOAP = "/etc/airtime/liquidsoap.cfg";
const CONF_FILE_MEDIAMONITOR = "/etc/airtime/media-monitor.cfg";
const CONF_FILE_MONIT = "/etc/monit/conf.d/airtime-monit.cfg";
@ -65,6 +66,10 @@ class AirtimeIni
echo "Could not copy airtime.conf to /etc/airtime/. Exiting.";
exit(1);
}
if (!copy(__DIR__."/../../python_apps/api_clients/api_client.cfg", AirtimeIni::CONF_FILE_API_CLIENT)){
echo "Could not copy api_client.cfg to /etc/airtime/. Exiting.";
exit(1);
}
if (!copy(__DIR__."/../../python_apps/pypo/pypo.cfg", AirtimeIni::CONF_FILE_PYPO)){
echo "Could not copy pypo.cfg to /etc/airtime/. Exiting.";
exit(1);

View File

@ -8,7 +8,9 @@
//Pear classes.
set_include_path(__DIR__.'/../../airtime_mvc/library/pear' . PATH_SEPARATOR . get_include_path());
require_once('DB.php');
require_once(__DIR__.'/../../airtime_mvc/application/configs/constants.php');
require_once(dirname(__FILE__).'/AirtimeIni.php');
if(exec("whoami") != "root"){
@ -87,9 +89,13 @@ if (strcmp($version, "1.9.0") < 0){
//set the new version in the database.
$sql = "DELETE FROM cc_pref WHERE keystr = 'system_version'";
$CC_DBC->query($sql);
$sql = "INSERT INTO cc_pref (keystr, valstr) VALUES ('system_version', '1.9.0-devel')";
$newVersion = AIRTIME_VERSION;
$sql = "INSERT INTO cc_pref (keystr, valstr) VALUES ('system_version', '$newVersion')";
$CC_DBC->query($sql);
echo PHP_EOL."*** Updating Api Client ***".PHP_EOL;
passthru("python ".__DIR__."/../../python_apps/api_clients/install/api_client_install.py");
echo PHP_EOL."*** Updating Pypo ***".PHP_EOL;
passthru("python ".__DIR__."/../../python_apps/pypo/install/pypo-install.py");

View File

@ -321,7 +321,7 @@ class AirtimeInstall{
INSERT INTO cc_country (isocode, name) VALUES ('ZWE', 'Zimbabwe ');";
echo "* Inserting data into country table".PHP_EOL;
execSqlQuery($sql);
Airtime190Upgrade::execSqlQuery($sql);
}
}
@ -420,7 +420,7 @@ class AirtimeIni{
}
}
function upgradeConfigFiles(){
public static function upgradeConfigFiles(){
$configFiles = array(AirtimeIni::CONF_FILE_AIRTIME,
AirtimeIni::CONF_FILE_PYPO,
@ -443,7 +443,7 @@ class AirtimeIni{
* This function creates the /etc/airtime configuration folder
* and copies the default config files to it.
*/
function CreateIniFiles()
public static function CreateIniFiles()
{
if (!file_exists("/etc/airtime/")){
if (!mkdir("/etc/airtime/", 0755, true)){
@ -474,47 +474,49 @@ class AirtimeIni{
}
}
function InstallAirtimePhpServerCode($phpDir)
{
class Airtime190Upgrade{
public static function InstallAirtimePhpServerCode($phpDir)
{
$AIRTIME_SRC = realpath(__DIR__.'/../../../airtime_mvc');
echo "* Installing PHP code to ".$phpDir.PHP_EOL;
exec("mkdir -p ".$phpDir);
exec("cp -R ".$AIRTIME_SRC."/* ".$phpDir);
}
}
function CopyUtils()
{
public static function CopyUtils()
{
$utilsSrc = __DIR__."/../../../utils";
echo "* Installing binaries to ".CONF_DIR_BINARIES.PHP_EOL;
exec("mkdir -p ".CONF_DIR_BINARIES);
exec("cp -R ".$utilsSrc." ".CONF_DIR_BINARIES);
}
}
/* Removes pypo, media-monitor, show-recorder and utils from system. These will
/* Removes pypo, media-monitor, show-recorder and utils from system. These will
* be reinstalled by the main airtime-upgrade script.
*/
function UninstallBinaries()
{
public static function UninstallBinaries()
{
echo "* Removing Airtime binaries from ".CONF_DIR_BINARIES.PHP_EOL;
exec('rm -rf "'.CONF_DIR_BINARIES.'"');
}
}
function removeOldAirtimeImport(){
public static function removeOldAirtimeImport(){
exec('rm -f "/usr/bin/airtime-import"');
exec('rm -f "/usr/lib/airtime/utils/airtime-import.php"');
exec('rm -rf "/usr/lib/airtime/utils/airtime-import"');
}
}
function updateAirtimeImportSymLink(){
public static function updateAirtimeImportSymLink(){
$dir = "/usr/lib/airtime/utils/airtime-import/airtime-import";
exec("ln -s $dir /usr/bin/airtime-import");
}
}
function execSqlQuery($sql){
public static function execSqlQuery($sql){
global $CC_DBC;
$result = $CC_DBC->query($sql);
@ -524,9 +526,9 @@ function execSqlQuery($sql){
}
return $result;
}
}
function connectToDatabase(){
public static function connectToDatabase(){
global $CC_DBC, $CC_CONFIG;
$values = parse_ini_file('/etc/airtime/airtime.conf', true);
@ -539,23 +541,23 @@ function connectToDatabase(){
$CC_CONFIG['dsn']['database'] = $values['database']['dbname'];
$CC_DBC = DB::connect($CC_CONFIG['dsn'], FALSE);
}
}
/* Old database had a "fullpath" column that stored the absolute path of each track. We have to
/* Old database had a "fullpath" column that stored the absolute path of each track. We have to
* change it so that the "fullpath" column has path relative to the "directory" column.
*/
function installMediaMonitor($values){
public static function installMediaMonitor($values){
/* Handle Database Changes. */
$stor_dir = realpath($values['general']['base_files_dir']."/stor")."/";
echo "* Inserting stor directory location $stor_dir into music_dirs table".PHP_EOL;
$sql = "UPDATE cc_music_dirs SET directory='$stor_dir' WHERE type='stor'";
echo $sql.PHP_EOL;
execSqlQuery($sql);
Airtime190Upgrade::execSqlQuery($sql);
$sql = "SELECT id FROM cc_music_dirs WHERE type='stor'";
echo $sql.PHP_EOL;
$rows = execSqlQuery($sql);
$rows = Airtime190Upgrade::execSqlQuery($sql);
echo "Creating media-monitor log file";
mkdir("/var/log/airtime/media-monitor/", 755, true);
@ -566,6 +568,8 @@ function installMediaMonitor($values){
echo "Could not copy media-monitor.cfg to /etc/airtime/. Exiting.";
}
AirtimeIni::UpdateIniValue(AirtimeIni::CONF_FILE_MEDIAMONITOR, "api_key", $values["general"]["api_key"]);
echo "Reorganizing files in stor directory";
$mediaMonitorUpgradePath = realpath(__DIR__."/../../../python_apps/media-monitor/media-monitor-upgrade.py");
exec("su -c \"python $mediaMonitorUpgradePath\"", $output);
@ -578,11 +582,14 @@ function installMediaMonitor($values){
$absPathOld = pg_escape_string($pair[0]);
$sql = "UPDATE cc_files SET filepath = '$relPathNew', directory=1 WHERE filepath = '$absPathOld'";
echo $sql.PHP_EOL;
execSqlQuery($sql);
Airtime190Upgrade::execSqlQuery($sql);
}
}
}
AirtimeInstall::CreateZendPhpLogFile();
/* In version 1.9.0 we have have switched from daemontools to more traditional
@ -622,19 +629,19 @@ foreach ($pathnames as $pn){
/* update Airtime Server PHP files */
$values = parse_ini_file(AirtimeIni::CONF_FILE_AIRTIME, true);
$phpDir = $values['general']['airtime_dir'];
InstallAirtimePhpServerCode($phpDir);
Airtime190Upgrade::InstallAirtimePhpServerCode($phpDir);
/* update utils (/usr/lib/airtime) folder */
UninstallBinaries();
CopyUtils();
Airtime190Upgrade::UninstallBinaries();
Airtime190Upgrade::CopyUtils();
/* James made a new airtime-import script, lets remove the old airtime-import php script,
*install the new airtime-import.py script and update the /usr/bin/symlink.
*/
removeOldAirtimeImport();
updateAirtimeImportSymLink();
Airtime190Upgrade::removeOldAirtimeImport();
Airtime190Upgrade::updateAirtimeImportSymLink();
connectToDatabase();
Airtime190Upgrade::connectToDatabase();
if(AirtimeInstall::DbTableExists('doctrine_migration_versions') === false) {
$migrations = array('20110312121200', '20110331111708', '20110402164819', '20110406182005');
@ -652,7 +659,7 @@ AirtimeInstall::InsertCountryDataIntoDatabase();
/* create cron file for phone home stat */
AirtimeInstall::CreateCronFile();
installMediaMonitor($values);
Airtime190Upgrade::installMediaMonitor($values);
AirtimeIni::upgradeConfigFiles();

View File

@ -0,0 +1,24 @@
import sys
from configobj import ConfigObj
class AirtimeMediaConfig:
MODE_CREATE = "create"
MODE_MODIFY = "modify"
MODE_MOVED = "moved"
MODE_DELETE = "delete"
def __init__(self, logger):
# loading config file
try:
config = ConfigObj('/etc/airtime/media-monitor.cfg')
self.cfg = config
except Exception, e:
logger.info('Error loading config: ', e)
sys.exit()
self.storage_directory = None

View File

@ -0,0 +1,243 @@
import os
import grp
import pwd
import logging
from subprocess import Popen, PIPE
from airtimemetadata import AirtimeMetadata
class MediaMonitorCommon:
timestamp_file = "/var/tmp/airtime/last_index"
def __init__(self, airtime_config):
self.supported_file_formats = ['mp3', 'ogg']
self.logger = logging.getLogger()
self.config = airtime_config
self.md_manager = AirtimeMetadata()
def is_parent_directory(self, filepath, directory):
filepath = os.path.normpath(filepath)
directory = os.path.normpath(directory)
return (directory == filepath[0:len(directory)])
def is_temp_file(self, filename):
info = filename.split(".")
if(info[-2] in self.supported_file_formats):
return True
else:
return False
def is_audio_file(self, filename):
info = filename.split(".")
if(info[-1] in self.supported_file_formats):
return True
else:
return False
#check if file is readable by "nobody"
def has_correct_permissions(self, filepath):
#drop root permissions and become "nobody"
os.seteuid(65534)
try:
open(filepath)
readable = True
except IOError:
self.logger.warn("File does not have correct permissions: '%s'", filepath)
readable = False
except Exception, e:
self.logger.error("Unexpected exception thrown: %s", e)
readable = False
finally:
#reset effective user to root
os.seteuid(0)
return readable
def set_needed_file_permissions(self, item, is_dir):
try:
omask = os.umask(0)
uid = pwd.getpwnam('www-data')[2]
gid = grp.getgrnam('www-data')[2]
os.chown(item, uid, gid)
if is_dir is True:
os.chmod(item, 02777)
else:
os.chmod(item, 0666)
except Exception, e:
self.logger.error("Failed to change file's owner/group/permissions. %s", e)
finally:
os.umask(omask)
#checks if path is a directory, and if it doesnt exist, then creates it.
#Otherwise prints error to log file.
def ensure_is_dir(self, directory):
try:
omask = os.umask(0)
if not os.path.exists(directory):
os.makedirs(directory, 02777)
elif not os.path.isdir(directory):
#path exists but it is a file not a directory!
self.logger.error("path %s exists, but it is not a directory!!!")
finally:
os.umask(omask)
#moves file from source to dest but also recursively removes the
#the source file's parent directories if they are now empty.
def move_file(self, source, dest):
try:
omask = os.umask(0)
os.rename(source, dest)
except Exception, e:
self.logger.error("failed to move file. %s", e)
finally:
os.umask(omask)
dir = os.path.dirname(source)
self.cleanup_empty_dirs(dir)
#keep moving up the file hierarchy and deleting parent
#directories until we hit a non-empty directory, or we
#hit the organize dir.
def cleanup_empty_dirs(self, dir):
if os.path.normpath(dir) != self.config.organize_directory:
if len(os.listdir(dir)) == 0:
os.rmdir(dir)
pdir = os.path.dirname(dir)
self.cleanup_empty_dirs(pdir)
#checks if path exists already in stor. If the path exists and the md5s are the
#same just overwrite.
def create_unique_filename(self, filepath, old_filepath):
try:
if(os.path.exists(filepath)):
self.logger.info("Path %s exists", filepath)
self.logger.info("Checking if md5s are the same.")
md5_fp = self.md_manager.get_md5(filepath)
md5_ofp = self.md_manager.get_md5(old_filepath)
if(md5_fp == md5_ofp):
self.logger.info("Md5s are the same, moving to same filepath.")
return filepath
self.logger.info("Md5s aren't the same, appending to filepath.")
file_dir = os.path.dirname(filepath)
filename = os.path.basename(filepath).split(".")[0]
#will be in the format .ext
file_ext = os.path.splitext(filepath)[1]
i = 1;
while(True):
new_filepath = '%s/%s(%s)%s' % (file_dir, filename, i, file_ext)
self.logger.error("Trying %s", new_filepath)
if(os.path.exists(new_filepath)):
i = i+1;
else:
filepath = new_filepath
break
except Exception, e:
self.logger.error("Exception %s", e)
return filepath
#create path in /srv/airtime/stor/imported/[song-metadata]
def create_file_path(self, original_path, orig_md):
storage_directory = self.config.storage_directory
is_recorded_show = False
try:
#will be in the format .ext
file_ext = os.path.splitext(original_path)[1]
file_ext = file_ext.encode('utf-8')
path_md = ['MDATA_KEY_TITLE', 'MDATA_KEY_CREATOR', 'MDATA_KEY_SOURCE', 'MDATA_KEY_TRACKNUMBER', 'MDATA_KEY_BITRATE']
md = {}
for m in path_md:
if m not in orig_md:
md[m] = u'unknown'.encode('utf-8')
else:
#get rid of any "/" which will interfere with the filepath.
if isinstance(orig_md[m], basestring):
md[m] = orig_md[m].replace("/", "-")
else:
md[m] = orig_md[m]
if 'MDATA_KEY_TRACKNUMBER' in orig_md:
#make sure all track numbers are at least 2 digits long in the filepath.
md['MDATA_KEY_TRACKNUMBER'] = "%02d" % (int(md['MDATA_KEY_TRACKNUMBER']))
#format bitrate as 128kbps
md['MDATA_KEY_BITRATE'] = str(md['MDATA_KEY_BITRATE']/1000)+"kbps"
filepath = None
#file is recorded by Airtime
#/srv/airtime/stor/recorded/year/month/year-month-day-time-showname-bitrate.ext
if(md['MDATA_KEY_CREATOR'] == "AIRTIMERECORDERSOURCEFABRIC".encode('utf-8')):
#yyyy-mm-dd-hh-MM-ss
y = orig_md['MDATA_KEY_YEAR'].split("-")
filepath = '%s/%s/%s/%s/%s-%s-%s%s' % (storage_directory, "recorded".encode('utf-8'), y[0], y[1], orig_md['MDATA_KEY_YEAR'], md['MDATA_KEY_TITLE'], md['MDATA_KEY_BITRATE'], file_ext)
elif(md['MDATA_KEY_TRACKNUMBER'] == u'unknown'.encode('utf-8')):
filepath = '%s/%s/%s/%s/%s-%s%s' % (storage_directory, "imported".encode('utf-8'), md['MDATA_KEY_CREATOR'], md['MDATA_KEY_SOURCE'], md['MDATA_KEY_TITLE'], md['MDATA_KEY_BITRATE'], file_ext)
else:
filepath = '%s/%s/%s/%s/%s-%s-%s%s' % (storage_directory, "imported".encode('utf-8'), md['MDATA_KEY_CREATOR'], md['MDATA_KEY_SOURCE'], md['MDATA_KEY_TRACKNUMBER'], md['MDATA_KEY_TITLE'], md['MDATA_KEY_BITRATE'], file_ext)
filepath = self.create_unique_filename(filepath, original_path)
self.logger.info('Unique filepath: %s', filepath)
self.ensure_is_dir(os.path.dirname(filepath))
except Exception, e:
self.logger.error('Exception: %s', e)
return filepath
def execCommandAndReturnStdOut(self, command):
p = Popen(command, shell=True, stdout=PIPE)
stdout = p.communicate()[0]
if p.returncode != 0:
self.logger.warn("command \n%s\n return with a non-zero return value", command)
return stdout
def scan_dir_for_new_files(self, dir):
command = 'find "%s" -type f -iname "*.ogg" -o -iname "*.mp3" -readable' % dir.replace('"', '\\"')
self.logger.debug(command)
stdout = self.execCommandAndReturnStdOut(command)
stdout = unicode(stdout, "utf_8")
return stdout.splitlines()
def touch_index_file(self):
open(self.timestamp_file, "w")
def organize_new_file(self, pathname):
self.logger.info("Organizing new file: %s", pathname)
file_md = self.md_manager.get_md_from_file(pathname)
if file_md is not None:
#is_recorded_show = 'MDATA_KEY_CREATOR' in file_md and \
# file_md['MDATA_KEY_CREATOR'] == "AIRTIMERECORDERSOURCEFABRIC".encode('utf-8')
filepath = self.create_file_path(pathname, file_md)
self.logger.debug("Moving from %s to %s", pathname, filepath)
self.move_file(pathname, filepath)
else:
filepath = None
self.logger.warn("File %s, has invalid metadata", pathname)
return filepath

View File

@ -0,0 +1,83 @@
<?php
/* THIS FILE IS NOT MEANT FOR CUSTOMIZING.
* PLEASE EDIT THE FOLLOWING TO CHANGE YOUR CONFIG:
* /etc/airtime/airtime.conf
* /etc/airtime/pypo.cfg
* /etc/airtime/recorder.cfg
*/
global $CC_CONFIG;
$CC_CONFIG = array(
// prefix for table names in the database
'tblNamePrefix' => 'cc_',
/* ================================================ storage configuration */
'soundcloud-client-id' => '2CLCxcSXYzx7QhhPVHN4A',
'soundcloud-client-secret' => 'pZ7beWmF06epXLHVUP1ufOg2oEnIt9XhE8l8xt0bBs',
"rootDir" => __DIR__."/../..",
'pearPath' => dirname(__FILE__).'/../../../airtime_mvc/library/pear',
'zendPath' => dirname(__FILE__).'/../../../airtime_mvc/library/Zend',
'phingPath' => dirname(__FILE__).'/../../../airtime_mvc/library/phing'
);
$CC_CONFIG = Config::loadConfig($CC_CONFIG);
// Add database table names
$CC_CONFIG['playListTable'] = $CC_CONFIG['tblNamePrefix'].'playlist';
$CC_CONFIG['playListContentsTable'] = $CC_CONFIG['tblNamePrefix'].'playlistcontents';
$CC_CONFIG['filesTable'] = $CC_CONFIG['tblNamePrefix'].'files';
$CC_CONFIG['accessTable'] = $CC_CONFIG['tblNamePrefix'].'access';
$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['prefTable'] = $CC_CONFIG['tblNamePrefix'].'pref';
$CC_CONFIG['scheduleTable'] = $CC_CONFIG['tblNamePrefix'].'schedule';
$CC_CONFIG['playListTimeView'] = $CC_CONFIG['tblNamePrefix'].'playlisttimes';
$CC_CONFIG['showSchedule'] = $CC_CONFIG['tblNamePrefix'].'show_schedule';
$CC_CONFIG['showDays'] = $CC_CONFIG['tblNamePrefix'].'show_days';
$CC_CONFIG['showTable'] = $CC_CONFIG['tblNamePrefix'].'show';
$CC_CONFIG['showInstances'] = $CC_CONFIG['tblNamePrefix'].'show_instances';
$CC_CONFIG['playListSequence'] = $CC_CONFIG['playListTable'].'_id';
$CC_CONFIG['filesSequence'] = $CC_CONFIG['filesTable'].'_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';
// Add libs to the PHP path
$old_include_path = get_include_path();
set_include_path('.'.PATH_SEPARATOR.$CC_CONFIG['pearPath']
.PATH_SEPARATOR.$CC_CONFIG['zendPath']
.PATH_SEPARATOR.$old_include_path);
class Config {
public static function loadConfig($CC_CONFIG) {
$values = parse_ini_file('/etc/airtime/airtime.conf', true);
// Name of the web server user
$CC_CONFIG['webServerUser'] = $values['general']['web_server_user'];
$CC_CONFIG['rabbitmq'] = $values['rabbitmq'];
$CC_CONFIG['baseUrl'] = $values['general']['base_url'];
$CC_CONFIG['basePort'] = $values['general']['base_port'];
// Database config
$CC_CONFIG['dsn']['username'] = $values['database']['dbuser'];
$CC_CONFIG['dsn']['password'] = $values['database']['dbpass'];
$CC_CONFIG['dsn']['hostspec'] = $values['database']['host'];
$CC_CONFIG['dsn']['phptype'] = 'pgsql';
$CC_CONFIG['dsn']['database'] = $values['database']['dbname'];
$CC_CONFIG['apiKey'] = array($values['general']['api_key']);
$CC_CONFIG['soundcloud-connection-retries'] = $values['soundcloud']['connection_retries'];
$CC_CONFIG['soundcloud-connection-wait'] = $values['soundcloud']['time_between_retries'];
return $CC_CONFIG;
}
}

View File

@ -0,0 +1,22 @@
[loggers]
keys=root
[handlers]
keys=fileOutHandler
[formatters]
keys=simpleFormatter
[logger_root]
level=DEBUG
handlers=fileOutHandler
[handler_fileOutHandler]
class=logging.handlers.RotatingFileHandler
level=DEBUG
formatter=simpleFormatter
args=("/var/log/airtime/media-monitor/media-monitor.log", 'a', 1000000, 5,)
[formatter_simpleFormatter]
format=%(asctime)s %(levelname)s - [%(filename)s : %(funcName)s() : line %(lineno)d] - %(message)s
datefmt=

View File

@ -0,0 +1 @@
bin_dir = "/usr/lib/airtime/api_clients"

View File

@ -0,0 +1,25 @@
import os
import shutil
import sys
from configobj import ConfigObj
def get_current_script_dir():
return os.path.dirname(os.path.realpath(__file__))
def copy_dir(src_dir, dest_dir):
if (os.path.exists(dest_dir)) and (dest_dir != "/"):
shutil.rmtree(dest_dir)
if not (os.path.exists(dest_dir)):
print "Copying directory "+os.path.realpath(src_dir)+" to "+os.path.realpath(dest_dir)
shutil.copytree(src_dir, dest_dir)
current_script_dir = get_current_script_dir()
"""load config file"""
try:
config = ConfigObj("%s/../api_client.cfg" % current_script_dir)
except Exception, e:
print 'Error loading config file: ', e
sys.exit(1)
copy_dir("%s/../../api_clients"%current_script_dir, config["bin_dir"])

View File

@ -0,0 +1,21 @@
import os
import sys
from configobj import ConfigObj
def remove_path(path):
os.system('rm -rf "%s"' % path)
def get_current_script_dir():
return os.path.dirname(os.path.realpath(__file__))
current_script_dir = get_current_script_dir()
"""load config file"""
try:
config = ConfigObj("%s/../api_client.cfg" % current_script_dir)
except Exception, e:
print 'Error loading config file: ', e
sys.exit(1)
print "Removing API Client files"
remove_path(config["bin_dir"])

View File

@ -30,6 +30,8 @@ except Exception, e:
logger = logging.getLogger()
logger.info("\n\n*** Media Monitor bootup ***\n\n")
try:
config = AirtimeMediaConfig(logger)
api_client = api_client.api_client_factory(config.cfg)

View File

@ -8,7 +8,7 @@ virtualenv_bin="/usr/lib/airtime/airtime_virtualenv/bin/"
media_monitor_path="/usr/lib/airtime/media-monitor/"
media_monitor_script="MediaMonitor.py"
api_client_path="/usr/lib/airtime/pypo/"
api_client_path="/usr/lib/airtime/"
cd ${media_monitor_path}

View File

@ -91,7 +91,7 @@ class AirtimeMediaMonitorBootstrap():
for file_path in new_files:
if len(file_path.strip(" \n")) > 0:
new_and_modified_files.add(file_path[len(dir)+1:])
new_and_modified_files.add(file_path[len(dir):])
"""
new_and_modified_files gives us a set of files that were either copied or modified
@ -114,15 +114,19 @@ class AirtimeMediaMonitorBootstrap():
self.mmc.touch_index_file()
for file_path in deleted_files_set:
self.pe.handle_removed_file(False, "%s/%s" % (dir, file_path))
self.pe.handle_removed_file(False, "%s%s" % (dir, file_path))
deleted_files_set.clear()
for file_path in new_files_set:
file_path = "%s/%s" % (dir, file_path)
file_path = "%s%s" % (dir, file_path)
if os.path.exists(file_path):
self.pe.handle_created_file(False, os.path.basename(file_path), file_path)
new_files_set.clear()
for file_path in modified_files_set:
file_path = "%s/%s" % (dir, file_path)
file_path = "%s%s" % (dir, file_path)
if os.path.exists(file_path):
self.pe.handle_modified_file(False, os.path.basename(file_path), file_path)
modified_files_set.clear()

View File

@ -112,7 +112,7 @@ class AirtimeNotifier(Notifier):
self.logger.info("Adding file to ignore: %s ", filepath)
mm.add_filepath_to_ignore(filepath)
if m['delete'] == "true":
if m['delete']:
self.logger.info("Deleting file: %s ", filepath)
os.unlink(filepath)

View File

@ -57,6 +57,9 @@ class AirtimeProcessEvent(ProcessEvent):
#file created is a tmp file which will be modified and then moved back to the original filename.
#Easy Tag creates this when changing metadata of ogg files.
self.temp_files[pathname] = None
#file is being overwritten/replaced in GUI.
elif "goutputstream" in pathname:
self.temp_files[pathname] = None
elif self.mmc.is_audio_file(pathname):
if self.mmc.is_parent_directory(pathname, self.config.organize_directory):
#file was created in /srv/airtime/stor/organize. Need to process and move

View File

@ -5,7 +5,7 @@ virtualenv_bin="/usr/lib/airtime/airtime_virtualenv/bin/"
ls_user="pypo"
export HOME="/var/tmp/airtime/pypo/"
api_client_path="/usr/lib/airtime/pypo/"
api_client_path="/usr/lib/airtime/"
ls_path="/usr/lib/airtime/pypo/bin/liquidsoap_bin/liquidsoap"
ls_param="/usr/lib/airtime/pypo/bin/liquidsoap_scripts/ls_script.liq"

View File

@ -7,7 +7,7 @@ pypo_user="pypo"
# Location of pypo_cli.py Python script
pypo_path="/usr/lib/airtime/pypo/bin/"
api_client_path="/usr/lib/airtime/pypo/"
api_client_path="/usr/lib/airtime/"
pypo_script="pypo-cli.py"
cd ${pypo_path}
exec 2>&1

View File

@ -96,7 +96,6 @@ try:
sys.exit(1)
copy_dir("%s/.."%current_script_dir, config["bin_dir"]+"/bin/")
copy_dir("%s/../../api_clients"%current_script_dir, config["bin_dir"]+"/api_clients/")
print "Setting permissions"
os.system("chmod -R 755 "+config["bin_dir"])

View File

@ -10,7 +10,7 @@ recorder_user="pypo"
recorder_path="/usr/lib/airtime/show-recorder/"
recorder_script="recorder.py"
api_client_path="/usr/lib/airtime/pypo/"
api_client_path="/usr/lib/airtime/"
cd ${recorder_path}
exec 2>&1

View File

@ -47,10 +47,10 @@ def copy_or_move_files_to(paths, dest, flag):
if( 'mp3' in ext or 'ogg' in ext ):
destfile = dest+os.path.basename(path)
if(flag == 'copy'):
print "Copying %(src)s to %(dest)s....." % {'src':path, 'dest':destfile}
print "Copying %(src)s to %(dest)s..." % {'src':path, 'dest':destfile}
shutil.copy2(path, destfile)
elif(flag == 'move'):
print "Moving %(src)s to %(dest)s....." % {'src':path, 'dest':destfile}
print "Moving %(src)s to %(dest)s..." % {'src':path, 'dest':destfile}
shutil.move(path, destfile)
else:
print "Cannot find file or path: %s" % path
@ -95,7 +95,7 @@ def printHelp():
========================
There are two ways to import audio files into Airtime:
1) Copy or move files into the storage folder
1) Use airtime-import to copy or move files into the storage folder.
Copied or moved files will be placed into the folder:
%s
@ -103,12 +103,12 @@ There are two ways to import audio files into Airtime:
Files will be automatically organized into the structure
"Artist/Album/TrackNumber-TrackName-Bitrate.file_extension".
2) Add a folder to the Airtime library("watch" a folder)
2) Use airtime-import to add a folder to the Airtime library ("watch" a folder).
All the files in the watched folder will be imported to Airtime and the
folder will be monitored to automatically detect any changes. Hence any
changes done in the folder(add, delete, edit a file) will trigger
updates in Airtime libarary.
updates in Airtime library.
""" % storage_dir
parser.print_help()
print ""
@ -117,7 +117,7 @@ def CopyAction(option, opt, value, parser):
errorIfMultipleOption(parser.rargs)
stor = helper_get_stor_dir()
if(stor is None):
exit("Unable to connect to the server.")
exit("Unable to connect to the Airtime server.")
dest = stor+"organize/"
copy_or_move_files_to(parser.rargs, dest, 'copy')
@ -125,16 +125,16 @@ def MoveAction(option, opt, value, parser):
errorIfMultipleOption(parser.rargs)
stor = helper_get_stor_dir()
if(stor is None):
exit("Unable to connect to the server.")
exit("Unable to connect to the Airtime server.")
dest = stor+"organize/"
copy_or_move_files_to(parser.rargs, dest, 'move')
def WatchAddAction(option, opt, value, parser):
errorIfMultipleOption(parser.rargs)
if(len(parser.rargs) > 1):
raise OptionValueError("Too many arguments. This option need exactly one argument.")
raise OptionValueError("Too many arguments. This option requires exactly one argument.")
elif(len(parser.rargs) == 0 ):
raise OptionValueError("No argument found. This option need exactly one argument.")
raise OptionValueError("No argument found. This option requires exactly one argument.")
path = parser.rargs[0]
if(os.path.isdir(path)):
res = api_client.add_watched_dir(path)
@ -144,17 +144,17 @@ def WatchAddAction(option, opt, value, parser):
if(res['msg']['code'] == 0):
print "%s added to watched folder list successfully" % path
else:
print "Adding a watched folder failed. : %s" % res['msg']['error']
print "Adding a watched folder failed: %s" % res['msg']['error']
else:
print "Given path is not a directory: %s" % path
def WatchListAction(option, opt, value, parser):
errorIfMultipleOption(parser.rargs)
if(len(parser.rargs) > 0):
raise OptionValueError("This option doesn't take any argument.")
raise OptionValueError("This option doesn't take any arguments.")
res = api_client.list_all_watched_dirs()
if(res is None):
exit("Unable to connect to the server.")
exit("Unable to connect to the Airtime server.")
dirs = res["dirs"].items()
# there will be always 1 which is storage folder
if(len(dirs) == 1):
@ -167,21 +167,21 @@ def WatchListAction(option, opt, value, parser):
def WatchRemoveAction(option, opt, value, parser):
errorIfMultipleOption(parser.rargs)
if(len(parser.rargs) > 1):
raise OptionValueError("Too many arguments. This option need exactly one argument.")
raise OptionValueError("Too many arguments. This option requires exactly one argument.")
elif(len(parser.rargs) == 0 ):
raise OptionValueError("No argument found. This option need exactly one argument.")
raise OptionValueError("No argument found. This option requires exactly one argument.")
path = parser.rargs[0]
if(os.path.isdir(path)):
res = api_client.remove_watched_dir(path)
if(res is None):
exit("Unable to connect to the server.")
exit("Unable to connect to the Airtime server.")
# sucess
if(res['msg']['code'] == 0):
print "%s removed from watched folder list successfully" % path
print "%s removed from watch folder list successfully." % path
else:
print "Removing a watched folder failed. : %s" % res['msg']['error']
print "Removing the watch folder failed: %s" % res['msg']['error']
else:
print "Given path is not a directory: %s" % path
print "The given path is not a directory: %s" % path
def StorageSetAction(option, opt, value, parser):
bypass = False
@ -199,38 +199,38 @@ def StorageSetAction(option, opt, value, parser):
confirm = raw_input("Are you sure you want to change the storage direcory? (y/N)")
confirm = confirm or 'N'
while(confirm not in possibleInput):
print "Not an acceptable input: %s" % confirm
confirm = raw_input("Are you sure you want to change the storage direcory? (y/N)")
print "Not an acceptable input: %s\n" % confirm
confirm = raw_input("Are you sure you want to change the storage direcory? (y/N) ")
confirm = confirm or 'N'
if(confirm == 'n' or confirm =='N'):
sys.exit(1)
if(len(parser.rargs) > 1):
raise OptionValueError("Too many arguments. This option need exactly one argument.")
raise OptionValueError("Too many arguments. This option requires exactly one argument.")
elif(len(parser.rargs) == 0 ):
raise OptionValueError("No argument found. This option need exactly one argument.")
raise OptionValueError("No argument found. This option requires exactly one argument.")
path = parser.rargs[0]
if(os.path.isdir(path)):
res = api_client.set_storage_dir(path)
if(res is None):
exit("Unable to connect to the server.")
exit("Unable to connect to the Airtime server.")
# sucess
if(res['msg']['code'] == 0):
print "Successfully set storage folder to %s" % path
else:
print "Setting storage folder to failed.: %s" % res['msg']['error']
print "Setting storage folder failed: %s" % res['msg']['error']
else:
print "Given path is not a directory: %s" % path
print "The given path is not a directory: %s" % path
def StorageGetAction(option, opt, value, parser):
errorIfMultipleOption(parser.rargs)
if(len(parser.rargs) > 0):
raise OptionValueError("This option doesn't take any argument.")
raise OptionValueError("This option does not take any arguments.")
print helper_get_stor_dir()
usage = """[-c|--copy FILE/DIR [FILE/DIR...]] [-m|--move FILE/DIR [FILE/DIR...]]
[--watch-add DIR] [--watch-list] [--watch-remve DIR]
[--watch-add DIR] [--watch-list] [--watch-remove DIR]
[--storage-dir-set DIR] [--storage-dir-get]"""
parser = OptionParser(usage=usage, add_help_option=False)