Merge branch '2.3.x' of dev.sourcefabric.org:airtime into 2.3.x

This commit is contained in:
denise 2013-02-04 11:06:53 -05:00
commit d63865625d
13 changed files with 184 additions and 44 deletions

View file

@ -273,7 +273,8 @@ class PreferenceController extends Zend_Controller_Action
Application_Model_Preference::SetEnableReplayGain($values["enableReplayGain"]); Application_Model_Preference::SetEnableReplayGain($values["enableReplayGain"]);
Application_Model_Preference::setReplayGainModifier($values["replayGainModifier"]); Application_Model_Preference::setReplayGainModifier($values["replayGainModifier"]);
$md = array('schedule' => Application_Model_Schedule::getSchedule()); $md = array('schedule' => Application_Model_Schedule::getSchedule());
Application_Model_RabbitMq::PushSchedule(); Application_Model_RabbitMq::SendMessageToPypo("update_schedule", $md);
//Application_Model_RabbitMq::PushSchedule();
} }
if (!Application_Model_Preference::GetMasterDjConnectionUrlOverride()) { if (!Application_Model_Preference::GetMasterDjConnectionUrlOverride()) {

View file

@ -32,6 +32,8 @@ class Logging {
{ {
if (is_array($p_msg) || is_object($p_msg)) { if (is_array($p_msg) || is_object($p_msg)) {
return print_r($p_msg, true); return print_r($p_msg, true);
} else if (is_bool($p_msg)) {
return $p_msg ? "true" : "false";
} else { } else {
return $p_msg; return $p_msg;
} }

View file

@ -1,6 +1,7 @@
2.3.0 - Jan 21st, 2013 2.3.0 - Jan 21st, 2013
* New features * New features
* Localization (Brazilian, Chinese, Czech, English, French, German, Italian, Korean, Portugese, Russian, Spanish) * Localization (Chinese, Czech, English, French, German, Italian, Korean,
Portuguese, Russian, Spanish)
* User management page for non-admin users * User management page for non-admin users
* Listener statistics (Icecast/Shoutcast) * Listener statistics (Icecast/Shoutcast)
* Airtime no longer requires Apache document root * Airtime no longer requires Apache document root

View file

@ -15,6 +15,8 @@ INSERT INTO cc_stream_setting ("keyname", "value", "type") VALUES ('s3_admin_pas
UPDATE cc_music_dirs SET directory = directory || '/' where id in (select id from cc_music_dirs where substr(directory, length(directory)) != '/'); UPDATE cc_music_dirs SET directory = directory || '/' where id in (select id from cc_music_dirs where substr(directory, length(directory)) != '/');
UPDATE cc_files SET filepath = substring(filepath from 2) where id in (select id from cc_files where substring(filepath from 1 for 1) = '/'); UPDATE cc_files SET filepath = substring(filepath from 2) where id in (select id from cc_files where substring(filepath from 1 for 1) = '/');
UPDATE cc_files SET cueout = length where cueout = '00:00:00';
INSERT INTO cc_pref("keystr", "valstr") VALUES('locale', 'en_CA'); INSERT INTO cc_pref("keystr", "valstr") VALUES('locale', 'en_CA');
INSERT INTO cc_pref("subjid", "keystr", "valstr") VALUES(1, 'user_locale', 'en_CA'); INSERT INTO cc_pref("subjid", "keystr", "valstr") VALUES(1, 'user_locale', 'en_CA');

View file

@ -80,23 +80,19 @@ class ApiRequest(object):
if logger is None: self.logger = logging if logger is None: self.logger = logging
else: self.logger = logger else: self.logger = logger
def __call__(self,_post_data=None, **kwargs): def __call__(self,_post_data=None, **kwargs):
# TODO : get rid of god damn urllib and replace everything with
# grequests or requests at least
final_url = self.url.params(**kwargs).url() final_url = self.url.params(**kwargs).url()
if _post_data is not None: _post_data = urllib.urlencode(_post_data) if _post_data is not None: _post_data = urllib.urlencode(_post_data)
try: try:
req = urllib2.Request(final_url, _post_data) req = urllib2.Request(final_url, _post_data)
response = urllib2.urlopen(req).read() response = urllib2.urlopen(req).read()
except Exception, e: except Exception, e:
self.logger.error('Exception: %s', e)
import traceback import traceback
top = traceback.format_exc() self.logger.error('Exception: %s', e)
self.logger.error("traceback: %s", top) self.logger.error("traceback: %s", traceback.format_exc())
response = "" raise
# Ghetto hack for now because we don't the content type we are getting # Ghetto hack for now because we don't the content type we are getting
# (Pointless to look at mime since it's not being set correctly always) # (Pointless to look at mime since it's not being set correctly always)
try: return json.loads(response) return json.loads(response)
except ValueError: return response
def req(self, *args, **kwargs): def req(self, *args, **kwargs):
self.__req = lambda : self(*args, **kwargs) self.__req = lambda : self(*args, **kwargs)
@ -182,13 +178,20 @@ class AirtimeApiClient(object):
except: return (False, None) except: return (False, None)
def notify_liquidsoap_started(self): def notify_liquidsoap_started(self):
return self.services.notify_liquidsoap_started() try:
self.services.notify_liquidsoap_started()
except Exception, e:
self.logger.error(str(e))
def notify_media_item_start_playing(self, media_id): def notify_media_item_start_playing(self, media_id):
""" This is a callback from liquidsoap, we use this to notify """ This is a callback from liquidsoap, we use this to notify
about the currently playing *song*. We get passed a JSON string about the currently playing *song*. We get passed a JSON string
which we handed to liquidsoap in get_liquidsoap_data(). """ which we handed to liquidsoap in get_liquidsoap_data(). """
return self.services.update_start_playing_url(media_id=media_id) try:
return self.services.update_start_playing_url(media_id=media_id)
except Exception, e:
self.logger.error(str(e))
return None
# TODO : get this routine out of here it doesn't belong at all here # TODO : get this routine out of here it doesn't belong at all here
def get_liquidsoap_data(self, pkey, schedule): def get_liquidsoap_data(self, pkey, schedule):
@ -199,7 +202,11 @@ class AirtimeApiClient(object):
return data return data
def get_shows_to_record(self): def get_shows_to_record(self):
return self.services.show_schedule_url() try:
return self.services.show_schedule_url()
except Exception, e:
self.logger.error(str(e))
return None
def upload_recorded_show(self, data, headers): def upload_recorded_show(self, data, headers):
logger = self.logger logger = self.logger
@ -235,8 +242,12 @@ class AirtimeApiClient(object):
return response return response
def check_live_stream_auth(self, username, password, dj_type): def check_live_stream_auth(self, username, password, dj_type):
return self.services.check_live_stream_auth( try:
username=username, password=password, djtype=dj_type) return self.services.check_live_stream_auth(
username=username, password=password, djtype=dj_type)
except Exception, e:
self.logger.error(str(e))
return {}
def construct_url(self,config_action_key): def construct_url(self,config_action_key):
"""Constructs the base url for every request""" """Constructs the base url for every request"""
@ -249,7 +260,11 @@ class AirtimeApiClient(object):
return url return url
def setup_media_monitor(self): def setup_media_monitor(self):
return self.services.media_setup_url() try:
return self.services.media_setup_url()
except Exception, e:
#TODO
self.logger.info(str(e))
def send_media_monitor_requests(self, action_list, dry=False): def send_media_monitor_requests(self, action_list, dry=False):
""" """
@ -310,13 +325,25 @@ class AirtimeApiClient(object):
return [] return []
def list_all_watched_dirs(self): def list_all_watched_dirs(self):
return self.services.list_all_watched_dirs() try:
return self.services.list_all_watched_dirs()
except Exception, e:
#TODO
self.logger.error(str(e))
def add_watched_dir(self, path): def add_watched_dir(self, path):
return self.services.add_watched_dir(path=base64.b64encode(path)) try:
return self.services.add_watched_dir(path=base64.b64encode(path))
except Exception, e:
#TODO
self.logger.error(str(e))
def remove_watched_dir(self, path): def remove_watched_dir(self, path):
return self.services.remove_watched_dir(path=base64.b64encode(path)) try:
return self.services.remove_watched_dir(path=base64.b64encode(path))
except Exception, e:
#TODO
self.logger.error(str(e))
def set_storage_dir(self, path): def set_storage_dir(self, path):
return self.services.set_storage_dir(path=base64.b64encode(path)) return self.services.set_storage_dir(path=base64.b64encode(path))
@ -334,7 +361,11 @@ class AirtimeApiClient(object):
(component = media-monitor, pypo etc.) ip address, and later use it (component = media-monitor, pypo etc.) ip address, and later use it
to query monit via monit's http service, or download log files via a to query monit via monit's http service, or download log files via a
http server. """ http server. """
return self.services.register_component(component=component) try:
return self.services.register_component(component=component)
except Exception, e:
#TODO
self.logger.error(str(e))
def notify_liquidsoap_status(self, msg, stream_id, time): def notify_liquidsoap_status(self, msg, stream_id, time):
logger = self.logger logger = self.logger
@ -343,6 +374,7 @@ class AirtimeApiClient(object):
self.services.update_liquidsoap_status.req(msg=encoded_msg, stream_id=stream_id, self.services.update_liquidsoap_status.req(msg=encoded_msg, stream_id=stream_id,
boot_time=time).retry(5) boot_time=time).retry(5)
except Exception, e: except Exception, e:
#TODO
logger.error("Exception: %s", e) logger.error("Exception: %s", e)
def notify_source_status(self, sourcename, status): def notify_source_status(self, sourcename, status):
@ -351,11 +383,16 @@ class AirtimeApiClient(object):
return self.services.update_source_status.req(sourcename=sourcename, return self.services.update_source_status.req(sourcename=sourcename,
status=status).retry(5) status=status).retry(5)
except Exception, e: except Exception, e:
#TODO
logger.error("Exception: %s", e) logger.error("Exception: %s", e)
def get_bootstrap_info(self): def get_bootstrap_info(self):
""" Retrive infomations needed on bootstrap time """ """ Retrieve infomations needed on bootstrap time """
return self.services.get_bootstrap_info() try:
return self.services.get_bootstrap_info()
except Exception, e:
#TODO
self.logger.error(str(e))
def get_files_without_replay_gain_value(self, dir_id): def get_files_without_replay_gain_value(self, dir_id):
""" """
@ -364,7 +401,11 @@ class AirtimeApiClient(object):
to this file is the return value. to this file is the return value.
""" """
#http://localhost/api/get-files-without-replay-gain/dir_id/1 #http://localhost/api/get-files-without-replay-gain/dir_id/1
return self.services.get_files_without_replay_gain(dir_id=dir_id) try:
return self.services.get_files_without_replay_gain(dir_id=dir_id)
except Exception, e:
#TODO
self.logger.error(str(e))
def get_files_without_silan_value(self): def get_files_without_silan_value(self):
""" """
@ -372,22 +413,35 @@ class AirtimeApiClient(object):
calculated. This list of files is downloaded into a file and the path calculated. This list of files is downloaded into a file and the path
to this file is the return value. to this file is the return value.
""" """
return self.services.get_files_without_silan_value() try:
return self.services.get_files_without_silan_value()
except Exception, e:
#TODO
self.logger.error(str(e))
def update_replay_gain_values(self, pairs): def update_replay_gain_values(self, pairs):
""" """
'pairs' is a list of pairs in (x, y), where x is the file's database 'pairs' is a list of pairs in (x, y), where x is the file's database
row id and y is the file's replay_gain value in dB row id and y is the file's replay_gain value in dB
""" """
self.logger.debug(self.services.update_replay_gain_value( try:
_post_data={'data': json.dumps(pairs)})) self.logger.debug(self.services.update_replay_gain_value(
_post_data={'data': json.dumps(pairs)}))
except Exception, e:
#TODO
self.logger.error(str(e))
def update_cue_values_by_silan(self, pairs): def update_cue_values_by_silan(self, pairs):
""" """
'pairs' is a list of pairs in (x, y), where x is the file's database 'pairs' is a list of pairs in (x, y), where x is the file's database
row id and y is the file's cue values in dB row id and y is the file's cue values in dB
""" """
print self.services.update_cue_values_by_silan(_post_data={'data': json.dumps(pairs)}) try:
print self.services.update_cue_values_by_silan(_post_data={'data': json.dumps(pairs)})
except Exception, e:
#TODO
self.logger.error(str(e))
def notify_webstream_data(self, data, media_id): def notify_webstream_data(self, data, media_id):
@ -395,19 +449,35 @@ class AirtimeApiClient(object):
Update the server with the latest metadata we've received from the Update the server with the latest metadata we've received from the
external webstream external webstream
""" """
self.logger.info( self.services.notify_webstream_data.req( try:
_post_data={'data':data}, media_id=str(media_id)).retry(5)) self.logger.info( self.services.notify_webstream_data.req(
_post_data={'data':data}, media_id=str(media_id)).retry(5))
except Exception, e:
#TODO
self.logger.error(str(e))
def get_stream_parameters(self): def get_stream_parameters(self):
response = self.services.get_stream_parameters() try:
self.logger.debug(response) response = self.services.get_stream_parameters()
return response self.logger.debug(response)
return response
except Exception, e:
#TODO
self.logger.error(str(e))
def push_stream_stats(self, data): def push_stream_stats(self, data):
# TODO : users of this method should do their own error handling # TODO : users of this method should do their own error handling
response = self.services.push_stream_stats(_post_data={'data': json.dumps(data)}) try:
return response response = self.services.push_stream_stats(_post_data={'data': json.dumps(data)})
return response
except Exception, e:
#TODO
self.logger.error(str(e))
def update_stream_setting_table(self, data): def update_stream_setting_table(self, data):
response = self.services.update_stream_setting_table(_post_data={'data': json.dumps(data)}) try:
return response response = self.services.update_stream_setting_table(_post_data={'data': json.dumps(data)})
return response
except Exception, e:
#TODO
self.logger.error(str(e))

View file

@ -15,4 +15,7 @@ elif dj_type == '--dj':
response = api_clients.check_live_stream_auth(username, password, source_type) response = api_clients.check_live_stream_auth(username, password, source_type)
print response['msg'] if 'msg' in response:
print response['msg']
else:
print False

View file

@ -5,5 +5,5 @@
check process airtime-playout check process airtime-playout
with pidfile "/var/run/airtime-playout.pid" with pidfile "/var/run/airtime-playout.pid"
start program = "/etc/init.d/airtime-playout monit-restart" with timeout 5 seconds start program = "/etc/init.d/airtime-playout start" with timeout 5 seconds
stop program = "/etc/init.d/airtime-playout stop" stop program = "/etc/init.d/airtime-playout stop"

View file

@ -176,7 +176,7 @@ if __name__ == '__main__':
sys.exit() sys.exit()
api_client = api_client.AirtimeApiClient() api_client = api_client.AirtimeApiClient()
ReplayGainUpdater.start_reply_gain(api_client) ReplayGainUpdater.start_reply_gain(api_client)
api_client.register_component("pypo") api_client.register_component("pypo")

View file

@ -18,7 +18,8 @@ from std_err_override import LogWriter
from configobj import ConfigObj from configobj import ConfigObj
# configure logging # configure logging
logging.config.fileConfig("logging.cfg") logging_cfg = os.path.join(os.path.dirname(__file__), "logging.cfg")
logging.config.fileConfig(logging_cfg)
logger = logging.getLogger() logger = logging.getLogger()
LogWriter.override_std_err(logger) LogWriter.override_std_err(logger)
@ -135,7 +136,7 @@ class PypoFetch(Thread):
try: try:
lock.acquire() lock.acquire()
tn = telnetlib.Telnet(LS_HOST, LS_PORT) tn = telnetlib.Telnet(LS_HOST, LS_PORT)
self.logger.info(command) logger.info(command)
tn.write(command) tn.write(command)
tn.write('exit\n') tn.write('exit\n')
tn.read_all() tn.read_all()

View file

@ -9,6 +9,7 @@ import logging.config
import telnetlib import telnetlib
import calendar import calendar
import math import math
import os
from pypofetch import PypoFetch from pypofetch import PypoFetch
from Queue import Empty from Queue import Empty
@ -21,7 +22,8 @@ from configobj import ConfigObj
# configure logging # configure logging
logging.config.fileConfig("logging.cfg") logging_cfg = os.path.join(os.path.dirname(__file__), "logging.cfg")
logging.config.fileConfig(logging_cfg)
logger = logging.getLogger() logger = logging.getLogger()
LogWriter.override_std_err(logger) LogWriter.override_std_err(logger)
@ -249,7 +251,7 @@ class PypoPush(Thread):
self.start_web_stream_buffer(current_item) self.start_web_stream_buffer(current_item)
self.start_web_stream(current_item) self.start_web_stream(current_item)
if is_file(current_item): if is_file(current_item):
self.modify_cue_point(file_chain[0]) file_chain = self.modify_first_link_cue_point(file_chain)
self.push_to_liquidsoap(file_chain) self.push_to_liquidsoap(file_chain)
#we've changed the queue, so let's refetch it #we've changed the queue, so let's refetch it
liquidsoap_queue_approx = self.get_queue_items_from_liquidsoap() liquidsoap_queue_approx = self.get_queue_items_from_liquidsoap()
@ -279,7 +281,7 @@ class PypoPush(Thread):
chain_to_push = file_chain[problem_at_iteration:] chain_to_push = file_chain[problem_at_iteration:]
if len(chain_to_push) > 0: if len(chain_to_push) > 0:
self.modify_cue_point(chain_to_push[0]) chain_to_push = self.modify_first_link_cue_point(chain_to_push)
self.push_to_liquidsoap(chain_to_push) self.push_to_liquidsoap(chain_to_push)
@ -363,6 +365,18 @@ class PypoPush(Thread):
original_cue_in_td = timedelta(seconds=float(link['cue_in'])) original_cue_in_td = timedelta(seconds=float(link['cue_in']))
link['cue_in'] = self.date_interval_to_seconds(original_cue_in_td) + diff_sec link['cue_in'] = self.date_interval_to_seconds(original_cue_in_td) + diff_sec
def modify_first_link_cue_point(self, chain):
if not len(chain):
return []
first_link = chain[0]
self.modify_cue_point(first_link)
if float(first_link['cue_in']) >= float(first_link['cue_out']):
chain = chain [1:]
return chain
""" """
Returns two chains, original chain and current_chain. current_chain is a subset of Returns two chains, original chain and current_chain. current_chain is a subset of
original_chain but can also be equal to original chain. original_chain but can also be equal to original chain.

View file

@ -0,0 +1,18 @@
#!/bin/bash
which py.test
pytest_exist=$?
if [ "$pytest_exist" != "0" ]; then
echo "Need to have py.test installed. Exiting..."
exit 1
fi
SCRIPT=`readlink -f $0`
# Absolute directory this script is in
SCRIPTPATH=`dirname $SCRIPT`
export PYTHONPATH=$PYTHONPATH:$SCRIPTPATH/..:$SCRIPTPATH/../..
py.test

View file

@ -0,0 +1,26 @@
from pypopush import PypoPush
from threading import Lock
from Queue import Queue
import datetime
pypoPush_q = Queue()
telnet_lock = Lock()
pp = PypoPush(pypoPush_q, telnet_lock)
def test_modify_cue_in():
link = pp.modify_first_link_cue_point([])
assert len(link) == 0
min_ago = datetime.datetime.utcnow() - datetime.timedelta(minutes = 1)
link = [{"start":min_ago.strftime("%Y-%m-%d-%H-%M-%S"),
"cue_in":"0", "cue_out":"30"}]
link = pp.modify_first_link_cue_point(link)
assert len(link) == 0
link = [{"start":min_ago.strftime("%Y-%m-%d-%H-%M-%S"),
"cue_in":"0", "cue_out":"70"}]
link = pp.modify_first_link_cue_point(link)
assert len(link) == 1

View file

@ -170,6 +170,8 @@ def WatchAddAction(option, opt, value, parser):
print "%s added to watched folder list successfully" % path print "%s added to watched folder list successfully" % path
else: else:
print "Adding a watched folder failed: %s" % res['msg']['error'] print "Adding a watched folder failed: %s" % res['msg']['error']
print "This error most likely caused by wrong permissions"
print "Try fixing this error by chmodding the parent directory(ies)"
else: else:
print "Given path is not a directory: %s" % path print "Given path is not a directory: %s" % path