Merge branch 'devel' of dev.sourcefabric.org:airtime into devel
This commit is contained in:
commit
aec11feba5
62 changed files with 918 additions and 868 deletions
|
@ -17,8 +17,9 @@ version_url = 'version/api_key/%%api_key%%'
|
|||
register_component = 'register-component/format/json/api_key/%%api_key%%/component/%%component%%'
|
||||
|
||||
# Hostname
|
||||
base_url = 'localhost'
|
||||
host = 'localhost'
|
||||
base_port = 80
|
||||
base_dir = ''
|
||||
|
||||
#############################
|
||||
## Config for Media Monitor
|
||||
|
|
|
@ -12,11 +12,8 @@ import urllib
|
|||
import urllib2
|
||||
import logging
|
||||
import json
|
||||
from urlparse import urlparse
|
||||
import base64
|
||||
from configobj import ConfigObj
|
||||
import string
|
||||
import traceback
|
||||
|
||||
AIRTIME_VERSION = "2.2.0"
|
||||
|
||||
|
@ -44,6 +41,91 @@ def convert_dict_value_to_utf8(md):
|
|||
# Airtime API Client
|
||||
################################################################################
|
||||
|
||||
class UrlException(Exception): pass
|
||||
|
||||
class IncompleteUrl(UrlException):
|
||||
def __init__(self, url): self.url = url
|
||||
def __str__(self): return "Incomplete url: '%s'" % self.url
|
||||
|
||||
class UrlBadParam(UrlException):
|
||||
def __init__(self, url, param):
|
||||
self.url = url
|
||||
self.param = param
|
||||
def __str__(self):
|
||||
return "Bad param '%s' passed into url: '%s'" % (self.param, self.url)
|
||||
|
||||
class ApcUrl(object):
|
||||
""" A safe abstraction and testable for filling in parameters in
|
||||
api_client.cfg"""
|
||||
def __init__(self, base_url): self.base_url = base_url
|
||||
|
||||
def params(self, **params):
|
||||
temp_url = self.base_url
|
||||
for k, v in params.iteritems():
|
||||
wrapped_param = "%%" + k + "%%"
|
||||
if wrapped_param in temp_url:
|
||||
temp_url = temp_url.replace(wrapped_param, str(v))
|
||||
else: raise UrlBadParam(self.base_url, k)
|
||||
return ApcUrl(temp_url)
|
||||
|
||||
def url(self):
|
||||
if '%%' in self.base_url: raise IncompleteUrl(self.base_url)
|
||||
else: return self.base_url
|
||||
|
||||
class ApiRequest(object):
|
||||
def __init__(self, name, url):
|
||||
self.name = name
|
||||
self.url = url
|
||||
self.__req = None
|
||||
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()
|
||||
if _post_data is not None: _post_data = urllib.urlencode(_post_data)
|
||||
req = urllib2.Request(final_url, _post_data)
|
||||
response = urllib2.urlopen(req).read()
|
||||
# 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)
|
||||
try: return json.loads(response)
|
||||
except ValueError: return response
|
||||
|
||||
def req(self, *args, **kwargs):
|
||||
self.__req = lambda : self(*args, **kwargs)
|
||||
return self
|
||||
|
||||
def retry(self, n, delay=5):
|
||||
"""Try to send request n times. If after n times it fails then
|
||||
we finally raise exception"""
|
||||
for i in range(0,n-1):
|
||||
try: return self.__req()
|
||||
except Exception: time.sleep(delay)
|
||||
return self.__req()
|
||||
|
||||
class RequestProvider(object):
|
||||
""" Creates the available ApiRequest instance that can be read from
|
||||
a config file """
|
||||
def __init__(self, cfg):
|
||||
self.config = cfg
|
||||
self.requests = {}
|
||||
self.url = ApcUrl("http://%s:%s/%s/%s/%s" \
|
||||
% (self.config["host"], str(self.config["base_port"]),
|
||||
self.config["base_dir"], self.config["api_base"],
|
||||
'%%action%%'))
|
||||
# Now we must discover the possible actions
|
||||
actions = dict( (k,v) for k,v in cfg.iteritems() if '%%api_key%%' in v)
|
||||
for action_name, action_value in actions.iteritems():
|
||||
new_url = self.url.params(action=action_value).params(
|
||||
api_key=self.config['api_key'])
|
||||
self.requests[action_name] = ApiRequest(action_name, new_url)
|
||||
|
||||
def available_requests(self) : return self.requests.keys()
|
||||
def __contains__(self, request) : return request in self.requests
|
||||
|
||||
def __getattr__(self, attr):
|
||||
if attr in self: return self.requests[attr]
|
||||
else: return super(RequestProvider, self).__getattribute__(attr)
|
||||
|
||||
|
||||
class AirtimeApiClient(object):
|
||||
|
||||
# This is a little hacky fix so that I don't have to pass the config object
|
||||
|
@ -64,129 +146,21 @@ class AirtimeApiClient(object):
|
|||
config_path=AirtimeApiClient.default_config )
|
||||
|
||||
def __init__(self, logger=None,config_path='/etc/airtime/api_client.cfg'):
|
||||
if logger is None:
|
||||
self.logger = logging
|
||||
else:
|
||||
self.logger = logger
|
||||
if logger is None: self.logger = logging
|
||||
else: self.logger = logger
|
||||
|
||||
# loading config file
|
||||
try:
|
||||
self.config = ConfigObj(config_path)
|
||||
self.services = RequestProvider(self.config)
|
||||
except Exception, e:
|
||||
self.logger.error('Error loading config file: %s', e)
|
||||
sys.exit(1)
|
||||
|
||||
def get_response_from_server(self, url, attempts=-1):
|
||||
logger = self.logger
|
||||
successful_response = False
|
||||
|
||||
while not successful_response:
|
||||
try:
|
||||
response = urllib2.urlopen(url).read()
|
||||
successful_response = True
|
||||
except IOError, e:
|
||||
logger.error('Error Authenticating with remote server: %s', e)
|
||||
if isinstance(url, urllib2.Request):
|
||||
logger.debug(url.get_full_url())
|
||||
else:
|
||||
logger.debug(url)
|
||||
except Exception, e:
|
||||
logger.error('Couldn\'t connect to remote server. Is it running?')
|
||||
logger.error("%s" % e)
|
||||
if isinstance(url, urllib2.Request):
|
||||
logger.debug(url.get_full_url())
|
||||
else:
|
||||
logger.debug(url)
|
||||
|
||||
#If the user passed in a positive attempts number then that means
|
||||
#attempts will roll over 0 and we stop. If attempts was initially negative,
|
||||
#then we have unlimited attempts
|
||||
if attempts > 0:
|
||||
attempts = attempts - 1
|
||||
if attempts == 0:
|
||||
successful_response = True
|
||||
|
||||
if not successful_response:
|
||||
logger.error("Error connecting to server, waiting 5 seconds and trying again.")
|
||||
time.sleep(5)
|
||||
|
||||
return response
|
||||
|
||||
def get_response_into_file(self, url, block=True):
|
||||
"""
|
||||
This function will query the server and download its response directly
|
||||
into a temporary file. This is useful in the situation where the
|
||||
response from the server can be huge and we don't want to store it into
|
||||
memory (potentially causing Python to use hundreds of MB's of memory).
|
||||
By writing into a file we can then open this file later, and read data
|
||||
a little bit at a time and be very mem efficient.
|
||||
|
||||
The return value of this function is the path of the temporary file.
|
||||
Unless specified using block = False, this function will block until a
|
||||
successful HTTP 200 response is received.
|
||||
"""
|
||||
|
||||
logger = self.logger
|
||||
successful_response = False
|
||||
|
||||
while not successful_response:
|
||||
try:
|
||||
path = urllib.urlretrieve(url)[0]
|
||||
successful_response = True
|
||||
except IOError, e:
|
||||
logger.error('Error Authenticating with remote server: %s', e)
|
||||
if not block:
|
||||
raise
|
||||
except Exception, e:
|
||||
logger.error('Couldn\'t connect to remote server. Is it running?')
|
||||
logger.error("%s" % e)
|
||||
if not block:
|
||||
raise
|
||||
|
||||
if not successful_response:
|
||||
logger.error("Error connecting to server, waiting 5 seconds and trying again.")
|
||||
time.sleep(5)
|
||||
|
||||
return path
|
||||
|
||||
|
||||
|
||||
def __get_airtime_version(self):
|
||||
logger = self.logger
|
||||
url = "http://%s:%s/%s/%s" % (self.config["base_url"],
|
||||
str(self.config["base_port"]), self.config["api_base"],
|
||||
self.config["version_url"])
|
||||
logger.debug("Trying to contact %s", url)
|
||||
url = url.replace("%%api_key%%", self.config["api_key"])
|
||||
|
||||
version = -1
|
||||
try:
|
||||
data = self.get_response_from_server(url)
|
||||
logger.debug("Data: %s", data)
|
||||
response_json = json.loads(data)
|
||||
version = response_json['version']
|
||||
logger.debug("Airtime Version %s detected", version)
|
||||
except Exception, e:
|
||||
logger.error("Unable to detect Airtime Version - %s", e)
|
||||
return -1
|
||||
|
||||
return version
|
||||
|
||||
def test(self):
|
||||
logger = self.logger
|
||||
items = self.get_schedule()[1]
|
||||
schedule = items["playlists"]
|
||||
logger.debug("Number of playlists found: %s", str(len(schedule)))
|
||||
count = 1
|
||||
for pkey in sorted(schedule.iterkeys()):
|
||||
logger.debug("Playlist #%s", str(count))
|
||||
count += 1
|
||||
playlist = schedule[pkey]
|
||||
for item in playlist["medias"]:
|
||||
filename = urlparse(item["uri"])
|
||||
filename = filename.query[5:]
|
||||
self.get_media(item["uri"], filename)
|
||||
|
||||
# TODO : maybe fix this function to drop an exception?
|
||||
try: return self.services.version_url()
|
||||
except Exception: return -1
|
||||
|
||||
def is_server_compatible(self, verbose=True):
|
||||
logger = self.logger
|
||||
|
@ -209,105 +183,30 @@ class AirtimeApiClient(object):
|
|||
|
||||
|
||||
def get_schedule(self):
|
||||
logger = self.logger
|
||||
|
||||
# Construct the URL
|
||||
export_url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["export_url"])
|
||||
|
||||
logger.info("Fetching schedule from %s", export_url)
|
||||
export_url = export_url.replace('%%api_key%%', self.config["api_key"])
|
||||
|
||||
response = ""
|
||||
try:
|
||||
response_json = self.get_response_from_server(export_url)
|
||||
response = json.loads(response_json)
|
||||
success = True
|
||||
except Exception, e:
|
||||
logger.error(e)
|
||||
success = False
|
||||
|
||||
return success, response
|
||||
|
||||
|
||||
def get_media(self, uri, dst):
|
||||
logger = self.logger
|
||||
|
||||
try:
|
||||
src = uri + "/api_key/%%api_key%%"
|
||||
logger.info("try to download from %s to %s", src, dst)
|
||||
src = src.replace("%%api_key%%", self.config["api_key"])
|
||||
# check if file exists already before downloading again
|
||||
headers = urllib.urlretrieve(src, dst)[1]
|
||||
logger.info(headers)
|
||||
except Exception, e:
|
||||
logger.error("%s", e)
|
||||
# TODO : properly refactor this routine
|
||||
# For now thre return type is a little fucked for compatibility reasons
|
||||
try: return (True, self.services.export_url())
|
||||
except: (False, "")
|
||||
|
||||
def notify_liquidsoap_started(self):
|
||||
logger = self.logger
|
||||
return self.services.notify_liquidsoap_started()
|
||||
|
||||
try:
|
||||
url = "http://%s:%s/%s/%s" % (self.config["base_url"], \
|
||||
str(self.config["base_port"]), \
|
||||
self.config["api_base"], \
|
||||
self.config["notify_liquidsoap_started"])
|
||||
|
||||
url = url.replace("%%api_key%%", self.config["api_key"])
|
||||
|
||||
self.get_response_from_server(url, attempts=5)
|
||||
except Exception, e:
|
||||
logger.error("Exception: %s", str(e))
|
||||
|
||||
|
||||
"""
|
||||
This is a callback from liquidsoap, we use this to notify about the
|
||||
currently playing *song*. We get passed a JSON string which we handed to
|
||||
liquidsoap in get_liquidsoap_data().
|
||||
"""
|
||||
def notify_media_item_start_playing(self, media_id):
|
||||
logger = self.logger
|
||||
response = ''
|
||||
try:
|
||||
url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["update_start_playing_url"])
|
||||
url = url.replace("%%media_id%%", str(media_id))
|
||||
logger.debug(url)
|
||||
url = url.replace("%%api_key%%", self.config["api_key"])
|
||||
|
||||
response = self.get_response_from_server(url, attempts = 5)
|
||||
response = json.loads(response)
|
||||
logger.info("API-Status %s", response['status'])
|
||||
logger.info("API-Message %s", response['message'])
|
||||
|
||||
except Exception, e:
|
||||
logger.error("Exception: %s", e)
|
||||
|
||||
return response
|
||||
""" This is a callback from liquidsoap, we use this to notify
|
||||
about the currently playing *song*. We get passed a JSON string
|
||||
which we handed to liquidsoap in get_liquidsoap_data(). """
|
||||
return self.services.update_start_playing_url(media_id=media_id)
|
||||
|
||||
# TODO : get this routine out of here it doesn't belong at all here
|
||||
def get_liquidsoap_data(self, pkey, schedule):
|
||||
playlist = schedule[pkey]
|
||||
data = dict()
|
||||
try:
|
||||
data["schedule_id"] = playlist['id']
|
||||
except Exception:
|
||||
data["schedule_id"] = 0
|
||||
try: data["schedule_id"] = playlist['id']
|
||||
except Exception: data["schedule_id"] = 0
|
||||
return data
|
||||
|
||||
def get_shows_to_record(self):
|
||||
logger = self.logger
|
||||
response = None
|
||||
try:
|
||||
url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["show_schedule_url"])
|
||||
logger.debug(url)
|
||||
url = url.replace("%%api_key%%", self.config["api_key"])
|
||||
response = self.get_response_from_server(url)
|
||||
|
||||
response = json.loads(response)
|
||||
logger.info("shows %s", response)
|
||||
|
||||
except Exception, e:
|
||||
logger.error("Exception: %s", e)
|
||||
response = None
|
||||
|
||||
return response
|
||||
return self.services.show_schedule_url()
|
||||
|
||||
def upload_recorded_show(self, data, headers):
|
||||
logger = self.logger
|
||||
|
@ -316,10 +215,9 @@ class AirtimeApiClient(object):
|
|||
retries = int(self.config["upload_retries"])
|
||||
retries_wait = int(self.config["upload_wait"])
|
||||
|
||||
url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["upload_file_url"])
|
||||
url = self.construct_url("upload_file_url")
|
||||
|
||||
logger.debug(url)
|
||||
url = url.replace("%%api_key%%", self.config["api_key"])
|
||||
|
||||
for i in range(0, retries):
|
||||
logger.debug("Upload attempt: %s", i + 1)
|
||||
|
@ -344,86 +242,21 @@ class AirtimeApiClient(object):
|
|||
return response
|
||||
|
||||
def check_live_stream_auth(self, username, password, dj_type):
|
||||
"""
|
||||
TODO: Why are we using print statements here? Possibly use logger that
|
||||
is directed to stdout. -MK
|
||||
"""
|
||||
|
||||
response = ''
|
||||
try:
|
||||
url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["check_live_stream_auth"])
|
||||
|
||||
url = url.replace("%%api_key%%", self.config["api_key"])
|
||||
url = url.replace("%%username%%", username)
|
||||
url = url.replace("%%djtype%%", dj_type)
|
||||
url = url.replace("%%password%%", password)
|
||||
|
||||
response = self.get_response_from_server(url)
|
||||
response = json.loads(response)
|
||||
except Exception, e:
|
||||
print "Exception: %s", e
|
||||
print "traceback: %s", traceback.format_exc()
|
||||
response = None
|
||||
|
||||
return response
|
||||
return self.services.check_live_stream_auth(
|
||||
username=username, password=password, dj_type=dj_type)
|
||||
|
||||
def construct_url(self,config_action_key):
|
||||
"""Constructs the base url for every request"""
|
||||
# TODO : Make other methods in this class use this this method.
|
||||
url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config[config_action_key])
|
||||
url = "http://%s:%s/%s/%s/%s" % \
|
||||
(self.config["host"], str(self.config["base_port"]),
|
||||
self.config["base_dir"], self.config["api_base"],
|
||||
self.config[config_action_key])
|
||||
url = url.replace("%%api_key%%", self.config["api_key"])
|
||||
return url
|
||||
|
||||
def setup_media_monitor(self):
|
||||
logger = self.logger
|
||||
response = None
|
||||
try:
|
||||
url = self.construct_url("media_setup_url")
|
||||
response = self.get_response_from_server(url)
|
||||
response = json.loads(response)
|
||||
logger.info("Connected to Airtime Server. Json Media Storage Dir: %s", response)
|
||||
except Exception, e:
|
||||
response = None
|
||||
logger.error("Exception: %s", e)
|
||||
return response
|
||||
|
||||
def update_media_metadata(self, md, mode, is_record=False):
|
||||
logger = self.logger
|
||||
response = None
|
||||
try:
|
||||
url = self.construct_url("update_media_url")
|
||||
url = url.replace("%%mode%%", mode)
|
||||
|
||||
self.logger.info("Requesting url %s" % url)
|
||||
|
||||
md = convert_dict_value_to_utf8(md)
|
||||
|
||||
data = urllib.urlencode(md)
|
||||
req = urllib2.Request(url, data)
|
||||
|
||||
response = self.get_response_from_server(req)
|
||||
logger.info("update media %s, filepath: %s, mode: %s", response, md['MDATA_KEY_FILEPATH'], mode)
|
||||
self.logger.info("Received response:")
|
||||
self.logger.info(response)
|
||||
try: response = json.loads(response)
|
||||
except ValueError:
|
||||
logger.info("Could not parse json from response: '%s'" % response)
|
||||
|
||||
if("error" not in response and is_record):
|
||||
url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["upload_recorded"])
|
||||
url = url.replace("%%fileid%%", str(response[u'id']))
|
||||
url = url.replace("%%showinstanceid%%", str(md['MDATA_KEY_TRACKNUMBER']))
|
||||
url = url.replace("%%api_key%%", self.config["api_key"])
|
||||
|
||||
response = self.get_response_from_server(url)
|
||||
response = json.loads(response)
|
||||
logger.info("associate recorded %s", response)
|
||||
except Exception, e:
|
||||
response = None
|
||||
logger.error('Exception: %s', e)
|
||||
logger.error("traceback: %s", traceback.format_exc())
|
||||
|
||||
return response
|
||||
return self.services.media_setup_url()
|
||||
|
||||
def send_media_monitor_requests(self, action_list, dry=False):
|
||||
"""
|
||||
|
@ -434,7 +267,6 @@ class AirtimeApiClient(object):
|
|||
says whether the show was recorded or not. The value of this key
|
||||
does not matter, only if it's present or not.
|
||||
"""
|
||||
url = self.construct_url('reload_metadata_group')
|
||||
# We are assuming that action_list is a list of dictionaries such
|
||||
# that every dictionary represents the metadata of a file along
|
||||
# with a special mode key that is the action to be executed by the
|
||||
|
@ -462,11 +294,7 @@ class AirtimeApiClient(object):
|
|||
# controller not to actually do any changes
|
||||
if dry: md_list['dry'] = 1
|
||||
self.logger.info("Pumping out %d requests..." % len(valid_actions))
|
||||
data = urllib.urlencode(md_list)
|
||||
req = urllib2.Request(url, data)
|
||||
response = self.get_response_from_server(req)
|
||||
response = json.loads(response)
|
||||
return response
|
||||
return self.services.reload_metadata_group(_post_data=md_list)
|
||||
|
||||
#returns a list of all db files for a given directory in JSON format:
|
||||
#{"files":["path/to/file1", "path/to/file2"]}
|
||||
|
@ -476,15 +304,11 @@ class AirtimeApiClient(object):
|
|||
logger = self.logger
|
||||
try:
|
||||
all_files = u"1" if all_files else u"0"
|
||||
url = self.construct_url("list_all_db_files")
|
||||
url = url.replace("%%dir_id%%", dir_id)
|
||||
url = url.replace("%%all%%", all_files)
|
||||
response = self.get_response_from_server(url)
|
||||
response = json.loads(response)
|
||||
response = self.services.list_all_db_files(dir_id=dir_id,
|
||||
all=all_files)
|
||||
except Exception, e:
|
||||
response = {}
|
||||
logger.error("Exception: %s", e)
|
||||
|
||||
try:
|
||||
return response["files"]
|
||||
except KeyError:
|
||||
|
@ -493,188 +317,52 @@ class AirtimeApiClient(object):
|
|||
return []
|
||||
|
||||
def list_all_watched_dirs(self):
|
||||
# Does this include the stor directory as well?
|
||||
logger = self.logger
|
||||
try:
|
||||
url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["list_all_watched_dirs"])
|
||||
|
||||
url = url.replace("%%api_key%%", self.config["api_key"])
|
||||
|
||||
response = self.get_response_from_server(url)
|
||||
response = json.loads(response)
|
||||
except Exception, e:
|
||||
response = None
|
||||
logger.error("Exception: %s", e)
|
||||
self.logger.debug(traceback.format_exc())
|
||||
|
||||
return response
|
||||
return self.services.list_all_watched_dirs()
|
||||
|
||||
def add_watched_dir(self, path):
|
||||
logger = self.logger
|
||||
try:
|
||||
url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["add_watched_dir"])
|
||||
|
||||
url = url.replace("%%api_key%%", self.config["api_key"])
|
||||
url = url.replace("%%path%%", base64.b64encode(path))
|
||||
|
||||
response = self.get_response_from_server(url)
|
||||
response = json.loads(response)
|
||||
except Exception, e:
|
||||
response = None
|
||||
logger.error("Exception: %s", e)
|
||||
|
||||
return response
|
||||
return self.services.add_watched_dir(path=base64.b64encode(path))
|
||||
|
||||
def remove_watched_dir(self, path):
|
||||
logger = self.logger
|
||||
try:
|
||||
url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["remove_watched_dir"])
|
||||
|
||||
url = url.replace("%%api_key%%", self.config["api_key"])
|
||||
url = url.replace("%%path%%", base64.b64encode(path))
|
||||
|
||||
response = self.get_response_from_server(url)
|
||||
response = json.loads(response)
|
||||
except Exception, e:
|
||||
response = None
|
||||
logger.error("Exception: %s", e)
|
||||
|
||||
return response
|
||||
return self.services.remove_watched_dir(path=base64.b64encode(path))
|
||||
|
||||
def set_storage_dir(self, path):
|
||||
logger = self.logger
|
||||
try:
|
||||
url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["set_storage_dir"])
|
||||
|
||||
url = url.replace("%%api_key%%", self.config["api_key"])
|
||||
url = url.replace("%%path%%", base64.b64encode(path))
|
||||
|
||||
response = self.get_response_from_server(url)
|
||||
response = json.loads(response)
|
||||
except Exception, e:
|
||||
response = None
|
||||
logger.error("Exception: %s", e)
|
||||
|
||||
return response
|
||||
return self.services.set_storage_dir(path=base64.b64encode(path))
|
||||
|
||||
def get_stream_setting(self):
|
||||
logger = self.logger
|
||||
try:
|
||||
url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["get_stream_setting"])
|
||||
|
||||
url = url.replace("%%api_key%%", self.config["api_key"])
|
||||
response = self.get_response_from_server(url)
|
||||
response = json.loads(response)
|
||||
try: return self.services.get_stream_setting()
|
||||
except Exception, e:
|
||||
response = None
|
||||
logger.error("Exception: %s", e)
|
||||
return None
|
||||
|
||||
return response
|
||||
|
||||
"""
|
||||
Purpose of this method is to contact the server with a "Hey its me!"
|
||||
message. This will allow the server to register the component's (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 http server.
|
||||
"""
|
||||
def register_component(self, component):
|
||||
logger = self.logger
|
||||
try:
|
||||
url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["register_component"])
|
||||
|
||||
url = url.replace("%%api_key%%", self.config["api_key"])
|
||||
url = url.replace("%%component%%", component)
|
||||
self.get_response_from_server(url)
|
||||
except Exception, e:
|
||||
logger.error("Exception: %s", e)
|
||||
""" Purpose of this method is to contact the server with a "Hey its
|
||||
me!" message. This will allow the server to register the component's
|
||||
(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
|
||||
http server. """
|
||||
return self.services.register_component(component=component)
|
||||
|
||||
def notify_liquidsoap_status(self, msg, stream_id, time):
|
||||
logger = self.logger
|
||||
try:
|
||||
url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["update_liquidsoap_status"])
|
||||
|
||||
url = url.replace("%%api_key%%", self.config["api_key"])
|
||||
msg = msg.replace('/', ' ')
|
||||
encoded_msg = urllib.quote(msg, '')
|
||||
url = url.replace("%%msg%%", encoded_msg)
|
||||
url = url.replace("%%stream_id%%", stream_id)
|
||||
url = url.replace("%%boot_time%%", time)
|
||||
|
||||
self.get_response_from_server(url, attempts = 5)
|
||||
self.update_liquidsoap_status.req(msg=encoded_msg, stream_id=stream_id,
|
||||
boot_time=time).retry(5)
|
||||
except Exception, e:
|
||||
logger.error("Exception: %s", e)
|
||||
|
||||
def notify_source_status(self, sourcename, status):
|
||||
logger = self.logger
|
||||
try:
|
||||
url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["update_source_status"])
|
||||
|
||||
url = url.replace("%%api_key%%", self.config["api_key"])
|
||||
url = url.replace("%%sourcename%%", sourcename)
|
||||
url = url.replace("%%status%%", status)
|
||||
|
||||
self.get_response_from_server(url, attempts = 5)
|
||||
logger = self.logger
|
||||
return self.services.update_source_status.req(sourcename=sourcename,
|
||||
status=status).retry(5)
|
||||
except Exception, e:
|
||||
logger.error("Exception: %s", e)
|
||||
|
||||
"""
|
||||
This function updates status of mounted file system information on airtime
|
||||
"""
|
||||
def update_file_system_mount(self, added_dir, removed_dir):
|
||||
logger = self.logger
|
||||
try:
|
||||
url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["update_fs_mount"])
|
||||
|
||||
url = url.replace("%%api_key%%", self.config["api_key"])
|
||||
|
||||
added_data_string = string.join(added_dir, ',')
|
||||
removed_data_string = string.join(removed_dir, ',')
|
||||
|
||||
map = [("added_dir", added_data_string), ("removed_dir", removed_data_string)]
|
||||
|
||||
data = urllib.urlencode(map)
|
||||
|
||||
req = urllib2.Request(url, data)
|
||||
response = self.get_response_from_server(req)
|
||||
|
||||
logger.info("update file system mount: %s", json.loads(response))
|
||||
except Exception, e:
|
||||
logger.error('Exception: %s', e)
|
||||
logger.error("traceback: %s", traceback.format_exc())
|
||||
|
||||
"""
|
||||
When watched dir is missing(unplugged or something) on boot up, this
|
||||
function will get called and will call appropriate function on Airtime.
|
||||
"""
|
||||
def handle_watched_dir_missing(self, dir):
|
||||
logger = self.logger
|
||||
try:
|
||||
url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["handle_watched_dir_missing"])
|
||||
|
||||
url = url.replace("%%api_key%%", self.config["api_key"])
|
||||
url = url.replace("%%dir%%", base64.b64encode(dir))
|
||||
|
||||
response = self.get_response_from_server(url)
|
||||
logger.info("update file system mount: %s", json.loads(response))
|
||||
except Exception, e:
|
||||
logger.error('Exception: %s', e)
|
||||
logger.error("traceback: %s", traceback.format_exc())
|
||||
|
||||
def get_bootstrap_info(self):
|
||||
"""
|
||||
Retrive infomations needed on bootstrap time
|
||||
"""
|
||||
logger = self.logger
|
||||
try:
|
||||
url = self.construct_url("get_bootstrap_info")
|
||||
response = self.get_response_from_server(url)
|
||||
response = json.loads(response)
|
||||
logger.info("Bootstrap info retrieved %s", response)
|
||||
except Exception, e:
|
||||
response = None
|
||||
logger.error('Exception: %s', e)
|
||||
logger.error("traceback: %s", traceback.format_exc())
|
||||
return response
|
||||
""" Retrive infomations needed on bootstrap time """
|
||||
return self.services.get_bootstrap_info()
|
||||
|
||||
def get_files_without_replay_gain_value(self, dir_id):
|
||||
"""
|
||||
|
@ -682,43 +370,16 @@ class AirtimeApiClient(object):
|
|||
calculated. This list of files is downloaded into a file and the path
|
||||
to this file is the return value.
|
||||
"""
|
||||
|
||||
#http://localhost/api/get-files-without-replay-gain/dir_id/1
|
||||
|
||||
logger = self.logger
|
||||
try:
|
||||
url = "http://%(base_url)s:%(base_port)s/%(api_base)s/%(get_files_without_replay_gain)s/" % (self.config)
|
||||
url = url.replace("%%api_key%%", self.config["api_key"])
|
||||
url = url.replace("%%dir_id%%", dir_id)
|
||||
response = self.get_response_from_server(url)
|
||||
|
||||
logger.info("update file system mount: %s", response)
|
||||
response = json.loads(response)
|
||||
#file_path = self.get_response_into_file(url)
|
||||
except Exception, e:
|
||||
response = None
|
||||
logger.error('Exception: %s', e)
|
||||
logger.error("traceback: %s", traceback.format_exc())
|
||||
|
||||
return response
|
||||
return self.services.get_files_without_replay_gain(dir_id=dir_id)
|
||||
|
||||
def update_replay_gain_values(self, pairs):
|
||||
"""
|
||||
'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
|
||||
"""
|
||||
|
||||
#http://localhost/api/update-replay-gain-value/
|
||||
try:
|
||||
url = "http://%(base_url)s:%(base_port)s/%(api_base)s/%(update_replay_gain_value)s/" % (self.config)
|
||||
url = url.replace("%%api_key%%", self.config["api_key"])
|
||||
data = urllib.urlencode({'data': json.dumps(pairs)})
|
||||
request = urllib2.Request(url, data)
|
||||
|
||||
self.logger.debug(self.get_response_from_server(request))
|
||||
except Exception, e:
|
||||
self.logger.error("Exception: %s", e)
|
||||
raise
|
||||
self.logger.debug(self.services.update_replay_gain_value(
|
||||
_post_data={'data': json.dumps(pairs)}))
|
||||
|
||||
|
||||
def notify_webstream_data(self, data, media_id):
|
||||
|
@ -726,14 +387,5 @@ class AirtimeApiClient(object):
|
|||
Update the server with the latest metadata we've received from the
|
||||
external webstream
|
||||
"""
|
||||
try:
|
||||
url = "http://%(base_url)s:%(base_port)s/%(api_base)s/%(notify_webstream_data)s/" % (self.config)
|
||||
url = url.replace("%%media_id%%", str(media_id))
|
||||
url = url.replace("%%api_key%%", self.config["api_key"])
|
||||
data = urllib.urlencode({'data': data})
|
||||
self.logger.debug(url)
|
||||
request = urllib2.Request(url, data)
|
||||
|
||||
self.logger.info(self.get_response_from_server(request, attempts = 5))
|
||||
except Exception, e:
|
||||
self.logger.error("Exception: %s", e)
|
||||
self.logger.info( self.notify_webstream_data.req(
|
||||
_post_data={'data':data}, media_id=str(media_id)).retry(5))
|
||||
|
|
0
python_apps/api_clients/tests/__init__.py
Normal file
0
python_apps/api_clients/tests/__init__.py
Normal file
30
python_apps/api_clients/tests/test_apcurl.py
Normal file
30
python_apps/api_clients/tests/test_apcurl.py
Normal file
|
@ -0,0 +1,30 @@
|
|||
import unittest
|
||||
from .. api_client import ApcUrl, UrlBadParam, IncompleteUrl
|
||||
|
||||
class TestApcUrl(unittest.TestCase):
|
||||
def test_init(self):
|
||||
url = "/testing"
|
||||
u = ApcUrl(url)
|
||||
self.assertEquals( u.base_url, url)
|
||||
|
||||
def test_params_1(self):
|
||||
u = ApcUrl("/testing/%%key%%")
|
||||
self.assertEquals(u.params(key='val').url(), '/testing/val')
|
||||
|
||||
def test_params_2(self):
|
||||
u = ApcUrl('/testing/%%key%%/%%api%%/more_testing')
|
||||
full_url = u.params(key="AAA",api="BBB").url()
|
||||
self.assertEquals(full_url, '/testing/AAA/BBB/more_testing')
|
||||
|
||||
def test_params_ex(self):
|
||||
u = ApcUrl("/testing/%%key%%")
|
||||
with self.assertRaises(UrlBadParam):
|
||||
u.params(bad_key='testing')
|
||||
|
||||
def test_url(self):
|
||||
u = "one/two/three"
|
||||
self.assertEquals( ApcUrl(u).url(), u )
|
||||
|
||||
def test_url_ex(self):
|
||||
u = ApcUrl('/%%one%%/%%two%%/three').params(two='testing')
|
||||
with self.assertRaises(IncompleteUrl): u.url()
|
21
python_apps/api_clients/tests/test_apirequest.py
Normal file
21
python_apps/api_clients/tests/test_apirequest.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
import unittest
|
||||
import json
|
||||
from mock import MagicMock, patch
|
||||
from .. api_client import ApcUrl, ApiRequest
|
||||
|
||||
class TestApiRequest(unittest.TestCase):
|
||||
def test_init(self):
|
||||
u = ApiRequest('request_name', ApcUrl('/test/ing'))
|
||||
self.assertEquals(u.name, "request_name")
|
||||
|
||||
def test_call(self):
|
||||
ret = json.dumps( {u'ok':u'ok'} )
|
||||
read = MagicMock()
|
||||
read.read = MagicMock(return_value=ret)
|
||||
u = '/testing'
|
||||
with patch('urllib2.urlopen') as mock_method:
|
||||
mock_method.return_value = read
|
||||
request = ApiRequest('mm', ApcUrl(u))()
|
||||
self.assertEquals(request, json.loads(ret))
|
||||
|
||||
if __name__ == '__main__': unittest.main()
|
32
python_apps/api_clients/tests/test_requestprovider.py
Normal file
32
python_apps/api_clients/tests/test_requestprovider.py
Normal file
|
@ -0,0 +1,32 @@
|
|||
import unittest
|
||||
import json
|
||||
from mock import patch, MagicMock
|
||||
from configobj import ConfigObj
|
||||
from .. api_client import RequestProvider
|
||||
|
||||
class TestRequestProvider(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.cfg = ConfigObj('api_client.cfg')
|
||||
def test_test(self):
|
||||
self.assertTrue('api_key' in self.cfg)
|
||||
def test_init(self):
|
||||
rp = RequestProvider(self.cfg)
|
||||
self.assertTrue( len( rp.available_requests() ) > 0 )
|
||||
def test_contains(self):
|
||||
rp = RequestProvider(self.cfg)
|
||||
methods = ['upload_recorded', 'update_media_url', 'list_all_db_files']
|
||||
for meth in methods:
|
||||
self.assertTrue( meth in rp )
|
||||
|
||||
def test_notify_webstream_data(self):
|
||||
ret = json.dumps( {u'testing' : u'123' } )
|
||||
rp = RequestProvider(self.cfg)
|
||||
read = MagicMock()
|
||||
read.read = MagicMock(return_value=ret)
|
||||
with patch('urllib2.urlopen') as mock_method:
|
||||
mock_method.return_value = read
|
||||
response = rp.notify_webstream_data(media_id=123)
|
||||
mock_method.called_once_with(media_id=123)
|
||||
self.assertEquals(json.loads(ret), response)
|
||||
|
||||
if __name__ == '__main__': unittest.main()
|
|
@ -256,3 +256,10 @@ class Manager(Loggable):
|
|||
notifier = pyinotify.Notifier(self.wm)
|
||||
notifier.coalesce_events()
|
||||
notifier.loop()
|
||||
#notifier = pyinotify.ThreadedNotifier(self.wm, read_freq=1)
|
||||
#notifier.coalesce_events()
|
||||
#notifier.start()
|
||||
#return notifier
|
||||
#import asyncore
|
||||
#notifier = pyinotify.AsyncNotifier(self.wm)
|
||||
#asyncore.loop()
|
||||
|
|
|
@ -56,7 +56,10 @@ class ReplayGainUpdater(Thread, Loggable):
|
|||
full_path = os.path.join(dir_path, f['fp'])
|
||||
processed_data.append((f['id'], replaygain.calculate_replay_gain(full_path)))
|
||||
|
||||
self.api_client.update_replay_gain_values(processed_data)
|
||||
try:
|
||||
self.api_client.update_replay_gain_values(processed_data)
|
||||
except Exception as e: self.unexpected_exception(e)
|
||||
|
||||
if len(files) == 0: break
|
||||
self.logger.info("Processed: %d songs" % total)
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ import media.monitor.pure as mmp
|
|||
from api_clients import api_client as apc
|
||||
|
||||
|
||||
|
||||
def main(global_config, api_client_config, log_config,
|
||||
index_create_attempt=False):
|
||||
for cfg in [global_config, api_client_config]:
|
||||
|
@ -134,7 +135,7 @@ def main(global_config, api_client_config, log_config,
|
|||
|
||||
apiclient.register_component('media-monitor')
|
||||
|
||||
manager.loop()
|
||||
return manager.loop()
|
||||
|
||||
__doc__ = """
|
||||
Usage:
|
||||
|
@ -147,6 +148,9 @@ Options:
|
|||
--log=<path> log config at <path>
|
||||
"""
|
||||
|
||||
def main_loop():
|
||||
while True: pass
|
||||
|
||||
if __name__ == '__main__':
|
||||
from docopt import docopt
|
||||
args = docopt(__doc__,version="mm1.99")
|
||||
|
@ -156,3 +160,5 @@ if __name__ == '__main__':
|
|||
sys.exit(0)
|
||||
print("Running mm1.99")
|
||||
main(args['--config'],args['--apiclient'],args['--log'])
|
||||
#gevent.joinall([ gevent.spawn(main_loop) ])
|
||||
|
||||
|
|
|
@ -21,27 +21,26 @@ import inspect
|
|||
from configobj import ConfigObj
|
||||
|
||||
# custom imports
|
||||
from util import *
|
||||
from api_clients import *
|
||||
#from util import *
|
||||
from api_client import AirtimeApiClient
|
||||
|
||||
import random
|
||||
import unittest
|
||||
|
||||
# configure logging
|
||||
logging.config.fileConfig("logging-api-validator.cfg")
|
||||
#logging.config.fileConfig("logging-api-validator.cfg")
|
||||
|
||||
try:
|
||||
config = ConfigObj('/etc/airtime/pypo.cfg')
|
||||
except Exception, e:
|
||||
print 'Error loading config file: ', e
|
||||
sys.exit()
|
||||
|
||||
|
||||
|
||||
|
||||
class TestApiFunctions(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.api_client = api_client.api_client_factory(config)
|
||||
|
||||
self.api_client = AirtimeApiClient()
|
||||
|
||||
def test_is_server_compatible(self):
|
||||
self.assertTrue(self.api_client.is_server_compatible(False))
|
||||
|
||||
|
@ -59,16 +58,17 @@ class TestApiFunctions(unittest.TestCase):
|
|||
arr = dict()
|
||||
arr["x"] = dict()
|
||||
arr["x"]["schedule_id"]=1
|
||||
|
||||
|
||||
response = self.api_client.notify_scheduled_item_start_playing("x", arr)
|
||||
self.assertTrue(response.has_key("status"))
|
||||
self.assertTrue(response.has_key("message"))
|
||||
|
||||
def test_notify_media_item_start_playing(self):
|
||||
def test_notify_media_item_start_playing(self):
|
||||
response = self.api_client.notify_media_item_start_playing('{"schedule_id":1}', 5)
|
||||
return response =
|
||||
self.assertTrue(response.has_key("status"))
|
||||
self.assertTrue(response.has_key("message"))
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
|
Binary file not shown.
|
@ -10,3 +10,4 @@ wsgiref==0.1.2
|
|||
configobj==4.7.2
|
||||
mutagen==1.20
|
||||
docopt==0.4.2
|
||||
gevent==0.13.7
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue