#!/usr/bin/python3 # -*- coding: utf-8 -*- import base64 import logging # import urllib import random import string # import tempfile import sys import os import json import datetime import decimal import traceback import simplejson from libraries.base.calendar import AuraCalendarService # from libraries.utils.parsexml import parsexml from libraries.base.schedulerconfig import AuraSchedulerConfig from libraries.reporting.messenger import RedisMessenger from libraries.security.user import AuraUser from libraries.exceptions.auraexceptions import NoProgrammeLoadedException from libraries.database.broadcasts import ScheduleEntry #from modules.communication.liquidsoap.liquidcontroller import LiquidController from modules.communication.liquidsoap.LiquidSoapCommunicator import LiquidSoapCommunicator from modules.scheduling.scheduler import AuraScheduler """ AuraController Class Communicates with the liquidsoap server and the scheduler """ class AuraController(): messenger = RedisMessenger() liquidsoapcommunicator = None scheduler = None userdb = AuraUser() is_intern = False lq_error = '' config = None sender = None debug = False # Constructor def __init__(self, config, debug): """ Constructor @type sender: object @param sender: Der Communicator Adapter - z-B. zmq @type lqs_socket: string @param lqs_socket: Liquidsoap Player Socket @type lqs_recsocket: string @param lqs_recsocket: Liquidsoap Recorder Socket """ self.liquidsoapcommunicator = LiquidSoapCommunicator(self, config, debug) self.debug = debug # Felder die Liquidsoap fuer einen Track (rid) zurueckliefert self.knownfields = ["status", "album", "time", "title", "artist", "comment", "filename", "on_air", "source", "rid", "genre"] self.job_result = ['', '', '', '', '', ''] self.messenger.set_channel('controller') self.messenger.set_section('execjob') errors_file = config.get("install_dir")+"/errormessages/controller_error.js" self.errorData = simplejson.load(open(errors_file)) self.scheduler = AuraScheduler(config, self.liquidsoapcommunicator, self.debug) self.config = config # ------------------------------------------------------------------------------------------ # def set_sender(self, sender): """ :param sender: ServerZMQAdapter :return: None """ self.sender = sender # ------------------------------------------------------------------------------------------ # def message(self, message, log=False, warning=False): """ Daten an einen Client senden oder bei einer internen Aktion loggen @type message: string @param message: String, der gesendet wird @type log: boolean @param log: Wenn falsch, wird die message an den Client gesendet @type warning: boolean @param warning: Wenn falsch, ist der logging typ info, andernfalls warning @rtype: string/None @return: Die Message, falls log false ist """ if log: if warning: logging.warning(message) else: logging.info(message) if self.is_intern: return message self.sender.send(message) # ------------------------------------------------------------------------------------------ # def fetch_new_programme(self): acs = AuraCalendarService(self.config, "", "", False) queue = acs.get_queue() # start fetching thread acs.start() # wait for the end response = queue.get() if response == "fetching_finished": programme = acs.get_calendar_data() self.scheduler.reload_programme() return programme else: print("Got an unknown response from AuraCalendarService: "+response) # ------------------------------------------------------------------------------------------ # def get_act_programme(self): try: programme = self.scheduler.get_act_programme() except NoProgrammeLoadedException as e: print("WARNING: no programme in memory. i have to reload it!") # refetch the programme from pv and importer self.fetch_new_programme() # is the recursion here really needed, or an additional error source? return self.get_act_programme() except Exception as e: traceback.print_exc() # exc_type, exc_obj, exc_tb = sys.exc_info() # fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1] # print(e.with_traceback(True)) # exc_type, fname, exc_tb.tb_lineno) self.fatal("01") else: # entries = [] # for p in programme: # entries.append(p["playlist"]["entries"]) # print(p) # for p in programme["entries"]: # print(p) #retstring = json.dumps([p._asdict() for p in programme], default=alchemyencoder) #retstring = json.dumps([p._asdict() for p in programme["entries"]], default=alchemyencoder) #retstring = json.dumps(programme["entries"], default=alchemyencoder) #retstring = json.dumps([p["playlist"]["entries"] for p in programme], default=alchemyencoder) #print("WANNA SEND") #print(retstring) self.success("00") return programme # ------------------------------------------------------------------------------------------ # def liquid_startup(self): return self.liquidsoapcommunicator.liquid_startup() # ------------------------------------------------------------------------------------------ # def scheduler_data(self): """ Scheduler Config ausliefern """ jobs = [] try: # Das scheduler.xml laden schedulerconfig = AuraSchedulerConfig(self.sender.schedule_config) jobs = schedulerconfig.getJobs() except: # Das scheint kein gültiges XML zu sein self.warning('01', False) self.success('00', simplejson.dumps(jobs)) self.notify_client() # ------------------------------------------------------------------------------------------ # def scheduler_store(self, adminuser, adminpassword, json): """ Scheduler Config zurückschreiben """ if not self.userdb.hasAdminRights(adminuser, adminpassword): self.warning('01', False) self.notify_client() return try: schedulerconfig = AuraSchedulerConfig(self.sender.schedule_config) except: self.warning('02', False) self.notify_client() try: schedulerconfig.storeJsonToXml( base64.b64decode(json)) except: self.warning('02', False) self.notify_client() else: if self.scheduler_reload(): self.success('00', True) else: self.warning('02', False) self.notify_client() # ------------------------------------------------------------------------------------------ # def set_password(self, adminuser, adminpassword, username, password): """ Ein Userpasswort setzen TODO: Passwörter verschlüsselt übertragen """ if self.userdb.hasAdminRights(adminuser, adminpassword): self.userdb.setPassword(username, password) self.success('00', password) else: self.warning('01', False) self.notify_client() self.sender.reload_config() # ------------------------------------------------------------------------------------------ # def add_user(self, adminuser, adminpassword, username): """ Einen User hinzufügen TODO: Passwort verschlüsselt übertragen """ if self.userdb.hasAdminRights(adminuser, adminpassword): password = ''.join(random.sample(string.lowercase+string.uppercase+string.digits,14)) self.userdb.insertUser(username, password, 'user') self.success('00', password) # TODO admin rechte checken user und passwort setzen, passwort zurückgeben else: self.warning('01', False) self.notify_client() self.sender.reload_config() # ------------------------------------------------------------------------------------------ # def del_user(self, adminuser, adminpassword, username): """ Einen User löschen TODO: Passwort verschlüsselt übertragen """ # TODO admin rechte checken user löschen if self.userdb.hasAdminRights(adminuser, adminpassword): self.userdb.delete(username) self.success('00', True) else: self.warning('01', False) self.notify_client() self.sender.reload_config() # ------------------------------------------------------------------------------------------ # def get_user_list(self, adminuser, adminpassword): """ Einen User löschen TODO: Passwort verschlüsselt übertragen """ # TODO admin rechte checken user löschen if self.userdb.hasAdminRights(adminuser, adminpassword): userlist = self.userdb.getUserlist() self.success('00', simplejson.dumps(userlist)) else: self.warning('01', False) self.notify_client() # ------------------------------------------------------------------------------------------ # def __metadata_format__(self, metadata): """ Private: Vereinheitlicht die Metadaten von Playlist und anderen Kanälen und entfernt Anführungszeichen in den Feldern @rtype: boolean/dict @return: False/Metadaten """ mdata = {} try: for key,val in metadata.items('root'): if key in self.knownfields: mdata[key] = val.strip().replace('"', '') return mdata except: return False # ------------------------------------------------------------------------------------------ # def __get_error__(self, errornumber): """ Privat: Ermittelt Fehlermeldung, Job-Name (Klassenmethode) und Fehlercode für den Job aus error/controller_error.js @type errornumber: string @param errornumber: Die interne Fehlernummer der aufrufenden Methode """ f = sys._getframe(2) job = f.f_code.co_name data = {'message':'', 'job':job, 'code':'unknown'} if job in self.errorData: errMsg = self.errorData[job][errornumber] errID = self.errorData[job]['id'] + str(errornumber) args = {x:f.f_locals[x] if not x == 'self' else '' for x in f.f_code.co_varnames[:f.f_code.co_argcount]} for key in args.keys(): errMsg = errMsg.replace('::' + key + '::', str(args[key])) data['message'] = errMsg data['job'] = job data['code'] = errID return data # ------------------------------------------------------------------------------------------ # def success(self, errnum='00', value='', section='main'): """ Erfolgsmeldung loggen @type errnum: string @param errnum: Errornummer der aufrufenden Funktion @type value: string @param value: Optionaler Wert @type section: string @param section: Gültigkeitsbereich """ error = self.__get_error__(errnum) self.job_result = {'message':error['message'], 'code':error['code'], 'success':'success', 'job':error['job'], 'value':value, 'section':section} self.messenger.send(error['message'], error['code'], 'success', error['job'], value, section) # ------------------------------------------------------------------------------------------ # def info(self, errnum='01', value='', section='main'): """ Info loggen @type errnum: string @param errnum: Errornummer der aufrufenden Funktion @type value: string @param value: Optionaler Wert @type section: string @param section: Gültigkeitsbereich """ error = self.__get_error__(errnum) self.job_result = {'message':error['message'], 'code':error['code'], 'success':'info', 'job':error['job'], 'value':value, 'section':section} self.messenger.send(error['message'], error['code'], 'info', error['job'], value, section) # ------------------------------------------------------------------------------------------ # def warning(self, errnum='01', value='', section='main'): """ Warnung loggen @type errnum: string @param errnum: Errornummer der aufrufenden Funktion @type value: string @param value: Optionaler Wert @type section: string @param section: Gültigkeitsbereich """ error = self.__get_error__(errnum) self.job_result = {'message':error['message'], 'code':error['code'], 'success':'warning', 'job':error['job'], 'value':value, 'section':section} self.messenger.send(error['message'], error['code'], 'warning', error['job'], value, section) # ------------------------------------------------------------------------------------------ # def error(self, errnum='01', value='', section='main'): """ Error loggen @type errnum: string @param errnum: Errornummer der aufrufenden Funktion @type value: string @param value: Optionaler Wert @type section: string @param section: Gültigkeitsbereich """ error = self.__get_error__(errnum) self.job_result = {'message':error['message'], 'code':error['code'], 'success':'error', 'job':error['job'], 'value':value, 'section':section} self.messenger.send(error['message'], error['code'], 'error', error['job'], value, section) # ------------------------------------------------------------------------------------------ # def fatal(self, errnum='01', value='', section='main'): """ Fatal error loggen @type errnum: string @param errnum: Errornummer der aufrufenden Funktion @type value: string @param value: Optionaler Wert @type section: string @param section: Gültigkeitsbereich """ error = self.__get_error__(errnum) self.job_result = {'message':error['message'], 'code':error['code'], 'success':'fatal', 'job':error['job'], 'value':value, 'section':section} self.messenger.send(error['message'], error['code'], 'fatal', error['job'], value, section) # ------------------------------------------------------------------------------------------ # def notify_client(self): """ Eine Nachricht als JSON-String an den Client senden """ if not self.is_intern: self.message(simplejson.dumps(self.job_result)) # ------------------------------------------------------------------------------------------ # def __check_result__(self, result): """ Fehlerbehandlung @type result: string @param result: Ein Json-String """ self.lq_error = simplejson.loads(result) try: if self.lq_error['success'] == 'true': return True else: return False except: return False # ------------------------------------------------------------------------------------------ # def __update_event_queue__(self, playlist): """ Playlist Eventqueue updaten @type playlist: dict @param playlist: Playlist """ # eventuell noch bestehende Events im Queue löschen self.messenger.queue_remove_events('playtrack', 'player') # Für jeden Tack einen Event ankündigen for track in playlist['playlist']['trackList']['track']: if 'time' in track and 'start' in track: starts = str(track['start'] + 'T' + track['time']) event = {'job':'play', 'location': track['location'],'length': track['length'], 'date': track['start'], 'time': track['time']} self.messenger.queue_add_event('playtrack', starts, event, 'player')