def __init__(self, sender, lqs_socket, lqs_recsocket): """ 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 """ # Der Liquidsoap Client self.lqc = LiquidsoapClient(lqs_socket) self.lqcr = LiquidsoapClient(lqs_recsocket) # Felder die Liquidsoap fuer einen Track (rid) zurueckliefert self.knownfields = ["status", "album","time","title","artist","comment","filename","on_air","source","rid", "genre"] self.lq_error = '' self.sender = sender self.is_intern = False self.messenger = CombaMessenger() self.messenger.setChannel('controller') self.messenger.setSection('execjob') self.userdb = CombaUser() self.job_result = ['', '', '', '','', ''] errors_file = os.path.dirname(os.path.realpath(__file__)) + '/error/combac_error.js' json_data = open(errors_file) self.errorData = simplejson.load(json_data)
def __init__(self, datefrom="", dateto=""): threading.Thread.__init__ (self) self.messenger = CombaMessenger() self.messenger.setChannel('helpers') self.messenger.setSection('getcalendar') self.xmlplaylist = range(0) self.loadConfig() self.messenger.setMailAddresses(self.get('frommail'), self.get('adminmail')) self.dateto = dateto self.datefrom = str(datefrom) self.until = ''
def __init__(self, CombaClient_instance): """ Constructor @type CombaClient_instance: object @param CombaClient_instance: Der Client für Liquidsoap """ self.client = CombaClient_instance # Messenger für Systemzustände initieren self.messenger = CombaMessenger() self.messenger.setChannel('monitor') self.messenger.setSection('execjob') self.playlistwatchers = [] self.watchers = [] # das pyev Loop-Object self.loop = pyev.default_loop() self.playlistwatcher_loop = pyev.Loop() # Die Signale, die Abbruch signalisieren self.stopsignals = (signal.SIGTERM, signal.SIGINT) # Das ist kein Reload self.initial = True self.config = object self.config_path = "" self.stop_time = '' self.start_time = '' self.duration = "" # Der Monitor wartet noch auf den Start Befehl self.ready = False self.block_combine = False errors_file = os.path.dirname(os.path.realpath(__file__)) + '/error/combam_error.js' json_data = open(errors_file) self.errorData = simplejson.load(json_data) self.livetime = False self.messenger.send('Monitor started', '0000', 'success', 'initApp' , None, 'appinternal')
def __init__(self, CombaClient_instance, config): """ Constructor @type CombaClient_instance: object @param CombaClient_instance: Der Client für Liquidsoap @type config: string @param config: Pfad zum scheduler.xml """ self.client = CombaClient_instance self.loadConfig() # Messenger für Systemzustände initieren self.messenger = CombaMessenger() self.messenger.setChannel('scheduler') self.messenger.setSection('execjob') self.messenger.setMailAddresses(self.get('frommail'), self.get('adminmail')) self.config_path = config self.config = object # Die Signale, die Abbruch signalisieren self.stopsignals = (signal.SIGTERM, signal.SIGINT) # das pyev Loop-Object self.loop = pyev.default_loop() # Das ist kein Reload self.initial = True # Der Scheduler wartet noch auf den Start Befehl self.ready = False # DIe Config laden self._loadConfig() self.scriptdir = os.path.dirname(os.path.abspath(__file__)) + '/..' errors_file = os.path.dirname(os.path.realpath(__file__)) + '/error/combas_error.js' json_data = open(errors_file) self.errorData = simplejson.load(json_data) self.messenger.send('Scheduler started', '0000', 'success', 'initApp', None, 'appinternal')
class CombaScheduler(CombaBase): def __init__(self, CombaClient_instance, config): """ Constructor @type CombaClient_instance: object @param CombaClient_instance: Der Client für Liquidsoap @type config: string @param config: Pfad zum scheduler.xml """ self.client = CombaClient_instance self.loadConfig() # Messenger für Systemzustände initieren self.messenger = CombaMessenger() self.messenger.setChannel('scheduler') self.messenger.setSection('execjob') self.messenger.setMailAddresses(self.get('frommail'), self.get('adminmail')) self.config_path = config self.config = object # Die Signale, die Abbruch signalisieren self.stopsignals = (signal.SIGTERM, signal.SIGINT) # das pyev Loop-Object self.loop = pyev.default_loop() # Das ist kein Reload self.initial = True # Der Scheduler wartet noch auf den Start Befehl self.ready = False # DIe Config laden self._loadConfig() self.scriptdir = os.path.dirname(os.path.abspath(__file__)) + '/..' errors_file = os.path.dirname(os.path.realpath(__file__)) + '/error/combas_error.js' json_data = open(errors_file) self.errorData = simplejson.load(json_data) self.messenger.send('Scheduler started', '0000', 'success', 'initApp', None, 'appinternal') #------------------------------------------------------------------------------------------# def set(self, key, value): """ Eine property setzen @type key: string @param key: Der Key @type value: mixed @param value: Beliebiger Wert """ self.__dict__[key] = value #------------------------------------------------------------------------------------------# def reload(self): """ Reload Scheduler - Config neu einlesen """ self.stop() # Scheduler Config neu laden if self._loadConfig(): self.messenger.send('Scheduler reloaded by user', '0500', 'success', 'reload', None, 'appinternal') self.start() #------------------------------------------------------------------------------------------# def _loadConfig(self): """ Scheduler-Config importieren @rtype: boolean @return: True/False """ # Wenn das Scheduling bereits läuft, muss der Scheduler nicht unbedingt angehalten werden error_type = 'fatal' if self.initial else 'error' watcher_jobs = self.getJobs() try: # Die Jobs aus der Config ... watcher_jobs = self.getJobs() except: self.messenger.send('Config is broken', '0301', error_type, 'loadConfig', None, 'config') if self.initial: self.ready = False return False # Fehlermeldung senden, wenn keine Jobs gefunden worden sind if len(watcher_jobs) == 0: self.messenger.send('No Jobs found in Config', '0302', error_type, 'loadConfig', None, 'config') # Der erste Watcher ist ein Signal-Watcher, der den sauberen Abbruch ermöglicht self.watchers = [pyev.Signal(sig, self.loop, self.signal_cb) for sig in self.stopsignals] # Der zweite Watcher soll das Signal zum Reload der Config ermöglicen sig_reload = self.loop.signal(signal.SIGUSR1, self.signal_reload) self.watchers.append(sig_reload) # Der dritte Watcher sendet alle 20 Sekunden ein Lebenszeichen say_alive = self.loop.timer(0, 20, self.sayAlive) self.watchers.append(say_alive) # Der vierte Watcher schaut alle 20 Sekunden nach, ob eine Vorproduktion eingespielt werden soll lookup_prearranged = self.loop.timer(0, 20, self.lookup_prearranged) self.watchers.append(lookup_prearranged) # Der fünfte Watcher führt initiale Jobs durch on_start = self.loop.timer(0, 30, self.on_start) self.watchers.append(on_start) # Nun Watcher für alle Jobs aus der Config erstellen for watcher_job in watcher_jobs: watcher = pyev.Scheduler(self.schedule_job, self.loop, self.exec_job, watcher_job) # Jeder watcher wird von der Scheduler Funktion schedule_job schedult und vom Callback exec_job ausgeführt # watcher_job wird an watcher.data übergeben # schedule_job meldet an den Loop den nächsten Zeitpunkt von watcher_job['time'] # exec_job führt die Funktion dieser Klasse aus, die von watcher_job['job'] bezeichnet wird self.watchers.append(watcher) # Es kann losgehen self.ready = True return True def getJobs(self): error_type = 'fatal' if self.initial else 'error' try: # Das scheduler.xml laden self.config = CombaSchedulerConfig(self.config_path) except: # Das scheint kein gültiges XML zu sein self.messenger.send('Config is broken', '0301', error_type, 'loadConfig', None, 'config') # Wenn das beim Start passiert können wir nix tun if self.initial: self.ready = False return False jobs = self.config.getJobs() for job in jobs: if job['job'] == 'start_recording' or job['job'] == 'play_playlist': stopjob = self._getStopJob(job) jobs.append(stopjob) return jobs # -----------------------------------------------------------------------# def _getStopJob(self, startjob): job = {} job['job'] = 'stop_playlist' if startjob['job'] == 'play_playlist' else 'stop_recording' if startjob['day'] == 'all': job['day'] = startjob['day'] else: if startjob['time'] < startjob['until']: job['day'] = startjob['day'] else: try: day = int(startjob['day']) stopday = 0 if day > 5 else day+1 job['day'] = str(stopday) except: job['day'] = 'all' job['time'] = startjob['until'] return job #------------------------------------------------------------------------------------------# def start(self): """ Event Loop starten """ # Alle watcher starten for watcher in self.watchers: watcher.start() logging.debug("{0}: started".format(self)) try: self.loop.start() except: self.messenger.send("Loop did'nt start", '0302', 'fatal', 'appstart', None, 'appinternal') else: self.messenger.send("Scheduler started", '0100', 'success', 'appstart', None, 'appinternal') #------------------------------------------------------------------------------------------# def stop(self): """ Event Loop stoppen """ self.loop.stop(pyev.EVBREAK_ALL) # alle watchers stoppen und entfernen while self.watchers: self.watchers.pop().stop() self.messenger.send("Loop stopped", '0400', 'success', 'appstart', None, 'appinternal') #------------------------------------------------------------------------------------------# def sayAlive(self, watcher, revents): """ Alle 20 Sekunden ein Lebenssignal senden @type watcher: object @param watcher: Das watcher Objekt @type revents: object @param revents: Event Callbacks """ self.messenger.sayAlive() #------------------------------------------------------------------------------------------# def signal_cb(self, loop, revents): """ Signalverarbeitung bei Abbruch @type loop: object @param loop: Das py_ev loop Objekt @type revents: object @param revents: Event Callbacks """ self.messenger.send("Received stop signal", '1100', 'success', 'appstop', None, 'appinternal') self.stop() #------------------------------------------------------------------------------------------# def signal_reload(self, loop, revents): """ Lädt Scheduling-Konfiguration neu bei Signal SIGUSR1 @type loop: object @param loop: Das py_ev loop Objekt @type revents: object @param revents: Event Callbacks """ self.messenger.send("Comba Scheduler gracefull restarted", '1200', 'success', 'appreload', None, 'appinternal') self.reload() #------------------------------------------------------------------------------------------# def schedule_job(self, watcher, now): """ Callback zum Scheduling eines Jobs @type watcher: object @param watcher: Das watcher Objekt @type now: float @param now: Aktuelle Zeit in Sekunden @rtype: float @return: Die Zeit zu der der Job ausgeführt werden soll in Sekunden """ # nächstes Ereignis dieses Watchers aus den watcher data data = watcher.data.copy() next_schedule = data['time'] # Minuten und Stunden (next_hour, next_minute) = next_schedule.split(':') # Zum Vergleich die aktuelle und die auszuführende Unhrzeit in Integer wandeln today_time = int(datetime.datetime.now().strftime('%H%M')) next_time = int(next_hour + next_minute) # Wenn der Job erst morgen ausgeführt werden soll ist day_offset 1 day_offset = 1 if (today_time >= next_time) else 0 # Ist ein Tag angegeben if data.has_key('day'): try: #Montag ist 0 dayofweek = int(data['day']) delta = relativedelta(hour=int(next_hour), minute=int(next_minute), second=0, microsecond=0, weekday=dayofweek) except: #Fallback - day ist vermutlich ein String delta = relativedelta(hour=int(next_hour), minute=int(next_minute), second=0, microsecond=0) else: delta = relativedelta(hour=int(next_hour), minute=int(next_minute), second=0, microsecond=0) # Ermittle das Datumsobjekt schedule_result = datetime.datetime.now() + timedelta(day_offset) + delta # In Sekunden umrechnen result = time.mktime(schedule_result.timetuple()) schedule_time_human = datetime.datetime.fromtimestamp(int(result)).strftime('%Y-%m-%d %H:%M:%S') time_now = datetime.datetime.fromtimestamp(time.time()).strftime('%Y-%m-%d %H:%M:%S') date_human = datetime.datetime.fromtimestamp(int(result)).strftime('%Y-%m-%d') time_human = datetime.datetime.fromtimestamp(int(result)).strftime('%H:%M') # Events feuern, zum stoppen und starten einer Playlist # TODO: Diese events müssen bei einem Reset gelöscht werden # Es sollte sicher sein, einfach alle keys mit playerevent_*_playliststart unc playerevent_*_playliststop zu löschen if data.has_key('job'): if data['job'] == 'play_playlist': event = {'job': 'play_playlist', 'date': date_human, 'time': time_human} self.messenger.queueAddEvent('playliststart', str(schedule_time_human).replace(' ', 'T'), event, 'player') if data['job'] == 'stop_playlist': event = {'job': 'stop_playlist', 'date': date_human, 'time': time_human} self.messenger.queueAddEvent('playliststop', str(schedule_time_human).replace(' ', 'T'), event, 'player') data['scheduled_at'] = time_now data['scheduled_for'] = schedule_time_human self.info('schedule_job', data, '00', simplejson.dumps(data), 'schedulejob') # das nächste mal starten wir diesen Job in result Sekunden return result #------------------------------------------------------------------------------------------# def exec_job(self, watcher, revents): """ Callback, um einen Job auszuführen @type watcher: object @param watcher: Das watcher Objekt @type revents: object @param revents: Event Callbacks """ data = watcher.data.copy() # Welcher Job ausgeführt werden soll wird in watcher.data vermerkt job = data['job'] # Job ausführen try: exec "a=self." + job + "(data)" except Exception, e: data['exec'] = 'exec"a=self.' + job + '(' + simplejson.dumps(data) + ')"' data['Exception'] = str(e) self.fatal('exec_job', data, '01', simplejson.dumps(data)) watcher.stop() #stop the watcher else:
class CombaCalendarService(threading.Thread, CombaBase): """ Holt Playlist-Daten und schreibt sie in die database """ def __init__(self, datefrom="", dateto=""): threading.Thread.__init__ (self) self.messenger = CombaMessenger() self.messenger.setChannel('helpers') self.messenger.setSection('getcalendar') self.xmlplaylist = range(0) self.loadConfig() self.messenger.setMailAddresses(self.get('frommail'), self.get('adminmail')) self.dateto = dateto self.datefrom = str(datefrom) self.until = '' #------------------------------------------------------------------------------------------# def setDateFrom(self, date): self.datefrom = str(date).replace(" ", "T") #------------------------------------------------------------------------------------------# def setDateTo(self, date): self.dateto = str(date).replace(" ", "T") #------------------------------------------------------------------------------------------# def setUntilTime(self, timestring): self.until = timestring #------------------------------------------------------------------------------------------# def setSecondsPerTrack(self, seconds): self.secondspertrack = int(seconds) #------------------------------------------------------------------------------------------# def setCalendarUrl(self, url): self.calendarurl = url #------------------------------------------------------------------------------------------# def setAudioPath(self, path): self.audiobase = path #------------------------------------------------------------------------------------------# def setAudioPath(self, path): self.audiobase = path #------------------------------------------------------------------------------------------# def setPlaylistStore(self, path): self.playlistdir = path #------------------------------------------------------------------------------------------# def getUri(self): if not self.playlistdir: return False if not self.datefrom: return False if not self._calcDateTo(): return hostname = self.get('servername'); port = self.get('serviceport'); date_from = self.datefrom[0:16] + ':00'; date_to = self.dateto[0:16] + ':00'; uri = 'http://' + hostname + ':' + port + '/playlist/' + date_from + '/' + date_to return uri #------------------------------------------------------------------------------------------# def run(self): """ Kalender-Daten abholen und in Playlist schreiben """ filestotal = 0 if not self._calcDateTo(): return self._setUrl() self._fetchData() # secondspertrack may be a string from config self.secondspertrack = int(self.secondspertrack) for show in self.data['shows']: #fix nonexisting origdatetime if not show.has_key('rerun'): continue if not show.has_key('end'): continue if not show.has_key('start'): continue if str(show['rerun']).lower() == 'true': try: show['origdate'] = show['replay_of_datetime'] except KeyError: continue else: show['origdate'] = show['start'] #TODO: message #mailnotice("Am " + datefrom[0:10] +" wurde eine Luecke im Kalender festgestellt") event = BroadcastEvent.objects(identifier=show['identifier']).first() if not event: event = BroadcastEvent() # calc duration duration = self._calcDuration(show['start'], show['end']) show['duration'] = datetime.timedelta(seconds=duration).__str__() for k in event: if show.has_key(k): event[k] = show[k] event.modified = datetime.datetime.now() event.modified_by = 'calendarservice' event.save() broadcast_event = event.to_dbref() trackstart = datetime.datetime.strptime(show['start'].replace(' ', 'T'), "%Y-%m-%dT%H:%M:%S") trackrecorded = datetime.datetime.strptime(show['origdate'].replace(' ', 'T'), "%Y-%m-%dT%H:%M:%S") # split in parts parts = duration / self.secondspertrack for i in range(0, parts): filestotal += 1 # Zu dieser Zeit muss die Aufnahme beendet werden trackDumpend = trackrecorded + datetime.timedelta(seconds=self.secondspertrack) trackPlayend = trackstart + datetime.timedelta(seconds=self.secondspertrack) identifier = show['identifier'] + ':' + str(i) track = BroadcastEventTrack.objects(identifier=identifier).first() if not track: track = BroadcastEventTrack() File = str(self.audiobase + '/').replace('//','/') + trackrecorded.strftime('%Y-%m-%d') + '/' + trackrecorded.strftime('%Y-%m-%d-%H-%M') + '.wav' track.identifier = identifier track.title = show['title'] track.ordering = i track.broadcast_event = broadcast_event track.location = 'file://' + File track.length = self.secondspertrack track.start = trackstart.strftime('%Y-%m-%d %H:%M:%S') track.end = trackPlayend.strftime('%Y-%m-%d %H:%M:%S') track.record_at = trackrecorded.strftime('%Y-%m-%d %H:%M:%S') track.save() # Events festhalten event = {'job': 'dump', 'location': File, 'length': track.length} self.messenger.queueAddEvent('dumpstart', track.record_at, event, 'recorder') event = {'job': 'dump', 'location': File, 'length': track.length} self.messenger.queueAddEvent('dumpend', trackDumpend, event, 'recorder') trackstart = trackstart + datetime.timedelta(seconds=self.secondspertrack) trackrecorded = trackrecorded + datetime.timedelta(seconds=self.secondspertrack) #------------------------------------------------------------------------------------------# def _calcDateTo(self): if self.dateto: return True if not self.until: return False if not self.datefrom: return False date_start = datetime.datetime.strptime(self.datefrom.replace('T',' '), "%Y-%m-%d %H:%M") time_start = date_start.strftime('%H:%M') day_offset = 1 if (time_start > self.until) else 0 end_date = date_start + datetime.timedelta(day_offset) self.dateto = end_date.strftime('%F') + 'T' + self.until return True #------------------------------------------------------------------------------------------# def _calcDuration(self, start, end): """ Berechnet Zeit in Sekunden aus Differenz zwischen Start und Enddatum @type start: datetime @param start: Startzeit @type end: datetime @param end: Endzeit @rtype: int @return: Zeit in Sekunden """ sec1 = int(datetime.datetime.strptime(start[0:16].replace(" ","T"),"%Y-%m-%dT%H:%M").strftime("%s")); sec2 = int(datetime.datetime.strptime(end[0:16].replace(" ","T"),"%Y-%m-%dT%H:%M").strftime("%s")); return (sec2 - sec1); #------------------------------------------------------------------------------------------# def _fetchData(self): # Now open Calendar Url try: response = urllib2.urlopen(self.dataURL) except IOError, e: self.messenger.send("Could not connect to service " + self.dataURL, '1101', 'error', 'fetchCalenderData', self._getErrorData(), 'getcalendar') #mailnotice("Verbindung zu Zappa konnte nicht hergestellt werden") #TODO: so gehts nicht sys.exit() else:
class CombaMonitor(CombaBase): # Constructor def __init__(self, CombaClient_instance): """ Constructor @type CombaClient_instance: object @param CombaClient_instance: Der Client für Liquidsoap """ self.client = CombaClient_instance # Messenger für Systemzustände initieren self.messenger = CombaMessenger() self.messenger.setChannel('monitor') self.messenger.setSection('execjob') self.playlistwatchers = [] self.watchers = [] # das pyev Loop-Object self.loop = pyev.default_loop() self.playlistwatcher_loop = pyev.Loop() # Die Signale, die Abbruch signalisieren self.stopsignals = (signal.SIGTERM, signal.SIGINT) # Das ist kein Reload self.initial = True self.config = object self.config_path = "" self.stop_time = '' self.start_time = '' self.duration = "" # Der Monitor wartet noch auf den Start Befehl self.ready = False self.block_combine = False errors_file = os.path.dirname(os.path.realpath(__file__)) + '/error/combam_error.js' json_data = open(errors_file) self.errorData = simplejson.load(json_data) self.livetime = False self.messenger.send('Monitor started', '0000', 'success', 'initApp' , None, 'appinternal') #------------------------------------------------------------------------------------------# def _loadConfig(self): """ Scheduler-Config importieren @rtype: boolean @return: True/False """ # Wenn das Scheduling bereits läuft, muss der Scheduler nicht unbedingt angehalten werden error_type = 'fatal' if self.initial else 'error' try: # Das scheduler.xml laden watcher_jobs = self.config.getJobs() except: # Das scheint kein gültiges XML zu sein self.messenger.send('Config is broken', '0301', error_type, 'loadConfig' , None, 'config') # Wenn das beim Start passiert können wir nix tun if self.initial: self.ready = False return False # Fehlermeldung senden, wenn keine Jobs gefunden worden sind if len(watcher_jobs) == 0: self.messenger.send('No Jobs found in Config', '0302', error_type, 'loadConfig' , None, 'config') # Anhand der Jobs weitere Daten ermitteln, die für interne Zwecke benötigt werden self._get_jobs_infos(watcher_jobs) # Es kann losgehen self.ready = True return True #------------------------------------------------------------------------------------------# def _get_jobs_infos(self, jobs): """ Ermittelt aus der Start- und Stopzeit der Playlist Dauer der Automatisierung und die Startzeit @type jobs: list @param jobs: Liste mit den Jobs """ self.livetime = True now = datetime.datetime.now() # Jobs nach Start und Stop-Zeit der Playlist durchsuchen for job in jobs: if job['job'] == 'play_playlist': if self.config.in_timeperiod(now,job): self.livetime = False self.start_time = job['time'] self.stop_time = job['until'] self.duration = int(job['duration']) elif job['job'] == 'start_recording' and self.config.in_timeperiod(now,job): self.start_time = job['time'] self.stop_time = job['until'] self.duration = int(job['duration']) #------------------------------------------------------------------------------------------# def set(self, key, value): """ Eine property setzen @type key: string @param key: Der Key @type value: mixed @param value: Beliebiger Wert """ self.__dict__[key] = value #------------------------------------------------------------------------------------------# def start(self): """ Monitor Loop starten """ self.config = CombaSchedulerConfig(self.config_path) #TODO: Unterscheide bei den Funktionsnamen zwischen scheduler.xml und comba.ini ## die comba.ini laden self.loadConfig() self.messenger.setMailAddresses(self.get('frommail'), self.get('adminmail')) ## die scheduler.xml laden self._loadConfig() # Der erste Watcher ist ein Signal-Watcher, der den sauberen Abbruch ermöglicht self.watchers = [pyev.Signal(sig, self.loop, self.signal_cb) for sig in self.stopsignals] self.watchers.append(self.loop.timer(0, 20, self.checkComponents)) self.watchers.append(self.loop.timer(0, 60, self.checkPlaylistEvent)) # Der dritte Watcher sendet alle 20 Sekunden ein Lebenszeichen say_alive = self.loop.timer(0, 20, self.sayAlive) #self.audiowatcher = self.loop.timer(0, 0,self.combine_audiofiles, self.audiowatcher_data) self.watchers.append(say_alive) # Alle watcher starten for watcher in self.watchers: watcher.start() logging.debug("{0}: started".format(self)) try: self.loop.start() except: self.messenger.send("Loop did'nt start", '0101', 'fatal', 'appstart' , None, 'appinternal') else: self.messenger.send("Monitor started", '0100', 'success', 'appstart' , None, 'appinternal') #------------------------------------------------------------------------------------------# def stop(self): """ Event Loop stoppen """ self.loop.stop(pyev.EVBREAK_ALL) # alle watchers stoppen und entfernen while self.watchers: self.watchers.pop().stop() self.messenger.send("Loop stopped", '0400', 'success', 'appstart' , None, 'appinternal') #------------------------------------------------------------------------------------------# def sayAlive(self, watcher, revents): """ Alle 20 Sekunden ein Lebenssignal senden @type watcher: object @param watcher: Das watcher Objekt @type revents: object @param revents: Event Callbacks """ self.messenger.sayAlive() #------------------------------------------------------------------------------------------# def signal_cb(self, loop, revents): """ Signalverarbeitung bei Abbruch @type loop: object @param loop: Das py_ev loop Objekt @type revents: object @param revents: Event Callbacks """ self.messenger.send("Received stop signal", '1100', 'success', 'appstop' , None, 'appinternal') self.stop() #------------------------------------------------------------------------------------------# def checkComponents(self, watcher, revents): """ Alle 20 Sekunden Checken, ob Komponenten noch laufen @type watcher: object @param watcher: Das watcher Objekt @type revents: object @param revents: Event Callbacks """ if not self.messenger.getAliveState('scheduler'): self.messenger.send("Scheduler is down... try to start scheduler", '0201', 'error', 'checkComponents' , None, 'appinternal') self._restartScheduler() if not self.messenger.getAliveState('controller'): self.messenger.send("Controller is down... try to start controller", '0202', 'error', 'checkComponents' , None, 'appinternal') self._restartController() if not self.messenger.getAliveState('record') or not self.messenger.getAliveState('altrecord') or not self.messenger.getAliveState('playd'): self.messenger.send("Liquidsoap components are down... try to start soundengine components", '0202', 'error', 'checkComponents' , None, 'appinternal') self._restartLiquidsoap() time.sleep(10); jobs = self.config.getJobs() self._get_jobs_infos(jobs) if self.livetime: for i in '123': if self.messenger.getAliveState('record'): self.messenger.send("Start recorder", '0204', 'info', 'checkComponents' , None, 'appinternal') start_new_thread(self._restartRecord,()) break; time.sleep(20); else: for i in '123': if self.messenger.getAliveState('playd'): self.messenger.send("Start playlist", '0205', 'info', 'checkComponents' , None, 'appinternal') start_new_thread(self._reloadPlaylist,()) break; time.sleep(20); if not self.messenger.getAliveState('archive'): self.messenger.send("Archive is down... try to start archive", '0201', 'error', 'checkComponents' , None, 'appinternal') self._restartArchive() #------------------------------------------------------------------------------------------# def checktrack(self, watcher, revents): """ Einen Track checken und ggf. den Player anhalten oder skippen @type watcher: object @param watcher: Das watcher Objekt @type revents: object @param revents: Event Callbacks """ def playlist_stop(wait): """ Interne Routine - stoppt den Player @type wait: string @param wait: Anz. Sekunden nach der der Player wieder gestartet wird """ self.client.playlist_stop() time.sleep(int(wait)) self.client.playlist_play() def playlist_skip(wait): """ Interne Routine - skippt den Player @type wait: string @param wait: Anz. Sekunden nach der der Player geskippt wird """ time.sleep(int(wait)) self.client.playlist_skip() # Watcher stoppen watcher.stop() stopforDuration = 0 skipafterDuration = 0 if not watcher.data.has_key('location'): return False # Dauer des Tracks lt. Playlist duration = int(watcher.data['length']) # Dateipfad fname = watcher.data['location'].replace('file://', '') # Wenn die Datei nicht existiert, muss der Player für die Dauer des Tracks angehalten werden if not os.path.isfile(fname): stopforDuration = str(duration) start_new_thread(playlist_stop,(stopforDuration,)) else: # Dauer der WAV-Datei ermitteln with contextlib.closing(wave.open(fname,'r')) as f: frames = f.getnframes() rate = f.getframerate() fduration = int(math.ceil(frames / float(rate))) # Ist die Dauer der WAV-Datei kleiner als die angegebene Dauer des Tracks, # muss die Playlist um die Differenz angehalten werden if fduration < duration: stopforDuration = str(duration - fduration) start_new_thread(playlist_stop,(stopforDuration,)) # Ist die Dauer der WAV-Datei größer als die angegebene Dauer des Tracks, # muss der laufende Track nach der Diffenz geskippt elif fduration > duration: skipafterDuration = str(fduration - duration) start_new_thread(playlist_skip,(skipafterDuration,)) #------------------------------------------------------------------------------------------# def checkPlaylistEvent(self, watcher, revents): """ Prüft ob der LoaPlaylist event gefeurt wurde und reagiert ggf. """ eventFired = self.messenger.getEvent('loadplaylist','player') if eventFired: eventQueue = self.messenger.getEventQueue('playtrack', 'player') if not eventQueue or not isinstance(eventQueue, list): self.messenger.send('No event queue present', '1301', 'warning', 'checkPlaylistEvent' , None, 'events') else: # stop all playlist watchers for watcher in self.playlistwatchers: watcher.stop() self.playlistwatchers = [] cnt = 0 self.playlistwatcher_loop.stop() self.playlistwatcher_loop = pyev.Loop() for index, item in enumerate(eventQueue): cnt = cnt + 10 if not item.has_key('date') or not item.has_key('time'): # funny continue # Sekunden berechnen nach der Timer gestartet wird track_time = int(datetime.datetime.strptime(item['date'][0:10] + 'T' + item['time'],"%Y-%m-%dT%H:%M").strftime("%s")) - 1 self.playlistwatchers.append(self.playlistwatcher_loop.periodic(track_time, 0.0, self.checktrack, item)) for watcher in self.playlistwatchers: watcher.start() #------------------------------------------------------------------------------------------# def combine_audiofiles(self, watcher, revents): """ Kombiniert Dateien, die in einem best. Zeitintervall entstanden sind @type watcher: object @param watcher: der watcher @type revents: object @param revents: revents - nicht verwendet """ self.block_combine = False self.messenger.send('Stop watcher', '0501', 'info', 'combine_audiofiles' , None, 'appinternal') from_time = int(watcher.data['from_time']) to_time = int(watcher.data['to_time']) watcher.stop() watcher.loop.stop() # Den Ordner bestimmen, in dem die Dateien liegen cur_folder = self.record_dir + '/' + datetime.datetime.fromtimestamp(from_time).strftime("%Y-%m-%d") + "/" uid = os.stat(cur_folder).st_uid gid = os.stat(cur_folder).st_gid # Name der Audiodatei, die angelegt werden muss out_file = datetime.datetime.fromtimestamp(from_time).strftime("%Y-%m-%d-%H-%M") + ".wav" # Alle WAV-Dateien im Ordner files = glob.glob(cur_folder + "*.wav") combine = [] if len(files) > 0: for file in files: t = int(os.path.getmtime(file)) # Liegt die Mtime im definierten Intervall? if t > from_time and t <= to_time: combine.append(file) if len(combine) > 0: audiotools = CombaAudiotools() audiotools.combine_audiofiles(combine, cur_folder, out_file, nice=19) os.chown(out_file, uid, gid); self.messenger.send("Combined to file " + out_file, '0502', 'info', 'combine_audiofiles' , None, 'appinternal') #------------------------------------------------------------------------------------------# def _load_playlist(self): """ Playlist laden und von dem Track und Zeitpunkt an starten, der aktuell laufen sollte """ self.messenger.send("Try to load playlist", '0501', 'info', 'loadPlaylist' , None, 'appinternal') # Die Playlist holen store = CombaCalendarService() # start_date ist das Datum, an dem die Playlist beginnen soll - abrunden auf den Beginn der denierten Länge für einen Track start_date = datetime.datetime.now() - datetime.timedelta(seconds=int(datetime.datetime.now().strftime('%s')) % int(self.secondspertrack)) store.setDateFrom(start_date.strftime("%Y-%m-%dT%H:%M")) store.setUntilTime(self.stop_time) store.start() uri = store.getUri() # wait until childs thread returns store.join() result = self.client.playlist_load(uri) if not self._get_data(result): self.messenger.send('Could not get Data from controller', '0504', 'fatal', 'loadPlaylist' , None, 'appinternal') else: result = self.client.playlist_play() if not self._get_data(result): self.messenger.send('Could not get Data from controller', '0505', 'fatal', 'loadPlaylist' , None, 'appinternal') else: time.sleep(0.2) unix_time_now = int(datetime.datetime.now().strftime('%s')) # berechnen die Sekunden die wir noch weiterspulen müssen seek_time = unix_time_now - int(start_date.strftime('%s')) # ... und seek self.client.playlist_seek(str(seek_time)) #------------------------------------------------------------------------------------------# def _restartScheduler(self): """ Scheduler neu starten """ os.system("service combascheduler restart") #------------------------------------------------------------------------------------------# def _restartArchive(self): """ Restart archive controller """ os.system("service combaarchive restart") #------------------------------------------------------------------------------------------# def _restartController(self): """ Controller neu starten """ os.system("service comba restart") #------------------------------------------------------------------------------------------# def _restartLiquidsoap(self): """ Player und recorder neu starten """ os.system("service comba_liq restart") #------------------------------------------------------------------------------------------# def _restartRecord(self): """ Recorder neu starten Da die Aufnahme vermutlich unterbrochen wurde, werden Audiodateien aus dem vorgesehenen Zeitraum zusammengefügt """ result = self.client.recorder_start() if self.block_combine: return self.block_combine = True now = int(datetime.datetime.now().strftime("%s")) mod = now % int(self.secondspertrack) from_time = (now - mod) to_time = from_time + int(self.secondspertrack) # combine_audiofiles wird 1 Sekunde nach Ende der Aufnahme ausgeführt time_start = float((to_time - now) +1) data = {} data['from_time'] = from_time data['to_time'] = to_time loop = pyev.Loop() #self.audiowatcher.set(0.0, time_start) watcher = loop.timer(time_start, 0, self.combine_audiofiles, data) #self.audiowatcher.set(10, 0.0) self.messenger.send("Audiofiles may be combined in " + str(watcher.remaining) + " seconds", '0601', 'info', 'restartRecord' , None, 'appinternal') watcher.start() loop.start() #------------------------------------------------------------------------------------------# def _reloadPlaylist(self): """ Playliste neu laden und starten """ self._load_playlist() #------------------------------------------------------------------------------------------# def _adminMessage(self,message): pass #------------------------------------------------------------------------------------------# def _get_data(self, result): """ @type result: dict @param result: Ein dict objekt """ if type(result) == type(str()): try: result = simplejson.loads(result) except: return False try: if result['success'] == 'success': if result.has_key('value') and result['value']: return result['value'] else: return True else: return False except: return False
class CombaController(object): # Constructor def __init__(self, sender, lqs_socket, lqs_recsocket): """ 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 """ # Der Liquidsoap Client self.lqc = LiquidsoapClient(lqs_socket) self.lqcr = LiquidsoapClient(lqs_recsocket) # Felder die Liquidsoap fuer einen Track (rid) zurueckliefert self.knownfields = ["status", "album","time","title","artist","comment","filename","on_air","source","rid", "genre"] self.lq_error = '' self.sender = sender self.is_intern = False self.messenger = CombaMessenger() self.messenger.setChannel('controller') self.messenger.setSection('execjob') self.userdb = CombaUser() self.job_result = ['', '', '', '','', ''] errors_file = os.path.dirname(os.path.realpath(__file__)) + '/error/combac_error.js' json_data = open(errors_file) self.errorData = simplejson.load(json_data) #------------------------------------------------------------------------------------------# 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 not self.is_intern: self.sender.send(message) else: return message if log: if warning: logging.warning(message) else: logging.info(message) #------------------------------------------------------------------------------------------# def allData(self): """ Gibt Metadaten aller Kanäle als JSON-String an den Client zurück @rtype: string/None @return: Die Antwort des Liquidsoap-Servers """ channels = self.sendLqcCommand(self.lqc, 'listChannels') if not isinstance(channels, list): self.warning('01') self.notifyClient() return data = {} pdata = {} try: self.is_intern = True playlist_data = simplejson.loads(self.playlist_data(True)) self.is_intern = False except: self.warning('01') self.notifyClient() return # Status des Playlistkanals abfragen status = self.sendLqcCommand(self.lqc, 'status', 'mixer', '0') states = status.split(' ') state_data = {} # Die Stati in python dicts einlesen for state in states: item = state.split('=') try: state_data[item[0]] = item[1] except: self.warning('01') self.notifyClient() return remaining = self.sendLqcCommand(self.lqc, 'playlist_remaining') state_data['remaining'] = remaining # Die Metadaten der Playlist pdata['state'] = state_data pdata['tracks'] = playlist_data data['playlist'] = pdata # Servermeldungen abschalten self.is_intern = True # die channel queues einlesen for channel in channels: data[channel] = self.channel_queue(channel, True) # Servermeldungen einschalten self.is_intern = False self.success('00', data) self.notifyClient() #------------------------------------------------------------------------------------------# def ping(self): """ dem Client antworten """ return self.message('OK') #------------------------------------------------------------------------------------------# def channel_insert(self, channel, uri, pos): """ Track in einen Channel einfuegen @type channel: string @param channel: Kanal @type uri: string @param uri: Uri - z.B. file:///my/audio/mp3 @type pos: int @param pos: Die Position an der eingefügt werden soll @rtype: string/None @return: Die Antwort des Liquidsoap-Servers """ message = self.sendLqcCommand(self.lqc, 'insert', uri, pos, channel) message = message.strip() try: if int(message) > -1: self.success() return self.message(message) except: self.warning('01') self.notifyClient() #------------------------------------------------------------------------------------------# def channel_move(self, channel, fromPos, toPos): """ Channel-Eintrag von Position fromPos nach Position toPos verschieben @type channel: string @param channel: Kanal @type fromPos: int @param fromPos: die Position des Eintrags, der verschoben wird @type toPos: int @param toPos: Zielposition @rtype: string @return: Die Antwort des Liquidsoap-Servers """ message = self.sendLqcCommand(self.lqc, 'get_queue', channel,'secondary_queue') rids = message.strip().split(' ') try: rid = rids[int(fromPos)-1] except: self.warning('01') self.notifyClient() return try: target = rids[int(toPos)-1] except: self.warning('01') self.notifyClient() return if rids[int(fromPos)-1] == rids[int(toPos)-1]: self.warning('02') self.notifyClient() return message = self.sendLqcCommand(self.lqc, 'move', rid, str(int(toPos)-1), channel) message = message.strip() if message.strip().find('OK') > -1: self.success() self.notifyClient() return else: self.warning('03') self.notifyClient() #------------------------------------------------------------------------------------------# def channel_off(self, channel): """ Channel deaktivieren @type channel: string @param channel: Kanal @rtype: string @return: Die Antwort des Liquidsoap-Servers """ # internal channel name for playlist is 'common' if channel == 'playlist': channel = 'common' channels = self.sendLqcCommand(self.lqc, 'listChannels', False) index = channels.index(channel) message = self.sendLqcCommand(self.lqc, 'deactivate', str(index)) if message.find('selected=false'): self.success() else: self.warning('01') self.notifyClient() #------------------------------------------------------------------------------------------# def channel_on(self, channel): """ Channel aktivieren @type channel: string @param channel: Kanal @rtype: string @return: Die Antwort des Liquidsoap-Servers """ # Find channels if channel == 'playlist': channel = 'common' channels = self.sendLqcCommand(self.lqc, 'listChannels', False) index = channels.index(channel) #a activate channel message = self.sendLqcCommand(self.lqc, 'activate', str(index)) if message.find('selected=true'): self.success() else: self.warning('01') self.notifyClient() #------------------------------------------------------------------------------------------# def channel_queue(self, channel, raw=False): """ Channel Queue abrufen @type channel: string @param channel: Kanal @type raw: boolean @param raw: Wenn true, Rückgabe als Python dict Object, andernfalls als JSON-String @rtype: string/dict @return: Der Channel Queue """ data = {} # queue will return request id's (rids) message = self.sendLqcCommand(self.lqc, 'get_queue', channel) rids = message.strip().split(' ') data['tracks'] = [] for rid in rids: if rid != '': # get each rids metadata metadata = self.sendLqcCommand(self.lqc, 'getMetadata', rid) track = self._metadata_format(metadata) if not track.has_key('title'): if track.has_key('location'): track['title'] = os.path.basename(track['location']) elif track.has_key('filename'): track['title'] = os.path.basename(track['filename']) else: track['title'] = 'unknown' data['tracks'].extend([track]) channels = self.sendLqcCommand(self.lqc, 'listChannels') """ now get channels state self.lqc.status: ready=false volume=100% single=false selected=false remaining=0.00 """ try: index = channels.index(channel) status = self.sendLqcCommand(self.lqc, 'status', 'mixer', str(index + 1)) states = status.split(' ') state_data = {} for state in states: item = state.split('=') if len(item) > 1: state_data[item[0]] = item[1] except: state_data = {} self.error('01') self.notifyClient() return data['state'] = state_data if raw: # return the list internal data['state'] = state_data return data else: self.success('00', data) self.notifyClient() #------------------------------------------------------------------------------------------# def channel_remove(self, channel, pos): """ Channel-Eintrag löschen @type channel: string @param channel: Kanal @type pos: int @param pos: Position des Eintrags """ # Es kann nur vom Secondary Queue gelöscht werden # Falls der Track im Primary Queue gelöscht werden soll, ist ein skip nötg message = self.sendLqcCommand(self.lqc, 'get_queue', channel, 'secondary_queue') rids = message.strip().split(' ') try: rid = rids[int(pos)-1] except: self.warning('02') self.notifyClient() return message = self.sendLqcCommand(self.lqc, 'remove', rid, channel) if message.find('OK') > -1: self.success() else: self.warning('01') self.notifyClient() #------------------------------------------------------------------------------------------# def channel_seek(self, channel, duration): """ Im aktuell spielenden Track auf dem Kanal <channel> <duration> Sekunden "vorspulen" @type channel: string @param channel: Kanal @type duration: int @param duration: Dauer in Sekunden """ # Liquidsoap Kommando data = self.sendLqcCommand(self.lqc, 'seek', duration, channel) # Resultate prüfen if self._check_result(data): self.success('00', self.lq_error['value']) else: self.warning('01') self.notifyClient() #------------------------------------------------------------------------------------------# def channel_skip(self, channel): """ Kanal skippen @type channel: string @param channel: Kanal """ # Liquidsoap Kommando channels = self.sendLqcCommand(self.lqc, 'listChannels') foundChannel = '' if not isinstance(channels, list): self.error('02') else: for index, item in enumerate(channels): if item == channel: foundChannel = self.sendLqcCommand(self.lqc, 'skip', 'mixer', str(index + 1)) break if foundChannel.strip().find('OK') > -1: self.success() elif len(channels) < 1: self.warning('01') else: self.error('03') self.notifyClient() #------------------------------------------------------------------------------------------# def channel_volume(self, channel, volume): """ Lautstärke auf Kanal <channel> setzen @type channel: string @param channel: Kanal @type volume: int @param volume: Lautstärke von 1-100 """ if channel == 'playlist': channel = 'common' # Liquidsoap Kommando channels = self.sendLqcCommand(self.lqc, 'listChannels', False) try: index = channels.index(channel) if len(channel) < 1: self.warning('02') except: self.error('03') else: message = self.sendLqcCommand(self.lqc, 'volume', str(index), str(int(volume))) if message.find('volume=' + str(volume) + '%'): self.success('01', str(volume)) else: self.warning('01') self.notifyClient() #------------------------------------------------------------------------------------------# def currentData(self): """ Metadaten des gespielten Tracks im JSON-Format Beispiel: {"title": "Deserted Cities of the Heart", "filename": "/home/michel/Nas-audio/cream/the_very_best_of/17_Deserted_Cities_of_the_Heart.mp3", "source": "ch2", "on_air": "2014/07/23 23:46:37", "rid": "2"} """ # Liquidsoap Kommando message = self.sendLqcCommand(self.lqc, 'currentTrack') rid = message.strip() metadata = self.sendLqcCommand(self.lqc, 'getMetadata', rid) data = self._metadata_format(metadata) if data: self.success('00', simplejson.dumps(data)) elif rid == '': self.warning('01') else: self.warning('02', rid) self.notifyClient() #------------------------------------------------------------------------------------------# def get_channel_state(self, channel): if channel == 'playlist': channel = 'common' channels = self.sendLqcCommand(self.lqc, 'listChannels', False) index = channels.index(channel) state_data = {} try: index = channels.index(channel) status = self.sendLqcCommand(self.lqc, 'status', 'mixer', str(index + 1)) states = status.split(' ') for state in states: item = state.split('=') if len(item) > 1: state_data[item[0]] = item[1] except: state_data = {} self.error('01') self.notifyClient() return self.success('00', simplejson.dumps(state_data)) self.notifyClient() #------------------------------------------------------------------------------------------# def help(self): """ Gibt die Hilfe aus """ errNum = '11' try: file = open(os.path.dirname(os.path.abspath(__file__)) + '/doc/comba.hlp', 'r') doc = file.read() return self.message(doc) except: self.warning('01') self.notifyClient() #------------------------------------------------------------------------------------------# def listChannels(self): """ Channels auflisten (Simple JSON) """ # Liquidsoap Kommando channels = self.sendLqcCommand(self.lqc, 'listChannels') if not isinstance(channels, list): self.error('02') elif len(channels) < 1: self.warning('01') else: self.success('00', channels) self.notifyClient() #------------------------------------------------------------------------------------------# def playlist_data(self, raw=False): """ Aktuelle Playlist Daten im JSON-Format """ # Liquidsoap Kommando data = self.sendLqcCommand(self.lqc, 'playlistData') if not raw: self.success('00', simplejson.loads(data)) self.notifyClient() else: return data #------------------------------------------------------------------------------------------# def playlist_flush(self): """ Aktuelle Playlist leeren """ data = self.sendLqcCommand(self.lqc, 'flush') self.success('00') self.notifyClient() #------------------------------------------------------------------------------------------# def playlist_insert(self, uri, pos): """ Track in die Playlist einfuegen """ data = self.sendLqcCommand(self.lqc, 'insert', uri, pos) if not self._check_result(data): self.warning('01') else: self.success('00') self.notifyClient() #------------------------------------------------------------------------------------------# def playlist_load(self, uri): """ Playlist laden @type uri: string @param uri: Uri der Playlist """ try: xml = urllib2.urlopen(uri).read().decode('utf8') except: try: xml = open(uri).read().decode('utf8') except: self.error('01', self.lq_error['message']) self.notifyClient() return (num, filename) = tempfile.mkstemp(suffix=".xspf") with codecs.open(filename, "w",encoding='utf8') as text_file: text_file.write(xml) playlist = parsexml(xml) if not isinstance(playlist, dict): self.error('02') self.notifyClient() else: self.sendLqcCommand(self.lqc, 'flush') data = self.sendLqcCommand(self.lqc, 'loadPlaylist', filename) if not self._check_result(data): self.error('01', self.lq_error['message']) else: os.remove(filename) self._updateEventQueue(playlist) event = {'job':'loadplaylist', 'uri': uri} self.messenger.fireEvent('loadplaylist', event, 'player') self.success('00') self.notifyClient() #------------------------------------------------------------------------------------------# def playlist_move(self, fromPos, toPos): """ Playlist-Eintrag von Position fromPos nach Position toPos verschieben @type fromPos: int @param fromPos: die Position des Eintrags, der verschoben wird @type toPos: int @param toPos: Zielposition """ data = self.sendLqcCommand(self.lqc, 'move', str(int(fromPos)+1), str(int(toPos)+1)) if not self._check_result(data): self.warning('01') else: self.success('00') self.notifyClient() #------------------------------------------------------------------------------------------# def playlist_pause(self): """ Playlist pausieren """ data = self.sendLqcCommand(self.lqc, 'pause') if not self._check_result(data): self.info('01') else: self.success('00') self.notifyClient() #------------------------------------------------------------------------------------------# def playlist_stop(self): """ Playlist stoppen - der Kanal wird deaktiviert """ # Kanal 0 (Playlist) deaktivieren self.sendLqcCommand(self.lqc, 'deactivate', '0') data = self.sendLqcCommand(self.lqc, 'pause') if not self._check_result(data): self.info('01') else: self.success('00') self.notifyClient() #------------------------------------------------------------------------------------------# def playlist_play(self, when='now'): """ Playlist starten @type when: string @param when: Wenn "now" werden alle anderen Kanäle deaktiviert und geskipped """ # Playlist Kanal aktivieren self.sendLqcCommand(self.lqc, 'activate', '0') if when == 'now': # immediately skip all playing channels # and activate the playlist channel channels = self.sendLqcCommand(self.lqc, 'listChannels') if not isinstance(channels, list): self.error('03') elif len(channels) < 1: self.warning('02') else: for i in xrange(len(channels)): status = self.sendLqcCommand(self.lqc, 'status', 'mixer', str(i + 1)) if "selected=true" in status: status = self.sendLqcCommand(self.lqc, 'deactivate', str(i + 1)) status = self.sendLqcCommand(self.lqc, 'skip', 'mixer', str(i + 1)) self.sendLqcCommand(self.lqc, 'activate', '0') # send the play command data = self.sendLqcCommand(self.lqc, 'play') if not self._check_result(data): self.info('01') else: self.success('00') self.notifyClient() #------------------------------------------------------------------------------------------# def playlist_push(self, uri): """ Eine Uri in die Playlist einfügen @type uri: str @param uri: Die Uri """ data = self.sendLqcCommand(self.lqc, 'push', uri) if not self._check_result(data): self.info('01') else: self.success('00') self.notifyClient() #------------------------------------------------------------------------------------------# def playlist_remove(self, pos): """ Playlist-Eintrag löschen @type pos: int @param pos: Position des Eintrags """ data = self.sendLqcCommand(self.lqc, 'remove', pos) if not self._check_result(data): self.info('01') else: self.success('00') self.notifyClient() #------------------------------------------------------------------------------------------# def playlist_seek(self, duration): """ Im aktuell spielenden Track auf dem der Playlist "vorspulen" @type duration: int @param duration: Dauer in Sekunden """ data = self.sendLqcCommand(self.lqc, 'seek', duration) # Resultate prüfen if self._check_result(data): self.success('00', self.lq_error['value']) else: self.warning('01') self.notifyClient() #------------------------------------------------------------------------------------------# def playlist_skip(self): """ Playlist skippen """ data = self.sendLqcCommand(self.lqc, 'skip') self.success('00') self.notifyClient() #------------------------------------------------------------------------------------------# def recorder_start(self): """ Recorder starten @rtype: string @return: Die Antwort des Liquidsoap-Servers """ message = self.sendLqcCommand(self.lqcr, 'start_record') if message.strip() == 'OK': self.success('00') else: self.warning('01') self.notifyClient() #------------------------------------------------------------------------------------------# def recorder_stop(self): """ Recorder stoppen """ message = self.sendLqcCommand(self.lqcr, 'stop_record') if message.strip() == 'OK': self.success('00') else: self.warning('01') self.notifyClient() #------------------------------------------------------------------------------------------# def recorder_stop(self): """ Recorder stoppen """ message = self.sendLqcCommand(self.lqcr, 'stop_record') if message.strip() == 'OK': self.success('00') else: self.warning('01') self.notifyClient() #------------------------------------------------------------------------------------------# def recorder_data(self): """ Status-Daten des Recorders Rückgabe-Beispiel: /var/audio/rec/2014-05-13/2014-05-13-22-00.wav,30 - Aufnahme von 30% der angegebenen Audiodatei """ message = self.sendLqcCommand(self.lqcr, 'recorder_data') l = message.split(',') data = {} if not isinstance(l, list): data = {'file':'', 'recorded': ''} self.warning('01') else: data['file'] = l[0] if len(l) > 1: data['recorded'] = l[1] else: data['recorded'] = '' self.success('00', data) self.notifyClient() #------------------------------------------------------------------------------------------# def scheduler_reload(self): """ Veranlasst den Scheduler zum Reload """ process = "combas.py" pids = psutil.get_pid_list() foundProcess = False for pid in pids: cmd = psutil.Process(pid).cmdline if len(cmd) > 1: processName = cmd[1] else: processName = psutil.Process(pid).name if processName.find(process) > 0: os.kill(pid,signal.SIGUSR1) foundProcess = True break if not foundProcess: return False else: return True #------------------------------------------------------------------------------------------# def scheduler_data(self): """ Scheduler Config ausliefern """ jobs = [] try: # Das scheduler.xml laden schedulerconfig = CombaSchedulerConfig(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.notifyClient() #------------------------------------------------------------------------------------------# def scheduler_store(self, adminuser, adminpassword, json): """ Scheduler Config zurückschreiben """ if not self.userdb.hasAdminRights(adminuser, adminpassword): self.warning('01', False) self.notifyClient() return try: schedulerconfig = CombaSchedulerConfig(self.sender.schedule_config) except: self.warning('02', False) self.notifyClient() try: schedulerconfig.storeJsonToXml( base64.b64decode(json)) except: self.warning('02', False) self.notifyClient() else: if self.scheduler_reload(): self.success('00', True) else: self.warning('02', False) self.notifyClient() #------------------------------------------------------------------------------------------# def setPassword(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.notifyClient() self.sender.reload() #------------------------------------------------------------------------------------------# def addUser(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.notifyClient() self.sender.reload() #------------------------------------------------------------------------------------------# def delUser(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.notifyClient() self.sender.reload() #------------------------------------------------------------------------------------------# def getUserlist(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.notifyClient() #------------------------------------------------------------------------------------------# 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 _getError(self, errornumber): """ Privat: Ermittelt Fehlermeldung, Job-Name (Klassenmethode) und Fehlercode für den Job aus error/combac_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 self.errorData.has_key(job): 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._getError(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._getError(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._getError(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._getError(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._getError(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 notifyClient(self): """ Eine Nachricht als JSON-String an den Client senden """ if not self.is_intern: self.message(simplejson.dumps(self.job_result)) #------------------------------------------------------------------------------------------# def sendLqcCommand(self, lqs_instance, command, *args): """ Ein Kommando an Liquidsoap senden @type lqs_instance: object @param lqs_instance: Instanz eines Liquidsoap-Servers - recorder oder player @type command: string @param command: Ein Funktionsname aus der Liquidsoap-Klasse @type args: list @param args: Parameterliste @rtype: string @return: Antwort der Liquidsoap-Instanz """ try: # Verbindung herstellen lqs_instance.connect() except: # Verbindung gescheitert - Fehler an Client if command.find('record') > -1: self.fatal('02') else: self.fatal('01') self.notifyClient() # Instanz/Thread zerstören - aufrufende Funktion wird nicht weiter abgearbeitet del self else: # Funktion des Liquidsoap Servers zusammenbasteln func = getattr(lqs_instance, command) result = func(*args) return 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 _updateEventQueue(self, playlist): """ Playlist Eventqueue updaten @type playlist: dict @param playlist: Playlist """ # eventuell noch bestehende Events im Queue löschen self.messenger.queueRemoveEvents('playtrack', 'player') # Für jeden Tack einen Event ankündigen for track in playlist['playlist']['trackList']['track']: if track.has_key('time') and track.has_key('start'): starts = str(track['start'] + 'T' + track['time']) event = {'job':'play', 'location': track['location'],'length': track['length'], 'date': track['start'], 'time': track['time']} self.messenger.queueAddEvent('playtrack', starts, event, 'player')