Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • dev-old
  • dev-old-david
  • develop
  • lars-tests
  • master
  • master-old
  • topic/filesystem-fallbacks
  • topic/tank_connection
  • topic/tank_connection_david
  • user/equinox/docker
10 results

Target

Select target project
  • aura/engine
  • hermannschwaerzler/engine
  • sumpfralle/aura-engine
3 results
Select Git revision
  • 122-synchronized-ci
  • feat-use-docker-main-tag
  • fix-aura-sysuser
  • fix-broken-pipe-153
  • fix-docker-release
  • fix-push-latest-with-tag
  • fix-streamchannel-retries
  • gitlab-templates
  • improve-test-coverage-137
  • improve-test-coverage-143
  • main
  • orm-less-scheduling
  • remove-mailer
  • update-changelog-alpha3
  • virtual-timeslots-131
  • 1.0.0-alpha1
  • 1.0.0-alpha2
  • 1.0.0-alpha3
  • 1.0.0-alpha4
  • 1.0.0-alpha5
20 results
Show changes
Showing
with 1848 additions and 0 deletions
File added
__author__ = 'michel'
# -*- coding: utf-8 -*-
import datetime, os, urllib, sys
import pprint
#from modules.web import db
from sqlalchemy import Boolean, Column, Date, DateTime, Float, Integer, String, Text, Time, ForeignKey
from libraries.database.database import db
class Model:
def store(self, commit=False):
db.session.add(self)
if commit:
self.commit()
def commit(self):
db.session.commit()
# ------------------------------------------------------------------------------------------ #
class TimeSlotModel(Model):
def get_length(self):
sec1 = int(datetime.datetime.strptime(self.start[0:16].replace(" ", "T"), "%Y-%m-%dT%H:%M").strftime("%s"))
sec2 = int(datetime.datetime.strptime(self.end[0:16].replace(" ", "T"), "%Y-%m-%dT%H:%M").strftime("%s"))
len = sec2 - sec1
return len
def pretty_print(self):
return pprint.pprint(self)
class TimeSlotEntryModel(Model):
# ------------------------------------------------------------------------------------------ #
@staticmethod
def upcoming(datefrom=datetime.datetime.now()):
upcomingtracks = TimeSlotEntry.query.filter(TimeSlotEntry.start > datefrom).all()
return upcomingtracks
# ------------------------------------------------------------------------------------------ #
@staticmethod
def select_all():
return TimeSlotEntry.query.filter().all()
def __str__(self):
return "TimeSlotEntry starts @ "+TimeSlotEntry.entry_start+" and ends @ "+TimeSlotEntry.entry_end+" and plays "+TimeSlotEntry.source
# ------------------------------------------------------------------------------------------ #
class TimeSlot(db.Model, TimeSlotModel):
"""
One specific TimeSlot for a show
"""
__tablename__ = 'timeslot'
timeslot_id = Column(Integer, primary_key=True)
show_id = Column(Integer)
timeslot_start = Column(DateTime, nullable=False)
timeslot_end = Column(DateTime, nullable=False)
show_name = Column(String(255))
show_hosts = Column(String(255))
is_repetition = Column(Boolean())
fallback_playlist = Column(String(255))
fallback_pool = Column(String(255))
station_fallback_pool = Column(String(255))
# entry_start = Column(DateTime, nullable=False)
# entry_end = Column(DateTime, nullable=False)
# source = Column(String(255))
def print(self):
return "TimeSlot starts @ "+self.timeslot_start+" and ends @ "+self.timeslot_end+" and plays "+self.source
class TimeSlotEntry(db.Model, TimeSlotEntryModel):
"""
One show can have multiple entries
"""
#entry_id = Column(Integer, primary_key=True, autoincrement=True)
__tablename__ = 'timeslotentry'
timeslot_id = Column(Integer, ForeignKey("timeslot.timeslot_id"))
entry_start = Column(DateTime, primary_key=True, nullable=False)
entry_end = Column(DateTime, primary_key=True, nullable=False)
source = Column(String(255))
#class Trackservice(db.Model, Model):
# """
# Trackservice and Logging
# """
# __tablename__ = 'trackservice'
# id = Column(Integer, primary_key=True)
# show_id = Column(Integer, ForeignKey("timeslot.id"))
def recreateDB():
print("Recreating Database...")
db.drop_all()
print("all dropped. creating...")
db.create_all()
print("all created. commiting...")
db.session.commit()
print("Database recreated!")
sys.exit(0)
#recreateDB()
import datetime, os, urllib
from sqlalchemy import Boolean, Column, Date, DateTime, Float, Integer, String, Text, Time, ForeignKey
from libraries.database.database import db
# ------------------------------------------------------------------------------------------ #
class BroadcastModel:
def store(self, commit=False):
db.session.add(self)
if commit:
self.commit()
def commit(self):
db.session.commit()
# ------------------------------------------------------------------------------------------ #
class Format(db.Model, BroadcastModel):
"""
ID of show format # Sendungsformat
"""
__tablename__ = 'format'
id = Column(Integer, primary_key=True)
name = Column(String(255))
# ------------------------------------------------------------------------------------------ #
class BroadcastPeriod(db.Model, BroadcastModel):
"""
Daten werden noch in der scheduling.xml gehalten
"""
__tablename__ = 'broadcast_period'
identifier = Column(Integer, primary_key=True)
start = Column(DateTime)
end = Column(DateTime)
meta = {
'ordering': ['+start']
}
# ------------------------------------------------------------------------------------------ #
class BroadcastEvent(db.Model, BroadcastModel):
"""
Definiert eine Sendung mit Start- und End-Datum
"""
__tablename__ = 'broadcast_event'
identifier = Column(Integer, primary_key=True, autoincrement=True) # Unique (o)id
id_ext = Column(String(30)) #
location = Column(String(512)) # archived audio file, will be available when the broadcast event is over
reccurrence_id = Column(Integer) # Unique id of event to be repeated
duration = Column(Integer) # duration in seconds
start = Column(DateTime, nullable=False, default=datetime.datetime.now()) # start date
end = Column(DateTime, nullable=False, default=datetime.datetime.now()+datetime.timedelta(minutes=60)) # end date
rerun = Column(Boolean, default=False) # true, if the event is a rerun
replay_of_datetime = Column(DateTime)
replay_of = Column(Integer, ForeignKey('broadcast_event.identifier'))
programme_id = Column(String(100))
station_name = Column(String(100))
station_id = Column(String(100))
title = Column(String(100))
subject = Column(String(200))
description = Column(String(300))
overwrite_event = Column(Integer, ForeignKey('broadcast_event.identifier'))
state = Column(String(50), default='created')
created = Column(DateTime, default=datetime.datetime.now)
modified = Column(DateTime, default=datetime.datetime.now)
modified_by = Column(String(50))
text = Column(String(400))
#data = DictField(required=False)
meta = {
'ordering': ['+start'],
'indexes': [
{'fields': ['title', "subject"],
'default_language': 'german',
'weight': {'title': 10, 'subject': 2}
}
]
}
def fileExists(self):
return os.path.exists(str(self.location).replace('file://', ''))
# ------------------------------------------------------------------------------------------ #
class BroadcastEventTrack(db.Model, BroadcastModel):
"""
Track, der einem BroadcastEvent zugeordnet ist
"""
__tablename__ = 'broadcast_event_track'
identifier = Column(Integer, primary_key=True) # Unique id
location = Column(String(512)) # audio location (url or file)
length = Column(Float) # duration in seconds
start = Column(DateTime) # start date
end = Column(DateTime) # end date
record_at = Column(DateTime) # The date on which the recording has to start.
broadcast_event = Column(Integer, ForeignKey('broadcast_event.identifier')) # the BroadcastEvent the track refers to
meta = {
'ordering': ['+start']
}
def isLast(self):
return self == BroadcastEventTrack.objects(broadcast_event=self.broadcast_event).order_by('-start').first()
def fileExists(self):
return os.path.exists(str(self.location).replace('file://', ''))
def totalLength(self):
tracks = BroadcastEventTrack.objects(broadcast_event=self.broadcast_event)
length = 0
for track in tracks:
length = length + track.length if track.length else length
return length#
# ------------------------------------------------------------------------------------------ #
class BroadcastEventOverride(db.Model, BroadcastModel): # Base
"""
Ein Track, der in die Sendeautomatisierung eingeblendet wird
Muss manuell erstellt werden
"""
__tablename__ = 'broadcast_event_override'
identifier = Column(Integer, primary_key=True) # Unique id
location = Column(String(512)) # audio location (url or file)
mimetype = Column(String(100)) # audio mime
bitrate = Column(Integer) # bitrate
channels = Column(Integer) # number of channels
length = Column(Float) # duration in seconds
start = Column(DateTime) # start date
end = Column(DateTime) # end date
seek = Column(Integer) # TODO: seconds to seek into the audio
ordering = Column(Integer, default=0) # track sorting
#data = DictField(required=False) # additional data
broadcast_event = Column(Integer, ForeignKey('broadcast_event.identifier')) # the BroadcastEvent the track refers to
meta = {
'ordering': ['+ordering']
}
def nextOrdering(self):
"""
Return number of next track in tracklist
@return: int ordering
"""
lastOverride = BroadcastEventOverride.objects(broadcast_event=self.broadcast_event)\
.order_by('-ordering')\
.first()
if lastOverride:
return lastOverride.ordering + 1
else:
return 1
def filled(self):
"""
what percent of the broadcast event is allready filled with audio
@return: percent filled with audio
"""
object = self.broadcast_event
parent_total = (object.end - object.start).total_seconds()
proc = 0
try:
proc = (self.totalLength() / parent_total) * 100
except:
proc = 0
return int(proc)
def totalLength(self):
"""
Get the total length of audio in the track list
@return: total length of audio
"""
tracks = BroadcastEventOverride.objects(broadcast_event=self.broadcast_event)
length = 0
for track in tracks:
if track.length:
length = length + track.length
return length
def fileExists(self):
request = urllib.Request(self.location)
request.get_method = lambda: 'HEAD'
try:
response = urllib.urlopen(request)
return True
except:
return False
from sqlalchemy.ext.declarative import declarative_base
from libraries.base.config import ConfigReader
from flask_sqlalchemy import SQLAlchemy
from flask_babel import Babel, get_locale
from flask import Flask
Base = declarative_base()
#### DATABASE CONN ####
config = ConfigReader()
config.loadConfig()
install_dir = config.get(str("install_dir"))
app = Flask(__name__, template_folder=install_dir+'/modules/web/templates')
app.config["SQLALCHEMY_DATABASE_URI"] = "mysql://comba:comba@localhost/comba?charset=utf8"
app.config['BABEL_DEFAULT_LOCALE'] = 'de'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.debug = True
db = SQLAlchemy(app)
babel = Babel(app)
#### END DATABASE CONN ####
\ No newline at end of file
File added
File added
File added
__author__ = 'michel'
import os
class AuraMailer():
def __init__(self, adminMails, fromMail):
self.adminMails = adminMails
self.fromMail = fromMail
def sendAdminMail(self, subject, body):
adminMails = self.adminMails.split()
for mailTo in adminMails:
self.send(self.fromMail, mailTo, subject, body)
def send(self, mailFrom, mailTo, subject, body):
sendmail_location = "/usr/sbin/sendmail"
p = os.popen("%s -t" % sendmail_location, "w")
p.write("From: %s\n" % mailFrom)
p.write("To: %s\n" % mailTo)
p.write("Subject: " + subject + "\n")
p.write("\n") # blank line separating headers from body
p.write(body)
status = p.close()
return status
\ No newline at end of file
# -*- coding: utf-8 -*-
import datetime
import time
import logging
from libraries.reporting.statestore import RedisStateStore
from libraries.reporting.mail import AuraMailer
"""
Meldungen an den StateStore schicken
"""
class AuraMessenger():
def __init__(self):
"""
Constructor
"""
self.channel = 'main'
self.section = ''
self.rstore = RedisStateStore()
self.errnr = '00'
self.components = {'controller':'01', 'scheduling':'02', 'playd':'03', 'recorder':'04', 'helpers':'09'}
self.fromMail = ''
self.adminMails = ''
# ------------------------------------------------------------------------------------------ #
def setChannel(self, channel):
"""
Einen "Kanal" setzen - zb scheduling
@type channel: string
@param channel: Kanal/Name der Komponente
"""
self.channel = channel
if channel in self.components:
self.errnr = self.components[channel]
self.rstore.setChannel(channel)
# ------------------------------------------------------------------------------------------ #
def setSection(self, section):
"""
Einen Sektion / Gültigkeitsbereich der Meldung setzen - zb internal
@type section: string
@param section: Gültigkeitsbereich
"""
self.section = section
# ------------------------------------------------------------------------------------------ #
def setMailAddresses(self, fromMail, adminMails):
"""
Einen Sektion / Gültigkeitsbereich der Meldung setzen - zb internal
@type section: string
@param section: Gültigkeitsbereich
"""
self.fromMail = fromMail
self.adminMails = adminMails
# ------------------------------------------------------------------------------------------ #
def send(self, message, code, level, job, value='', section=''):
"""
Eine Message senden
@type message: string
@param message: menschenverständliche Nachricht
@type code: string
@param code: Fehlercode - endet mit 00 bei Erfolg
@type level: string
@param level: Error-Level - info, warning, error, fatal
@type job: string
@param job: Name der ausgeführten Funktion
@type value: string
@param value: Ein Wert
@type section: string
@param section: Globale Sektion überschreiben
"""
section = self.section if section == '' else section
self.time = str(datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%S:%f'))
self.utime = time.time()
state = {'message':message.strip().replace("'","\\'"), 'code':self.errnr + str(code),'job':job,'value':value}
self.rstore.setSection(section)
self.rstore.store(level, state)
###TODO: hier kann auch was zu redis gepostet werden
if level == 'info' or level == 'success':
logging.info(message)
elif level == 'warning':
logging.warning(message)
elif level == 'error':
logging.error(message)
self.sendAdminmail(level, message, state)
elif level == 'fatal':
logging.critical(message)
self.sendAdminmail(level, message, state)
# ------------------------------------------------------------------------------------------ #
def sayAlive(self):
"""
Soll alle 20 Sekunden von den Komponenten ausgeführt werden,
um zu melden, dass sie am Leben sind
"""
self.rstore.setAliveState()
# ------------------------------------------------------------------------------------------ #
def getAliveState(self, channel):
"""
Live State abfragen
@type channel: string
@param channel: Channel/Komponente
"""
return self.rstore.getAliveState(channel)
# ------------------------------------------------------------------------------------------ #
def setState(self, name, value, expires=None, channel=None):
"""
Kündigt einen Event an
@type name: string
@param name: Name des state
@type value: string
@param value: Wert
@type channel: string
@param channel: Kanal (optional)
"""
if not channel:
channel = self.channel
self.rstore.setState(name, value, expires, channel)
# ------------------------------------------------------------------------------------------ #
def queueAddEvent(self, name, eventtime, value, channel=None):
"""
Kündigt einen Event an
@type name: string
@param name: der Name des Events
@type eventtime: string|datetime.datetime
@param eventtime: Datum und Zeit des events
@type value: dict
@param value: Werte
@type channel: string
@param channel: Kanal (optional)
"""
if not channel:
channel = self.channel
if type(eventtime) == type(str()):
eventtime_str = datetime.datetime.strptime(eventtime[0:16].replace(' ','T'), "%Y-%m-%dT%H:%M").strftime("%Y-%m-%dT%H:%M")
elif type(eventtime) is datetime.datetime:
eventtime_str = eventtime.strftime("%Y-%m-%dT%H:%M")
else:
raise TypeError('eventtime must be a datetime.date or a string, not a %s' % type(eventtime))
self.rstore.queueAddEvent(eventtime_str, name, value, channel)
# ------------------------------------------------------------------------------------------ #
def queueRemoveEvents(self, name, channel=None):
"""
Löscht Events
@type name: string
@param name: der Name des Events
@type channel: string
@param channel: Kanal (optional)
"""
if not channel:
channel = self.channel
self.rstore.queueRemoveEvents(name, channel)
# ------------------------------------------------------------------------------------------ #
def fireEvent(self, name, value, channel=None):
"""
Feuert einen Event
@type name: string
@param name: der Name des Events
@type value: dict
@param value: Werte
@type channel: string
@param channel: Kanal (optional)
"""
if not channel:
channel = self.channel
self.rstore.fireEvent(name, value, channel)
# ------------------------------------------------------------------------------------------ #
def getEventQueue(self, name=None, channel=None):
"""
Holt events eines Kanals
@type channel: string
@param channel: Kanal (optional)
@rtype: list
@return: Liste der Events
"""
queue = self.rstore.getEventQueue(name, channel)
return queue
# ------------------------------------------------------------------------------------------ #
def getEvents(self, name=None, channel=None):
"""
Holt events eines Kanals
@type channel: string
@param channel: Kanal (optional)
@rtype: list
@return: Liste der Events
"""
events = self.rstore.getEvents(name, channel)
return events
# ------------------------------------------------------------------------------------------ #
def getEvent(self, name=None, channel=None):
"""
Holt event eines Kanals
@type channel: string
@param channel: Kanal (optional)
@rtype: dict
@return: Event
"""
events = self.rstore.getEvents(name, channel)
result = False
if events:
result = events.pop(0)
return result
# ------------------------------------------------------------------------------------------ #
def sendAdminmail(self, level, message, state):
"""
Sendent mail an Admin(s),
@type message: string
@param message: Die Message
@type state: dict
@param state: Der State
@return result
"""
if self.fromMail and self.adminMails:
subject = "Possible comba problem on job " + state['job'] + " - " + level
mailmessage = "Hi Admin,\n comba reports a possible problem\n\n"
mailmessage = mailmessage + level + "!\n"
mailmessage = mailmessage + message + "\n\n"
mailmessage = mailmessage + "Additional information:\n"
mailmessage = mailmessage + "##################################################\n"
mailmessage = mailmessage + "Job:\t" + state['job'] + "\n"
mailmessage = mailmessage + "Code:\t" + state['code'] + "\n"
mailmessage = mailmessage + "Value:\t" + str(state['value']) + "\n"
mailer = AuraMailer(self.adminMails, self.fromMail)
mailer.sendAdminMail(subject,mailmessage)
else:
return False
# ------------------------------------------------------------------------------------------ #
def receive(self):
"""
Bisher wird nichts empfangen
"""
return ""
# -*- coding: utf-8 -*-
import redis
import time
import datetime
import json
import re
import uuid
class RedisStateStore(object):
"""Store and get Reports from redis"""
def __init__(self, **redis_kwargs):
"""The default connection parameters are: host='localhost', port=6379, db=0"""
self.db= redis.Redis()
self.channel = '*'
self.section = '*'
self.separator = '_'
self.daily = False
# ------------------------------------------------------------------------------------------ #
def setChannel(self, channel):
"""
Kanal setzen
@type channel: string
@param channel: Kanal
"""
self.channel = channel
# ------------------------------------------------------------------------------------------ #
def setSection(self, section):
"""
Sektion setzen
@type section: string
@param section: Sektion
"""
self.section = section
# ------------------------------------------------------------------------------------------ #
def setAliveState(self):
"""
Alive Funktion - alle 20 Sekunden melden, dass man noch am Leben ist
"""
self.setState('alive', 'Hi', 21)
# ------------------------------------------------------------------------------------------ #
def getAliveState(self, channel):
"""
Alive Status eines Channels ermitteln
@type channel: string
@param channel: der Channel
@rtype: string/None
@return: Ein String, oder None, bei negativem Ergebnis
"""
return self.getState('alive', channel)
# ------------------------------------------------------------------------------------------ #
def setState(self, name, value, expires=None, channel=None):
"""
Setzt einen Status
@type name: string
@param name: Name des state
@type value: string
@param value: Wert
@type channel: string
@param channel: Kanal (optional)
"""
if not channel:
channel = self.channel
key = self._createKey(channel + 'State', name)
if value == "":
self.db.delete(key)
else:
# publish on channel
message = json.dumps({'eventname':name, 'value': value})
self.db.publish(channel + 'Publish', message)
# store in database
self.db.set(key, value)
if(expires):
self.db.expire(key, 21)
# ------------------------------------------------------------------------------------------ #
def getState(self, name, channel):
"""
Holt einen Status
@type name: string
@param name: Name des state
@type channel: string
@param channel: Kanal (optional)
"""
key = self._createKey(channel + 'State', name)
return self.db.get(key)
# ------------------------------------------------------------------------------------------ #
def queueAddEvent(self, eventtime, name, value, channel=None):
"""
Kündigt einen Event an
@type eventtime: string
@param eventtime: Datum und Zeit des events
@type name: string
@param name: Name des Events
@type value: dict
@param value: Werte
@type channel: string
@param channel: Kanal (optional)
"""
timeevent = datetime.datetime.strptime(eventtime[0:16],"%Y-%m-%dT%H:%M")
expire = int(time.mktime(timeevent.timetuple()) - time.time()) + 60
self._setEvent(name, eventtime, value, 'Evqueue', 'evqueue', expire, channel)
# ------------------------------------------------------------------------------------------ #
def queueRemoveEvents(self, name=None, channel=None):
"""
Löscht Events
@type name: string
@param name: Name des Events
@type channel: string
@param channel: Kanal (optional)
"""
query = channel + 'Evqueue_' if channel else '*Evqueue_'
query = query + '*_' + name if name else query + '*_*'
keys = self.db.keys(query)
for delkey in keys:
self.db.delete(delkey)
# ------------------------------------------------------------------------------------------ #
def fireEvent(self, name, value, channel=None):
"""
Feuert einen Event
@type name: string
@param name: Name des Events
@type value: dict
@param value: Werte
@type channel: string
@param channel: Kanal (optional)
"""
eventtime = datetime.datetime.now().strftime("%Y-%m-%dT%H:%M")
self._setEvent(name, eventtime, value, 'Event', 'events', 60, channel)
# ------------------------------------------------------------------------------------------ #
def _setEvent(self, name, eventtime, value, type, namespace, expire, channel=None):
"""
Feuert einen Event
@type eventtime: string
@param eventtime: Datum und Zeit des events
@type value: dict
@param value: Werte
@type channel: string
@param channel: Kanal (optional)
"""
if not channel:
channel = self.channel
timeevent = datetime.datetime.strptime(eventtime[0:16],"%Y-%m-%dT%H:%M")
key = self._createKey(channel + type, eventtime, name)
value['starts'] = eventtime[0:16]
value['eventchannel'] = channel
value['eventname'] = name
self.db.hset(key, namespace, value)
self.db.expire(key, expire)
# ------------------------------------------------------------------------------------------ #
def getEventQueue(self, name=None, channel=None):
"""
Holt events eines Kanals
@type channel: string
@param channel: Kanal (optional)
@rtype: list
@return: Liste der Events
"""
query = channel + 'Evqueue_' if channel else '*Evqueue_'
query = query + '*_' + name if name else query + '*_*'
keys = self.db.keys(query)
keys.sort()
entries = self._getEntries(keys, 'evqueue')
return entries
# ------------------------------------------------------------------------------------------ #
def getEvents(self, name=None, channel=None):
"""
Holt events eines Kanals
@type channel: string
@param channel: Kanal (optional)
@rtype: list
@return: Liste der Events
"""
query = channel + 'Event_' if channel else '*Event_'
query = query + '*_' + name if name else query + '*_*'
keys = self.db.keys(query)
keys.sort()
entries = self._getEntries(keys, 'events')
return entries
# ------------------------------------------------------------------------------------------ #
def getNextEvent(self, name=None, channel=None):
"""
Holt den aktuellsten Event
@type channel: string
@param channel: Kanal (optional)
@rtype: dict/boolean
@return: ein Event oder False
"""
events = self.getEventQueue(name, channel)
if len(events) > 0:
result = events.pop(0)
else:
result = False
return result
# ------------------------------------------------------------------------------------------ #
def store(self, level, value):
"""
Hash speichern
@type level: string
@param level: der errorlevel
@type value: dict
@param value: Werte als dict
"""
microtime = str(time.time())
value['microtime'] = microtime
value['level'] = level
key = self._createKey(self.channel, self.section, level, microtime, str(uuid.uuid1()))
self.db.hset(key, self.channel, value)
self.db.expire(key, 864000)
# ------------------------------------------------------------------------------------------ #
def _getKeys(self, level = '*'):
"""
Redis-Keys nach Suchkriterium ermitteln
@type level: string
@param level: einen Errorlevel filtern
@rtype: list
@return: Die Keys auf die das Suchkriterium zutrifft
"""
key = self._createKey(self.channel, self.section, level)
microtime = str(time.time())
search = microtime[0:4] + '*' if self.daily else '*'
return self.db.keys(key + self.separator + '*')
# ------------------------------------------------------------------------------------------ #
def _createKey(self, *args):
"""
Key erschaffen - beliebig viele Argumente
@rtype: string
@return: Der key
"""
return self.separator.join(args)
def getEntries(self, level = '*'):
"""
Liste von Hashs nach Suchkriterium erhalten
@type level: string
@param level: einen Errorlevel filtern
@rtype: list
@return: Redis Hashs
"""
def tsort(x,y):
if float(x.split('_',4)[3]) > float(y.split('_',4)[3]):
return 1
elif float(x.split('_',4)[3]) < float(y.split('_',4)[3]):
return -1
else:
return 0
keys = self._getKeys(level)
keys.sort(tsort)
entries = self._getEntries(keys, self.channel)
entries = sorted(entries, key=lambda k: k['microtime'], reverse=True)
return entries
# ------------------------------------------------------------------------------------------ #
def _getEntries(self, keys, channel):
entries = []
for key in keys:
entry = self.db.hget(key,channel)
entry = json.dumps(entry.decode('utf-8'))
if not (entry is None):
try:
entry = entry.decode('utf-8').replace('None','"None"')
entry = re.sub("########[^]]*########", lambda x:x.group(0).replace('\"','').replace('\'',''),entry.replace("\\\"","########").replace("\\'","++++++++").replace("'",'"').replace('u"','"').replace('"{','{').replace('}"','}')).replace("########","\"")
entry = json.loads(entry)
entry['key'] = key
entries.append(entry)
except:
pass
return entries
File added
File added
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# user.py
#
"""
Aura User Class - Benutzerverwaltung
"""
import os
import sys
import redis
import random
import string
## current directory
scriptdir = os.path.dirname(os.path.abspath(__file__))
# Hier stecken unsere eigenen Python libraries
package_dir = os.path.join(scriptdir, 'python')
path = list(sys.path)
# Das package_dir zu den Systempfaden hinzufügen, damit wir Importe durchführen können
sys.path.insert(0, package_dir)
"""
User verwalten
"""
class AuraUser(object):
def __init__(self):
self.db = redis.Redis()
self.dbname = 'aurausers'
self.userprefix = 'aurauser:'
pass
# ------------------------------------------------------------------------------------------ #
def delete(self, username):
"""
Delete an user
:param username:
:return: boolean
"""
userid = self.db.hget(self.dbname,username).decode("utf-8")
if not userid:
return False
else:
self.db.delete(self.userprefix + userid)
self.db.hdel(self.dbname,username)
return True
# ------------------------------------------------------------------------------------------ #
def setPassword(self,username, password):
"""
Set users password
:param username: string
:param password: string
:return: boolean
"""
userid = self.db.hget(self.dbname,username).decode("utf-8")
if not userid:
return False
self.db.hset(self.userprefix + userid, 'password', password)
return True
# ------------------------------------------------------------------------------------------ #
def hasRole(self, username, role):
"""
Compare users role
:param username: string
:param role: string
:return:boolean
"""
userid = self.db.hget(self.dbname,username).decode("utf-8")
dbrole = self.db.hget(self.userprefix + str(userid), 'role').decode("utf-8")
if(dbrole == "admin"):
return True
print("username: " + username + " - userid: " + userid + " - role: " + role + " - dbrole: " + dbrole)
return (dbrole == role)
# ------------------------------------------------------------------------------------------ #
def hasPassword(self,username, password):
"""
Compare users password with the given one
:param username: string
:param password: string
:return:
"""
userid = self.db.hget(self.dbname,username).decode("utf-8")
dbpassword = self.db.hget(self.userprefix + userid, 'password').decode("utf-8")
print("username: "+username+" - userid: "+userid+" - password: "+password+" - dbpassword: "+dbpassword)
print(dbpassword == password)
return (dbpassword == password)
# ------------------------------------------------------------------------------------------ #
def hasAdminRights(self, username, password):
"""
Check admin rights
:param username: username
:param password: password
:return:
"""
return (self.hasPassword(username,password) and self.hasRole(username, 'admin'))
# ------------------------------------------------------------------------------------------ #
def insertUser(self, username, password, role="user"):
"""
Insert or update user
:param username: string
:param password: string
:param role: string
:return: string - the password
"""
userid = self.db.hget(self.dbname,username).decode("utf-8")
if not userid:
userid = self.db.incr("next_aurauser_id")
self.db.hset(self.dbname,username,userid)
self.db.hmset(self.userprefix + userid, {"username" : username,"password" :password, "role" : role})
return password
# ------------------------------------------------------------------------------------------ #
def getUser(self, username):
"""
Get users data
:param username: string
:return: dict - userdata
"""
userid = self.db.hget(self.dbname,username).decode("utf-8")
return self.db.hgetall(self.userprefix + userid)
# ------------------------------------------------------------------------------------------ #
def getUserlist(self):
"""
get all users
:return: list - the userlist
"""
accounts=[]
keys = self.db.keys(self.userprefix + "*")
for key in keys:
accounts.append(self.db.hgetall(key))
return accounts
# ------------------------------------------------------------------------------------------ #
def getLogins(self):
"""
get usernames passwords as dict in format {username1:password1, username2;password2, ...}
:return:
"""
accounts={}
keys = self.db.keys(self.userprefix + "*")
for key in keys:
account = self.db.hgetall(key)
try:
accounts[account['username']] = account['password']
except:
pass
return accounts
# ------------------------------------------------------------------------------------------ #
def createPassword(self):
"""
create a new passoword
:return: string - the password
"""
password = ''.join(random.sample(string.lowercase+string.uppercase+string.digits,14))
return password
\ No newline at end of file
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# aurawhitelist
#
"""
Aura Whitelist - IP-Adressen oder Domains aus der Whitelist holen
"""
import os
import sys
import redis
"""
Whitelisting ips or hostnames
"""
class AuraWhitelist(object):
def __init__(self):
self.db = redis.Redis()
self.dbname = 'aurawhitelist'
pass
# ------------------------------------------------------------------------------------------ #
def getList(self):
"""
get the whitelist
:return: list - list of whitelisted ip's
"""
return self.db.lrange(self.dbname, 0, -1)
# ------------------------------------------------------------------------------------------ #
def add(self,address):
"""
Add ip/host to whitelist
:param address: string - ip or hostname
:return: boolean
"""
list = self.getList()
for item in list:
if item == address:
return False
self.db.lpush(self.dbname, address)
return True
# ------------------------------------------------------------------------------------------ #
def remove(self,address):
"""
Remove an ip or host from whitelist
:param address: string - ip or hostname
:return: boolean
"""
if not address:
return False
self.db.lrem(self.dbname, address, 1)
return True
#!/usr/bin/python
# -*- coding: utf-8 -*-
import os
import sys
import time
import socket
import urllib.parse
import configparser
from io import StringIO
class LQConnectionError(Exception):
def __init__(self, value):
self.value = value
def __str__(self):
return repr(self.value)
""" LiquidsoapClient Class
Kommandos an den Liquidsoap Soundserver oder Recorder senden
Repräsentiert alle Kommandos, die Soundserver oder Recorder kennen
"""
class LiquidSoapClient():
def __init__(self, socketPath):
"""
Constructor
@type socketPath: string
@param socketPath: Der Pfad zum Socket des Liquidsoap-Scripts
"""
self.socketpath = socketPath
# init
self.connected = False
self.can_connect = True
self.message = ''
self.client = None
if sys.version_info <= (3, 2):
self.metareader = configparser.ConfigParser({'strict': False, 'interpolation': None})
else:
self.metareader = configparser.ConfigParser()
# ------------------------------------------------------------------------------------------ #
def connect(self):
"""
Verbindung herstellen
"""
try:
self.client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
self.client.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.client.connect(self.socketpath)
except socket.error as e:
self.connected = False
raise LQConnectionError(e)
return False
else:
self.can_connect = True
self.connected = True
return True
# ------------------------------------------------------------------------------------------ #
def isConnected(self):
return self.connected
# ------------------------------------------------------------------------------------------ #
def write(self, data):
"""
Auf den Socket schreiben
@type data: string
@param data: Der String der gesendet wird
"""
if self.connected:
self.client.sendall(data.decode("UTF-8"))
# ------------------------------------------------------------------------------------------ #
def read_all(self, timeout=2):
"""
Vom Socket lesen, bis dieser "END" sendet
@type timeout: int
@param timeout: Ein optionales Timeout
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
# make socket non blocking
# self.client.setblocking(0)
data = '';
self.client.settimeout(2)
# recv something
# while True:
try:
time.sleep(0.1)
raw = self.client.recv(8192)
data = raw.decode()
except Exception as e:
print(e)
pass
return data
# ------------------------------------------------------------------------------------------ #
def read(self):
"""
Vom Socket lesen und anschließend quit senden, um den Server zu veranlassen, die Verbindung schließen
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
if self.connected:
ret = self.read_all()
ret = ret.splitlines()
try:
ret.pop() # pop 'END' out
self.message = str.join('\n', ret)
# self.message = str(b'\n'.join(ret))
except Exception as e:
print(e)
# self.client.sendall('quit\n')
return self.message
# ------------------------------------------------------------------------------------------ #
def close(self):
"""
Quit senden und Verbindung schließen
"""
if self.connected:
message = "quit\r"
self.client.sendall(message.decode("UTF-8"))
self.client.close()
self.connected = False
# ------------------------------------------------------------------------------------------ #
def command(self, namespace, command, param=""):
"""
Kommando an Liquidosap senden
@type command: string
@param command: Kommando
@type namespace: string
@param namespace: Namespace/Kanal der angesprochen wird
@type param: mixed
@param param: ein optionaler Parameter
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
param = (param.strip() if param.strip() == "" else " " + urllib.parse.unquote(param.strip()))
if self.connected:
# print namespace + '.' + command + param + "\n"
if namespace is "":
message = str(command) + str(param) + str('\n')
else:
message = str(namespace) + str('.') + str(command) + str(param) + str('\n')
try:
debug = False
if debug:
print("command: " + command)
print("namespace: " + namespace)
print("param: " + param)
print("wanna send: " + message)
self.client.sendall(message.encode())
self.read()
# self.client.close()
except:
print("Unexpected error:", sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2])
raise
return self.message
else:
return False
# ------------------------------------------------------------------------------------------ #
def simplecommand(self, command):
"""
Parameterloses Kommando ohne Namespace senden
@type command: string
@param command: Kommando
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
if self.connected:
message = str(command) + '\n'
self.client.sendall(message.decode("UTF-8"))
self.read()
# self.client.close()
return self.message
# ------------------------------------------------------------------------------------------ #
def getMetadata(self, rid):
"""
Parameterloses Kommando ohne Namespace senden
@type rid: string/int
@param rid: Die ID eines Requests
@rtype: dict
@return: Die Metadaten als dict
"""
meta = self.command('metadata ' + str(rid), 'request')
meta = '[root]\n' + meta
if sys.version_info <= (3, 2):
meta = StringIO.StringIO(meta)
try:
self.metareader.readfp(meta)
except configparser.ParsingError:
return False
else:
try:
self.metareader.read_string(meta)
except configparser.ParsingError:
return False
return self.metareader
# ------------------------------------------------------------------------------------------ #
def skip(self, namespace="playlist", pos=""):
"""
Source skippen
@type namespace: string
@param namespace: Namespace der Source
@type pos: string
@param pos: Die Position - optional - Position des Channels vom Mixer benötigt
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
self.command('skip', namespace, pos)
return self.message
# ------------------------------------------------------------------------------------------ #
def remove(self, pos, namespace="playlist"):
"""
Track aus der secondary_queue oder der Playlist entfernen
@type pos: string
@param pos: Die Position
@type namespace: string
@param namespace: Namespace der Source
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
self.command('remove', namespace, str(pos))
return self.message
# ------------------------------------------------------------------------------------------ #
def insert(self, uri, pos='0', namespace="playlist"):
"""
Track einfügen
@type uri: string
@param uri: Uri einer Audiodatei
@type pos: string
@param pos: Die Position
@type namespace: string
@param namespace: Namespace der Source
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
self.command('insert', namespace, str(pos) + ' ' + uri)
return self.message
# ------------------------------------------------------------------------------------------ #
def move(self, fromPos, toPos, namespace="playlist"):
"""
Track von Position fromPos nach Position toPos verschieben
@type fromPos: string/int
@param fromPos: Position des zu verschiebenden Tracks
@type toPos: string
@param toPos: Die Position zu der verschoben werden soll
@type namespace: string
@param namespace: Namespace der Source
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
self.command('move', namespace, str(fromPos) + ' ' + str(toPos))
return self.message
# ------------------------------------------------------------------------------------------ #
def play(self, namespace="playlist"):
"""
Source abspielen - funktioniert nur bei Playlist
@type namespace: string
@param namespace: Namespace der Source
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
self.command('play', namespace)
return self.message
# ------------------------------------------------------------------------------------------ #
def pause(self, namespace="playlist"):
"""
Source pausieren/stoppen - funktioniert nur bei Playlist
@type namespace: string
@param namespace: Namespace der Source
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
self.command('pause', namespace)
return self.message
# ------------------------------------------------------------------------------------------ #
def flush(self, namespace="playlist"):
"""
Playlist leeren
@type namespace: string
@param namespace: Namespace der Source
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
self.command('flush', namespace)
return self.message
# ------------------------------------------------------------------------------------------ #
def push(self, uri, namespace="playlist"):
"""
Track einfügen und abspielen (wenn source aktiv ist)
@type uri: string
@param uri: Uri eines Audios
@type namespace: string
@param namespace: Namespace der Source
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
self.command('push', namespace, str(uri))
return self.message
# ------------------------------------------------------------------------------------------ #
def playlistData(self):
"""
Metadaten der Playlist ausgeben
@rtype: string
@return: Ein Json-String
"""
self.command('data', 'playlist')
return self.message
# ------------------------------------------------------------------------------------------ #
def seek(self, duration, namespace="playlist"):
"""
Aktuell laufenen Track des Kanals vorspulen
@type duration: string/int
@param duration: Dauer in Sekunden
@type namespace: string
@param namespace: Namespace der Source
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
self.command('seek', namespace, str(duration))
return self.message
# ------------------------------------------------------------------------------------------ #
def get_queue(self, namespace="ch1", queue='queue'):
"""
Queue eines Kanals ausgeben
@type namespace: string
@param namespace: Namespace der Source
@type queue: string
@param queue: Name des queues (queue, primary_queue, secondary_queue)
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
self.command(queue, namespace)
return self.message
# ------------------------------------------------------------------------------------------ #
def loadPlaylist(self, uri, params="", namespace="playlist"):
"""
Playlist laden
@type uri: string
@param uri: Uri einer Playlist im XSPF-Format
@type params: string
@param params: obsolete
@type namespace: string
@param namespace: Namespace der Source - hier nur playlist
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
self.command('load', namespace, uri + params)
return self.message
# ------------------------------------------------------------------------------------------ #
def currentTrack(self, namespace="request"):
"""
Das oder die ID(s) der gerade abgespielten requests erhalten
@type namespace: string
@param namespace: Namespace der Source
@rtype: string
@return: Die Antwort des Liquidsoap-Servers (als String)
"""
self.command('on_air', namespace)
return self.message
# ------------------------------------------------------------------------------------------ #
def volume(self, pos, volume, namespace="mixer"):
"""
Lautstärke eines Kanals setzen
@type pos: int/string
@param pos: Die Position/ Nummer des Kanals (playlist=0)
@type volume: int/string
@param volume: Zahl von 1 -100
@type namespace: string
@param namespace: Namespace der Source (immer mixer)
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
self.command('volume', namespace, str(pos) + ' ' + str(volume))
return self.message
# ------------------------------------------------------------------------------------------ #
def playlist_remaining(self):
"""
Wie lange läuft der aktuelle Track der Playlist noch
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
self.command('remaining', 'playlist')
return self.message
# ------------------------------------------------------------------------------------------ #
def help(self):
"""
get liquidsoap server help
@rtype: string
@return: the response of the liquidsoap server
"""
if self.connected:
self.command('help', '')
print('read (' + str(len(self.message)) + ' bytes): ' + self.message)
return self.message
# ------------------------------------------------------------------------------------------ #
def version(self):
"""
Liquidsoap get version
@rtype: string
@return: the response of the liquidsoap server
"""
if self.connected:
message = 'version'
self.command(message, '')
return self.message
# ------------------------------------------------------------------------------------------ #
def uptime(self):
"""
Liquidsoap get uptime
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
if self.connected:
self.command('uptime', '')
print('read (' + str(len(self.message)) + ' bytes): ' + self.message)
return self.message
# ------------------------------------------------------------------------------------------ #
def byebye(self):
"""
Liquidsoap say byebye
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
if self.connected:
message = 'quit'
self.command("", message) # client.sendall(message.encode())
# self.read()
# print("read: " + self.message)
return self.message
\ No newline at end of file
from modules.communication.liquidsoap.LiquidSoapPlayerClient import LiquidSoapPlayerClient
class LiquidSoapCommunicator:
lqs = None
debug = None
def __init__(self, debug=False):
# Der Liquidsoap Client
lqs_socket = "/home/gg/PycharmProjects/aura/modules/liquidsoap/simplestmixer.sock"
self.lqs = LiquidSoapPlayerClient(lqs_socket)
self.debug = debug
def switchmixernumber(self, mixernumber, activate=True):
return self.sendLqcCommand(self.lqs, "mixer", "select", mixernumber, activate)
def getactivemixer(self):
activeinputs = []
self.enableTransaction()
self.openConn(self.lqs)
inputs = self.__getmixerinputs__()
cnt = 0
for input in inputs:
status = self.__getmixerstatus__(cnt)
if "selected=true" in status:
activeinputs.append(input)
cnt = cnt + 1
self.disableTransaction()
self.closeConn(self.lqs)
return activeinputs
def getmixerstatus(self):
inputstate = {}
self.enableTransaction()
self.openConn(self.lqs)
inputs = self.__getmixerinputs__()
cnt = 0
for input in inputs:
inputstate[input] = self.__getmixerstatus__(cnt)
cnt = cnt + 1
self.disableTransaction()
self.closeConn(self.lqs)
return inputstate
def __getmixerinputs__(self):
return self.sendLqcCommand(self.lqs, "mixer", "inputs")
def __getmixerstatus__(self, mixernumber):
return self.sendLqcCommand(self.lqs, "mixer", "status", mixernumber)
# ------------------------------------------------------------------------------------------ #
def getClient(self):
return self.lqs
# ------------------------------------------------------------------------------------------ #
def sendLqcCommand(self, lqs_instance, namespace, command, *args):
"""
Ein Kommando an Liquidsoap senden
@type lqs_instance: object
@param lqs_instance: Instance of LiquidSoap Client
@type namespace: string
@param namespace: Namespace of function
@type command: string
@param command: Function name
@type args: list
@param args: List of parameters
@rtype: string
@return: Response from LiquidSoap
"""
try:
# connect
if self.transaction == False:
self.openConn(lqs_instance)
# call wanted function ...
func = getattr(lqs_instance, namespace)
# ... and fetch the result
result = func(command, *args)
# say byebye
if self.transaction == False:
self.closeConn(lqs_instance)
except Exception as e:
# Verbindung gescheitert - Fehler an Client
if command.find('record') > -1:
raise e
#self.fatal('02')
else:
raise e
#self.fatal('01')
print(e)
self.notifyClient()
# Instanz/Thread zerstören - aufrufende Funktion wird nicht weiter abgearbeitet
del self
else:
# byebye = getattr(lqs_instance, 'byebye')
# byebye()
if self.debug:
if(len(args) == 0):
print("response to lqc command [" + namespace + "." + command + "] is " + str(result))
else:
print("response to lqc command [" + namespace + "." + command + " " + str(args) +"] is "+str(result))
return result
def enableTransaction(self):
self.transaction = True
def disableTransaction(self):
self.transaction = False
def openConn(self, socket):
socket.connect()
def closeConn(self, socket):
socket.byebye()
from modules.communication.liquidsoap.LiquidSoapClient import LiquidSoapClient
class LiquidSoapPlayerClient(LiquidSoapClient):
# ------------------------------------------------------------------------------------------ #
def mixer(self, command, *args):
if command == "status":
return self.mixerstatus(*args)
if command == "inputs":
return self.mixerinputs()
if command == "select":
if len(args) == 2:
return self.mixerselect(args[0], args[1])
return "LiquidSoapPlayerClient does not understand "+command+str(args)
# ------------------------------------------------------------------------------------------ #
def mixerinputs(self):
"""
List all channels on the mixer
@type namespace: string
@param namespace: lqs namespace
@rtype: list
@return: answer of our lqs server
"""
# self.logger.info("listchannels modules/controller/liquidsoap.py")
# send command
self.command("mixer", "inputs")
# convert to list and return it
return self.message.strip().split(' ')
# ------------------------------------------------------------------------------------------ #
def mixerstatus(self, pos=""):
"""
Get state of a source in the mixer
@type pos: string
@param pos: Mixerposition
@rtype: string
@return: Response from LiquidSoap
"""
self.command("mixer", "status", str(pos))
return self.message
# ------------------------------------------------------------------------------------------ #
def mixerselect(self, pos, activate):
"""
Kanal/Source aktivieren
@type pos: string
@param pos: Die Position
@type namespace: string
@param namespace: Namespace der Source
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
self.command("mixer", "select", str(pos) + " " + str(activate).lower())
return self.message
from modules.communication.liquidsoap.LiquidSoapClient import LiquidSoapClient
class LiquidSoapRecorderClient(LiquidSoapClient):
# ------------------------------------------------------------------------------------------ #
def recorder_setfilename(self, filename):
"""
Dateinamen für Aufnahme (Vorproduktion) definieren
@type filename: string
@param filename: Dateiname - Angabe ohne Verzeichnis und mit Extension
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
self.command('setfilename', 'record', str(filename))
return self.message
# ------------------------------------------------------------------------------------------ #
def stop_record(self):
"""
Recorder stoppen
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
message = self.command('stop', 'record')
return self.message
# ------------------------------------------------------------------------------------------ #
def start_record(self):
"""
Recorder starten
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
self.command('start', 'record')
return self.message
# ------------------------------------------------------------------------------------------ #
def recorder_data(self):
"""
Daten des recorders erhalten
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
self.command('curfile', 'record')
return self.message
\ No newline at end of file
File added